Skip to content

Commit

Permalink
Merge pull request #6568 from dotty-staging/add-tupled-functions
Browse files Browse the repository at this point in the history
Add generalized tupled functions abstraction
  • Loading branch information
nicolasstucki authored May 29, 2019
2 parents 1dcf62d + 9507c1f commit 0dc2172
Show file tree
Hide file tree
Showing 20 changed files with 898 additions and 5 deletions.
24 changes: 22 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import collection.mutable
import Denotations.SingleDenotation
import util.SimpleIdentityMap

import scala.annotation.tailrec

object Definitions {

/** The maximum number of elements in a tuple or product.
Expand All @@ -24,7 +26,7 @@ object Definitions {
* The limit 22 is chosen for Scala2x interop. It could be something
* else without affecting the set of programs that can be compiled.
*/
val MaxImplementedFunctionArity: Int = 22
val MaxImplementedFunctionArity: Int = MaxTupleArity
}

/** A class defining symbols and types of standard definitions
Expand Down Expand Up @@ -795,6 +797,13 @@ class Definitions {
def TupleXXL_apply(implicit ctx: Context): Symbol =
TupleXXLModule.info.member(nme.apply).requiredSymbol("method", nme.apply, TupleXXLModule)(_.info.isVarArgsMethod)

lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction")
def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass

lazy val InternalTupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.internal.TupledFunction")
def InternalTupleFunctionClass(implicit ctx: Context): ClassSymbol = InternalTupledFunctionTypeRef.symbol.asClass
def InternalTupleFunctionModule(implicit ctx: Context): Symbol = ctx.requiredModule("scala.internal.TupledFunction")

// Annotation base classes
lazy val AnnotationType: TypeRef = ctx.requiredClassRef("scala.annotation.Annotation")
def AnnotationClass(implicit ctx: Context): ClassSymbol = AnnotationType.symbol.asClass
Expand Down Expand Up @@ -1187,10 +1196,21 @@ class Definitions {

def tupleType(elems: List[Type]): Type = {
val arity = elems.length
if (arity <= MaxTupleArity && TupleType(arity) != null) TupleType(arity).appliedTo(elems)
if (0 < arity && arity <= MaxTupleArity && TupleType(arity) != null) TupleType(arity).appliedTo(elems)
else TypeOps.nestedPairs(elems)
}

def tupleTypes(tp: Type, bound: Int = Int.MaxValue)(implicit ctx: Context): Option[List[Type]] = {
@tailrec def rec(tp: Type, acc: List[Type], bound: Int): Option[List[Type]] = tp match {
case _ if bound < 0 => Some(acc.reverse)
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args.head :: acc, bound - 1)
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
case _ => None
}
rec(tp.stripTypeVar, Nil, bound)
}

def isProductSubType(tp: Type)(implicit ctx: Context): Boolean =
tp.derivesFrom(ProductType.symbol)

Expand Down
48 changes: 47 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,51 @@ trait Implicits { self: Typer =>
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext)
else EmptyTree

def synthesizedTupleFunction(formal: Type): Tree = {
formal match {
case AppliedType(_, funArgs @ fun :: tupled :: Nil) =>
def functionTypeEqual(baseFun: Type, actualArgs: List[Type], actualRet: Type, expected: Type) = {
expected =:= defn.FunctionOf(actualArgs, actualRet, defn.isImplicitFunctionType(baseFun), defn.isErasedFunctionType(baseFun))
}
val arity: Int = {
if (defn.isErasedFunctionType(fun) || defn.isErasedFunctionType(fun)) -1 // TODO support?
else if (defn.isFunctionType(fun)) {
// TupledFunction[(...) => R, ?]
fun.dropDependentRefinement.dealias.argInfos match {
case funArgs :+ funRet if functionTypeEqual(fun, defn.tupleType(funArgs) :: Nil, funRet, tupled) =>
// TupledFunction[(...funArgs...) => funRet, ?]
funArgs.size
case _ => -1
}
} else if (defn.isFunctionType(tupled)) {
// TupledFunction[?, (...) => R]
tupled.dropDependentRefinement.dealias.argInfos match {
case tupledArgs :: funRet :: Nil =>
defn.tupleTypes(tupledArgs) match {
case Some(funArgs) if functionTypeEqual(tupled, funArgs, funRet, fun) =>
// TupledFunction[?, ((...funArgs...)) => funRet]
funArgs.size
case _ => -1
}
case _ => -1
}
}
else {
// TupledFunction[?, ?]
-1
}
}
if (arity == -1)
EmptyTree
else if (arity <= Definitions.MaxImplementedFunctionArity)
ref(defn.InternalTupleFunctionModule).select(s"tupledFunction$arity".toTermName).appliedToTypes(funArgs)
else
ref(defn.InternalTupleFunctionModule).select("tupledFunctionXXL".toTermName).appliedToTypes(funArgs)
case _ =>
EmptyTree
}
}

/** If `formal` is of the form Eql[T, U], try to synthesize an
* `Eql.eqlAny[T, U]` as solution.
*/
Expand Down Expand Up @@ -828,7 +873,8 @@ trait Implicits { self: Typer =>
trySpecialCase(defn.GenericClass, synthesizedGeneric,
trySpecialCase(defn.TastyReflectionClass, synthesizedTastyContext,
trySpecialCase(defn.EqlClass, synthesizedEq,
trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed))))))
trySpecialCase(defn.TupledFunctionClass, synthesizedTupleFunction,
trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed)))))))
}
}

Expand Down
5 changes: 3 additions & 2 deletions docs/docs/reference/dropped-features/limit22.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ The limits of 22 for the maximal number of parameters of function types
and the maximal number of fields in tuple types have been dropped.

Functions can now have an arbitrary number of
parameters. Functions beyond Function22 are represented with a new trait
`scala.FunctionXXL`.
parameters. Functions beyond Function22 are erased to a new trait
`scala.FunctionXXL` and tuples beyond Tuple22 are erased to a new trait `scala.TupleXXL`.
Both of these are implemented using arrays.

Tuples can also have an arbitrary number of fields. Furthermore, they support generic operation such as concatenation and indexing.
78 changes: 78 additions & 0 deletions docs/docs/reference/other-new-features/tupled-function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
layout: doc-page
title: "Tupled Function"
---

Tupled Function
----------------------

With functions bounded to arities up to 22 it was possible to generalize some operation on all function types using overloading.
Now that we have functions and tuples generalized to [arities above 22](https://dotty.epfl.ch/docs/reference/dropped-features/limit22.html) overloading is not an option anymore.
The type class `TupleFunction` provides a way to abstract directly over a function of any arity converting it to an equivalent function that receives all arguments in a single tuple.

```scala
/** Type class relating a `FunctionN[..., R]` with an equivalent tupled function `Function1[TupleN[...], R]`
*
* @tparam F a function type
* @tparam G a tupled function type (function of arity 1 receiving a tuple as argument)
*/
@implicitNotFound("${F} cannot be tupled as ${G}")
sealed trait TupledFunction[F, G] {
def tupled(f: F): G
def untupled(g: G): F
}
```

The compiler will synthesize an instance of `TupledFunction[F, G]` if:

* `F` is a function type of arity `N`
* `G` is a function with a single tuple argument of size `N` and it's types are equal to the arguments of `F`
* The return type of `F` is equal to the return type of `G`
* `F` and `G` are the same kind of function (both are `(...) => R` or both are `given (...) => R`)
* If only one of `F` or `G` is instantiated the second one is inferred.

Examples
--------
`TupledFunction` can be used to generalize the `Function1.tupled`, ... `Function22.tupled` methods to functions of any arities ([full example](https://github.com/lampepfl/dotty/tests/run/tupled-function-tupled.scala))

```scala
/** Creates a tupled version of this function: instead of N arguments,
* it accepts a single [[scala.Tuple]] argument.
*
* @tparam F the function type
* @tparam Args the tuple type with the same types as the function arguments of F
* @tparam R the return type of F
*/
def (f: F) tupled[F, Args <: Tuple, R] given (tf: TupledFunction[F, Args => R]): Args => R = tf.tupled(f)
```

`TupledFunction` can be used to generalize the `Function.untupled` methods to functions of any arities ([full example](https://github.com/lampepfl/dotty/tests/run/tupled-function-untupled.scala))

```scala
/** Creates an untupled version of this function: instead of single [[scala.Tuple]] argument,
* it accepts a N arguments.
*
* This is a generalization of [[scala.Function.untupled]] that work on functions of any arity
*
* @tparam F the function type
* @tparam Args the tuple type with the same types as the function arguments of F
* @tparam R the return type of F
*/
def (f: Args => R) untupled[F, Args <: Tuple, R] given (tf: TupledFunction[F, Args => R]): F = tf.untupled(f)
```

`TupledFunction` can also be used to generalize the [`Tuple1.compose`](https://github.com/lampepfl/dotty/tests/run/tupled-function-compose.scala) and [`Tuple1.andThen`](https://github.com/lampepfl/dotty/tests/run/tupled-function-andThen.scala) methods to compose functions of larger arities and with functions that return tuples.

```scala
/** Composes two instances of TupledFunctions in a new TupledFunctions, with this function applied last
*
* @tparam F a function type
* @tparam G a function type
* @tparam FArgs the tuple type with the same types as the function arguments of F and return type of G
* @tparam GArgs the tuple type with the same types as the function arguments of G
* @tparam R the return type of F
*/
def (f: F) compose[F, G, FArgs <: Tuple, GArgs <: Tuple, R](g: G) given (tg: TupledFunction[G, GArgs => FArgs], tf: TupledFunction[F, FArgs => R]): GArgs => R = {
(x: GArgs) => tf.tupled(f)(tg.tupled(g)(x))
}
```
2 changes: 2 additions & 0 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ sidebar:
url: docs/reference/other-new-features/erased-terms.html
- title: Kind Polymorphism
url: docs/reference/other-new-features/kind-polymorphism.html
- title: Tupled Function
url: docs/reference/other-new-features/tupled-function.html
- title: Other Changed Features
subsection:
- title: Volatile Lazy Vals
Expand Down
1 change: 1 addition & 0 deletions library/src-3.x/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ object DottyPredef {
}

inline def the[T] given (x: T): x.type = x

}
Loading

0 comments on commit 0dc2172

Please sign in to comment.