-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add dependent function types #3464
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
Conversation
We should have some neg tests for implicit dependent function types. |
This is great. Now we can almost have a nice first-class polymorphic lambda syntax. > def baz(f: (t: C) => List[t.T] => List[t.T]): Int =
f(new C{type T = Int})(List(1,2,3)).head
> baz(t => (ls:List[t.T]) => ls.reverse)
res0: Int = 3 Unfortunately, we currently need to explicitly state the type of Ideally, is it reasonable to expect the following program to work? def baz(f: implicit (t: C) => List[t.T] => List[t.T]): Int = f(new C{type T = Int})(List(1,2,3)).head
baz(ls => ls.reverse) |
*/ | ||
def instantiateDependent(tp: Type, tparams: List[Symbol], vparamss: List[List[Symbol]])(implicit ctx: Context): Unit = { | ||
val dependentVars = new TypeAccumulator[Set[TypeVar]] { | ||
lazy val params = (vparamss :\ tparams)( _ ::: _) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It this equivalent to vparamss.flatten ::: tparams
? I guess as order doesn't matter it would be (tparams :: vparamss).flatten
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both will not conserve the last param list in vparamss, this might be useful in all non type polymorphic methods with a single argument list ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(tparams :: vparamss).flatten
is indeed much clearer.
ParamRefNameString(tp) ~ ".type" | ||
case tp: TypeParamRef => | ||
ParamRefNameString(tp) ~ lambdaHash(tp.binder) | ||
case tp: SingletonType => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something is wrong with the way dependent function types are printed:
scala> val depfun1: DF = (x: C) => x.m
val depfun1: (C => ){apply: (x: C): } = <function1>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now.
// Reproduced here because the one from DottyPredef is lacking a Tasty tree and | ||
// therefore can't be inlined when testing non-bootstrapped. | ||
// But inlining `implicitly` is vital to make the definition of `ifun` below work. | ||
inline final def implicitly[T](implicit ev: T): T = ev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would changing implicitly to return ev.type
instead of T
help here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that adding inline
changes how a method is typechecked doesn't seem like a great thing. I think @OlivierBlanvillain has some examples where it breaks working code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a bit nervous about changing it to ev.type
. The inferencer has to do major acrobatics to deal with implicit method types. It might interfere with something else. The current type of implicitly
is simpler and clearer.
@@ -1686,7 +1714,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit | |||
} | |||
|
|||
protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { | |||
val defn.FunctionOf(formals, resType, true) = pt.dealias | |||
val defn.FunctionOf(formals, _, true) = pt.dealias.dropDependentRefinement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since .dropDependentRefinement
is always used after .dealias
I guess the dealias could be pushed inside
|
||
args match { | ||
case ValDef(_, _, _) :: _ => | ||
typedDependent(args.asInstanceOf[List[ValDef]])( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this cast, args matches ValDef()
and is casted to a List[ValDef]
??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I didn't see the ::
val (protoFormals, protoResult) = decomposeProtoFunction(pt, params.length) | ||
def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { | ||
val untpd.Function(args, body) = tree | ||
val params = args.asInstanceOf[List[untpd.ValDef]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe type annotate args
in the pattern above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
trait C { type M; val m: M } | ||
|
||
type DF = (x: C) => x.M | ||
val depfun1: DF = (x: C) => x.m |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like than both the type and the value have the same syntax :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is something that I really like about Scala in general.
} | ||
val funCls = defn.FunctionClass(args.length, isImplicit) | ||
|
||
def typedDependent(params: List[ValDef])(implicit ctx: Context) = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a comment explaining to quickly describe what this function returns.
93bf1f8
to
07ab554
Compare
Tests indicate it's not needed anymore. Also: fix neg test error position.
For the moment we want to rule this out. There are quite a lot of bits we have to think through before we can allow this.
If the result type of a dependent function contains a type variable whose bounds refer to the function's parameters, that type variable has to be instantiated before the method type is formed. Otherwise, if the type variable is instantiated later the method result type will refer to the original parameter symbols instead of the method types ParamRefs.
`isFunctionType` nor returns true for dependent as well as non-dependent function types. `isNonDepFunctionType` returns true only for the latter.
The change that TermParamRefs are Singletons caused a change how they are printed, which this commit reverts. We had before def f(x: T): x.type def f(x: T): x.type # M Once TermParamRefs were singletons we got: def f(x: T): T (x) def f(x: T): x.M With this commit we now get: def f(x: T): x.type def f(x: T): x.M
The missing case was discovered after inlining `implicitly`. It produced a type mismatch in Ycheck after lambda lift.
Given a dependently typed function value like this one: def f: (x: C) => D => x.T => E we did not propagate information about the subsequent types `D` and `x.T` to the result type of the closure with parameter `(x: C)`. Doing so is a bit tricky because of the dependency. But it's necessary to infer the types of subsequent parameters. Test case: eff-dependent.scala
We now support dependent function types as long as there are no inter-parameter dependencies.
Assuming
a dependent function type is written like this:
It gets represented as a normal function type that approximates the result type together with a refinement that adds an
apply
method with the precise type. E.g. the type above would be represented asFor the moment, dependent function types cannot be implicit. Maybe we can lift that restriction in the future.Dependent function types are important, because they are already in DOT, and they are probably a key feature to enable polymorphic effect systems.