Skip to content

Commit 0dc2172

Browse files
Merge pull request #6568 from dotty-staging/add-tupled-functions
Add generalized tupled functions abstraction
2 parents 1dcf62d + 9507c1f commit 0dc2172

20 files changed

+898
-5
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import collection.mutable
1010
import Denotations.SingleDenotation
1111
import util.SimpleIdentityMap
1212

13+
import scala.annotation.tailrec
14+
1315
object Definitions {
1416

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

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

800+
lazy val TupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.TupledFunction")
801+
def TupledFunctionClass(implicit ctx: Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass
802+
803+
lazy val InternalTupledFunctionTypeRef: TypeRef = ctx.requiredClassRef("scala.internal.TupledFunction")
804+
def InternalTupleFunctionClass(implicit ctx: Context): ClassSymbol = InternalTupledFunctionTypeRef.symbol.asClass
805+
def InternalTupleFunctionModule(implicit ctx: Context): Symbol = ctx.requiredModule("scala.internal.TupledFunction")
806+
798807
// Annotation base classes
799808
lazy val AnnotationType: TypeRef = ctx.requiredClassRef("scala.annotation.Annotation")
800809
def AnnotationClass(implicit ctx: Context): ClassSymbol = AnnotationType.symbol.asClass
@@ -1187,10 +1196,21 @@ class Definitions {
11871196

11881197
def tupleType(elems: List[Type]): Type = {
11891198
val arity = elems.length
1190-
if (arity <= MaxTupleArity && TupleType(arity) != null) TupleType(arity).appliedTo(elems)
1199+
if (0 < arity && arity <= MaxTupleArity && TupleType(arity) != null) TupleType(arity).appliedTo(elems)
11911200
else TypeOps.nestedPairs(elems)
11921201
}
11931202

1203+
def tupleTypes(tp: Type, bound: Int = Int.MaxValue)(implicit ctx: Context): Option[List[Type]] = {
1204+
@tailrec def rec(tp: Type, acc: List[Type], bound: Int): Option[List[Type]] = tp match {
1205+
case _ if bound < 0 => Some(acc.reverse)
1206+
case tp: AppliedType if defn.PairClass == tp.classSymbol => rec(tp.args(1), tp.args.head :: acc, bound - 1)
1207+
case tp: AppliedType if defn.isTupleClass(tp.tycon.classSymbol) => Some(acc.reverse ::: tp.args)
1208+
case tp if tp.classSymbol == defn.UnitClass => Some(acc.reverse)
1209+
case _ => None
1210+
}
1211+
rec(tp.stripTypeVar, Nil, bound)
1212+
}
1213+
11941214
def isProductSubType(tp: Type)(implicit ctx: Context): Boolean =
11951215
tp.derivesFrom(ProductType.symbol)
11961216

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,51 @@ trait Implicits { self: Typer =>
709709
if (ctx.inInlineMethod || enclosingInlineds.nonEmpty) ref(defn.TastyReflection_macroContext)
710710
else EmptyTree
711711

712+
def synthesizedTupleFunction(formal: Type): Tree = {
713+
formal match {
714+
case AppliedType(_, funArgs @ fun :: tupled :: Nil) =>
715+
def functionTypeEqual(baseFun: Type, actualArgs: List[Type], actualRet: Type, expected: Type) = {
716+
expected =:= defn.FunctionOf(actualArgs, actualRet, defn.isImplicitFunctionType(baseFun), defn.isErasedFunctionType(baseFun))
717+
}
718+
val arity: Int = {
719+
if (defn.isErasedFunctionType(fun) || defn.isErasedFunctionType(fun)) -1 // TODO support?
720+
else if (defn.isFunctionType(fun)) {
721+
// TupledFunction[(...) => R, ?]
722+
fun.dropDependentRefinement.dealias.argInfos match {
723+
case funArgs :+ funRet if functionTypeEqual(fun, defn.tupleType(funArgs) :: Nil, funRet, tupled) =>
724+
// TupledFunction[(...funArgs...) => funRet, ?]
725+
funArgs.size
726+
case _ => -1
727+
}
728+
} else if (defn.isFunctionType(tupled)) {
729+
// TupledFunction[?, (...) => R]
730+
tupled.dropDependentRefinement.dealias.argInfos match {
731+
case tupledArgs :: funRet :: Nil =>
732+
defn.tupleTypes(tupledArgs) match {
733+
case Some(funArgs) if functionTypeEqual(tupled, funArgs, funRet, fun) =>
734+
// TupledFunction[?, ((...funArgs...)) => funRet]
735+
funArgs.size
736+
case _ => -1
737+
}
738+
case _ => -1
739+
}
740+
}
741+
else {
742+
// TupledFunction[?, ?]
743+
-1
744+
}
745+
}
746+
if (arity == -1)
747+
EmptyTree
748+
else if (arity <= Definitions.MaxImplementedFunctionArity)
749+
ref(defn.InternalTupleFunctionModule).select(s"tupledFunction$arity".toTermName).appliedToTypes(funArgs)
750+
else
751+
ref(defn.InternalTupleFunctionModule).select("tupledFunctionXXL".toTermName).appliedToTypes(funArgs)
752+
case _ =>
753+
EmptyTree
754+
}
755+
}
756+
712757
/** If `formal` is of the form Eql[T, U], try to synthesize an
713758
* `Eql.eqlAny[T, U]` as solution.
714759
*/
@@ -828,7 +873,8 @@ trait Implicits { self: Typer =>
828873
trySpecialCase(defn.GenericClass, synthesizedGeneric,
829874
trySpecialCase(defn.TastyReflectionClass, synthesizedTastyContext,
830875
trySpecialCase(defn.EqlClass, synthesizedEq,
831-
trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed))))))
876+
trySpecialCase(defn.TupledFunctionClass, synthesizedTupleFunction,
877+
trySpecialCase(defn.ValueOfClass, synthesizedValueOf, failed)))))))
832878
}
833879
}
834880

docs/docs/reference/dropped-features/limit22.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ The limits of 22 for the maximal number of parameters of function types
77
and the maximal number of fields in tuple types have been dropped.
88

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

1314
Tuples can also have an arbitrary number of fields. Furthermore, they support generic operation such as concatenation and indexing.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
layout: doc-page
3+
title: "Tupled Function"
4+
---
5+
6+
Tupled Function
7+
----------------------
8+
9+
With functions bounded to arities up to 22 it was possible to generalize some operation on all function types using overloading.
10+
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.
11+
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.
12+
13+
```scala
14+
/** Type class relating a `FunctionN[..., R]` with an equivalent tupled function `Function1[TupleN[...], R]`
15+
*
16+
* @tparam F a function type
17+
* @tparam G a tupled function type (function of arity 1 receiving a tuple as argument)
18+
*/
19+
@implicitNotFound("${F} cannot be tupled as ${G}")
20+
sealed trait TupledFunction[F, G] {
21+
def tupled(f: F): G
22+
def untupled(g: G): F
23+
}
24+
```
25+
26+
The compiler will synthesize an instance of `TupledFunction[F, G]` if:
27+
28+
* `F` is a function type of arity `N`
29+
* `G` is a function with a single tuple argument of size `N` and it's types are equal to the arguments of `F`
30+
* The return type of `F` is equal to the return type of `G`
31+
* `F` and `G` are the same kind of function (both are `(...) => R` or both are `given (...) => R`)
32+
* If only one of `F` or `G` is instantiated the second one is inferred.
33+
34+
Examples
35+
--------
36+
`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))
37+
38+
```scala
39+
/** Creates a tupled version of this function: instead of N arguments,
40+
* it accepts a single [[scala.Tuple]] argument.
41+
*
42+
* @tparam F the function type
43+
* @tparam Args the tuple type with the same types as the function arguments of F
44+
* @tparam R the return type of F
45+
*/
46+
def (f: F) tupled[F, Args <: Tuple, R] given (tf: TupledFunction[F, Args => R]): Args => R = tf.tupled(f)
47+
```
48+
49+
`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))
50+
51+
```scala
52+
/** Creates an untupled version of this function: instead of single [[scala.Tuple]] argument,
53+
* it accepts a N arguments.
54+
*
55+
* This is a generalization of [[scala.Function.untupled]] that work on functions of any arity
56+
*
57+
* @tparam F the function type
58+
* @tparam Args the tuple type with the same types as the function arguments of F
59+
* @tparam R the return type of F
60+
*/
61+
def (f: Args => R) untupled[F, Args <: Tuple, R] given (tf: TupledFunction[F, Args => R]): F = tf.untupled(f)
62+
```
63+
64+
`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.
65+
66+
```scala
67+
/** Composes two instances of TupledFunctions in a new TupledFunctions, with this function applied last
68+
*
69+
* @tparam F a function type
70+
* @tparam G a function type
71+
* @tparam FArgs the tuple type with the same types as the function arguments of F and return type of G
72+
* @tparam GArgs the tuple type with the same types as the function arguments of G
73+
* @tparam R the return type of F
74+
*/
75+
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 = {
76+
(x: GArgs) => tf.tupled(f)(tg.tupled(g)(x))
77+
}
78+
```

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ sidebar:
8989
url: docs/reference/other-new-features/erased-terms.html
9090
- title: Kind Polymorphism
9191
url: docs/reference/other-new-features/kind-polymorphism.html
92+
- title: Tupled Function
93+
url: docs/reference/other-new-features/tupled-function.html
9294
- title: Other Changed Features
9395
subsection:
9496
- title: Volatile Lazy Vals

library/src-3.x/dotty/DottyPredef.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ object DottyPredef {
3737
}
3838

3939
inline def the[T] given (x: T): x.type = x
40+
4041
}

0 commit comments

Comments
 (0)