TL;DR this could be solvable by abstracting over non-value types.
That seems a very good point. In fact, it is somewhat orthogonal to implicits and applies to the reader monad as well, though it matters less there. I think the current syntax should mean option 2 for consistency (I haven’t checked what’s implemented, I’d call 1 a bug), but there is a consistent way (though I’m not 100% happy with it).
Nowadays we can’t abstract over parts of a method type, because method types are not value types, but type alias only support value types.
We know that
f2 aren’t equivalent operationally because of the function-method distinction, while
f3 are equivalent.
def f1(a: A)(b: B): C = foo def f2(a: A): B => C = b => foo def f3(a: A): Reader[B, C] = b => foo type Reader[S, T] = Function1[S, T]
To abstract over parts of a method type, you might consider something like this, with
f4 behaving like
f1, ignoring the concrete syntax:
type ImaginedReader[S, T] = (s: S): T def f4(a: A): ImaginedReader[B, C] = b => foo
I don’t know the concrete syntax for the body of
f4, what I wrote doesn’t make so much sense (trick questions: is
s in scope in
foo? Why is closure syntax not creating a closure?). But let me ignore syntax bikeshedding for this post—also because this problem doesn’t appear in the implicit case.
I’m not sure how this would look in Haskell syntax happens to support this better (I’m not 100% sure this actually works in Haskell, but it makes sense):
type ImaginedReader s t = s -> t f4: A -> ImaginedReader B C f4 a b c = foo
Allowing that is a non-trivial extension, but at least it fits in what we understand — the compiler should just eagerly inline such type aliases.
Note 1: I considered having a separate keyword from
type, but couldn’t pick a decent one—I had picked
non-value-type as a strawman but it was so ugly I didn’t like my own idea.
Note 2: “eagerly inline type aliases” is actually very non-trivial since all type definitions in Scala are mutually recursive, so I expect significant implementation restrictions and I’m not sure such aliases could be exported.
Back to @Ichoran’s concern, it could be solved by
type ImplicitReader[S, T] = (implicit s: S): T // or maybe it's // type ImplicitReader[S, T] = implicit (s: S): T // ? I'm not sure, though `implicit` outside parens makes more sense. def f5: ImplicitReader[Foo, Bar] = f5body // no abstraction here needed, just like the blog post, so fewer syntax questions
Do we want literally that? More brainstorming needed, please, but I think that’s a start.
And it turns out this goes in a similar direction—if one understands
type ImplicitTuple3[A, B, C] = ... as a non-value type alias to be inlined at each use site (which I think gives the effect you want).
A technical problem is that nowadays a parameter block is not a type at all, just part of the syntax of method types, but changing that sounds conceptually plausible. Alternatively, just abstract over the whole binder.
Another technical problem is that various non-value types (such as method types) have not only free type variables but free term variables. So you can write
foo3 aren’t allowed.
def foo1(ctx: Context)(tree: ctx.Tree): ctx.Tree type Bar = (ctx: Context)(tree: ctx.Tree): ctx.Tree def foo2: Bar type Baz(ctx: Context) = (tree: ctx.Tree): ctx.Tree def foo3(ctx: Context): Baz(ctx)
But you could generally allow type members to abstract over paths, like it happens in DOT — for non-eager types, the value arguments could be erased during compilation. Right now, any such abstraction can only be done by defining
Baz as a class, and it’s less obvious
ctx can be erased there.