Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generalized tupled functions abstraction #6568

Merged
merged 14 commits into from
May 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
julienrf marked this conversation as resolved.
Show resolved Hide resolved

* `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