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 10 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
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,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 +795,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,7 +1194,7 @@ 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)
}

Expand Down
27 changes: 26 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,30 @@ 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) if defn.isFunctionType(fun) && defn.isFunctionType(tupled) =>
lazy val funTypes = fun.dropDependentRefinement.dealias.argInfos
lazy val tupledTypes = tupled.dropDependentRefinement.dealias.argInfos
if (
defn.isImplicitFunctionType(fun) == defn.isImplicitFunctionType(tupled) &&
tupledTypes.size == 2 &&
defn.tupleType(funTypes.init) =:= tupledTypes.head &&
funTypes.last =:= tupledTypes.last
) {
val arity = funTypes.size - 1
if (defn.isErasedFunctionType(fun))
EmptyTree // TODO support?
else if (arity <= Definitions.MaxImplementedFunctionArity)
ref(defn.InternalTupleFunctionModule).select(s"tupledFunction$arity".toTermName).appliedToTypes(funArgs)
else
ref(defn.InternalTupleFunctionModule).select("tupledFunctionXXL".toTermName).appliedToTypes(funArgs)
} else EmptyTree
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 +852,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.
61 changes: 61 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,61 @@
---
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 functions of any arity converting it to an equivalent function that receives all arguments in a single tuple.
nicolasstucki marked this conversation as resolved.
Show resolved Hide resolved

```scala
/** Type class relating a `FunctionN[..., R]` with an equvalent 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 apply(f: F): G
}
```

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 functions (`given` arguments or not)
julienrf marked this conversation as resolved.
Show resolved Hide resolved

Examples
--------
`TupledFunction` can be used to generalize the `Tuple2.tupled`, ... `Tuple22.tupled` method 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 (tupled: TupledFunction[F, Args => R]): Args => R = tupled(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 (tupledG: TupledFunction[G, GArgs => FArgs], tupledF: TupledFunction[F, FArgs => R]): GArgs => R = {
(x: GArgs) => tupledF(f)(tupledG(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

}
120 changes: 120 additions & 0 deletions library/src-3.x/scala/TupledFunction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package scala

import scala.annotation.implicitNotFound

/** Type class relating a `FunctionN[..., R]` with an equvalent 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 apply(f: F): G
}

package internal {

object TupledFunction {

def tupledFunction0[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G =
((args: Unit) => f.asInstanceOf[() => Any].apply()).asInstanceOf[G]
}

def tupledFunction1[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G =
((args: Tuple1[Any]) => f.asInstanceOf[Any => Any].apply(args._1)).asInstanceOf[G]
}

def tupledFunction2[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function2[_, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction3[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function3[_, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction4[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function4[_, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction5[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function5[_, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction6[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function6[_, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction7[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function7[_, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction8[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function8[_, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction9[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function9[_, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction10[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function10[_, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction11[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function11[_, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction12[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function12[_, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction13[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function13[_, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction14[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function14[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction15[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function15[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction16[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function16[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction17[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function17[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction18[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function18[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction19[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function19[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction20[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function20[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction21[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function21[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunction22[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G = f.asInstanceOf[Function22[_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _]].tupled.asInstanceOf[G]
}

def tupledFunctionXXL[F, G]: TupledFunction[F, G] = new TupledFunction {
def apply(f: F): G =
((args: TupleXXL) => f.asInstanceOf[FunctionXXL].apply(args.elems)).asInstanceOf[G]
}

}

}
Loading