-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
New implicit parameter & argument syntax #1260
Comments
I like this because it would prevent sneaky and hard to diagnose bugs like 5225f00 But I worry that newcomers will think that |
This would still lack the expressive power to skip an implicit parameter section that is immediately followed by another implicit param section. The workaround would be the same for as the way we selectively provide implicit params within a single section, by using def foo ?(a: Int) ?(b: Int) = 0
foo ?(1)?(2) // explicitly providing a and b
foo ?(implicitly)?(2) // explicitly providing b |
@retronym Good point. I think the workaround is fine. |
Hi! I like that you address this problem :-) Should you want to bikeshed syntax, I'd consider using characters that are reserved — my proposal happened to use
Slight issue with this workaround: you might need many implicitly to skip many arguments, and adding implicit parameters would break more source code. What about using allowing to use named parameters to skip implicit sections? That's the solution in Agda, and it easily scales to longer lists of arguments.
|
@Blaisorblade The named argument trick looks neat. I'd like to check how hard it would be to add it. |
So it would be " I prefer using an additional keyword for introducing implicit parameters such as Declaration: |
This |
As an alternative, how about reusing the period ( implicit val str: String = "x = "
def foo(x: Int).(y: String) = y+x
foo(0) // "x = 0"
foo(1).("Value: ") // "Value: 1" Also, to which implicit block would context bounds be appended? Could we say, an entirely new block appended immediately after the type parameters, or would that be more confusing than a new block at the end? Perhaps, given this new lightweight syntax, the type parameter block is the wrong place to be defining context bounds. Much as I like the context-bound syntax, assuming an anonymous parameter syntax, there's not that much difference between, def foo[T: Typeclass](t: T) = ... and, def foo[T](t: T).(_: Typeclass[T]) = ... versus the original, def foo[T](t: T)(implicit ev: Typeclass[T]) = ... and I think the argument could quite easily be made that the additional clarity at the definition site (especially given potential confusion about which implicit block contains the context bounds) would be a net benefit. Generally, I'm very supportive of the original proposal, though. 👍 |
Like the proposal, I disagree with the In fact, I don't really see the problem with having stuff like def f(x: Int) implicit (ctx: Context) = ... and f(3) implicit(ctx) or maybe f(3) implicitly(ctx) Note that I have just replaced p.s. I am kinda a fan of the implicit keyword because its easier to spot with your eye and its not confused with anything else. If we really want a symbol, than I think something like
|
Well, implicits are sort-of-optional arguments, the |
Most other mainstream languages tend to use I guess my biggest grip is more how it visually/syntactically looks |
I like the proposal, but I'm not sure about the syntax, as def f[A, B]?(a: ?A, b: ?B) |
Add an optional Another option is to make implicitness part of the typesystem when you add effects. E.g. |
Also implicits can simply become annotations than a baked in language feature. |
I assume that this change in syntax should also be reflected on lambdas with implicits. @odersky do you have a proposed syntax for those cases? In particular consider a method that has a parameter that is a lambda which receives one implicit parameter. Would that clash with the Or type with |
Another way might be |
How about allowing use of a keyword in the default syntax?
On Aug 19, 2016 1:51 AM, "Suminda Dharmasena" notifications@github.com
|
@stuhood Most proposals are about implicit parameter lists—but all parameters in the same list are of the same "implicitness", unlike in your example. Going in that direction seems be more problematic though I admit I lack ATM a specific example of problems with it. |
I think new syntax should allow to pick and choose what is implicit. |
A bit late to the party, here is my proposal for explicitly providing implicit arguments: remove this option from the language. Instead, implicit arguments could be provided explicitly using the following: (Assuming implicit class ExplicitlySyntax[I, O](f: implicit I => O) {
def explicitly(i: I): O = {
implicit val ii: I = a
f.apply
}
} Example of usage: def f(i: Int)(implicit j: Int)(k: Int)(implicit l: Int): Int = i + j + k + l
f(1)(3) // i = 1, k = 3, j & l implicitly resolved
f(1).explicitly(2)(3).explicitly(4) // i = 1, j = 2, k = 3, l = 4 I think this would play nicely with removing parameter blocks with multiple implicits from the language, which would also fix point 2 of the original motivations. (otherwise |
If you do that, how would you migrate existing code to the proposed language? The original proposal is also confusing in that regard, but since code migration is an important concern, I assumed the old syntax would still be supported with the current semantics... I don't immediately see how in your proposal. Unless you do all those changes on the new and improved implicit syntax. |
Code migration is an issue but shouldn't you make dotty as an opportunity to improve and and fix problems in a breaking way. Let as long as Scalafix can fix something breaking will not be an issue. |
@Blaisorblade Turning f(Nil)(ctx) becomes {
implicit val $c: Context = ctx
f(Nil)
} Which hopefully is equivalent and cross compile with both scalac/my proposal. |
I still feel better to represent |
I thought we'd keep the syntax The migration story for
would enforce that an implicit argument is passed to So, if we can get scalac to accept |
@Blaisorblade : My feeling is that aligning implicit arguments with default arguments as @sirinath suggested (although not directly relevant to the discussion of multiple parameter lists) would be a cohesive change. In particular, the semantics of default arguments (needing to be defined at the end of a parameter list) match. The ability to pass implicit arguments by name would also be cohesive. Additionally, in a huge number of cases, defining an implicit would not require a second parameter list. You'd only define a second parameter list if it gave the callsite syntax you desired. I suspect that a vast majority of currently multi-arg-list functions would not need to be any longer. |
Dotty should be take as an opportunity to make breaking changes to improve. |
@stuhood The thing that I think would be difficult to reconcile (though maybe not impossible) is the calculations of LUBs for unconstrained types mentioned in more than one parameter in the same parameter block, when implicit resolution is involved too. In Scala 2.x, types mentioned in implicit parameter blocks get fixed parameter-by-parameter, left-to-right. It's often very useful to walk this delicate path to get desirable type inference, though that's not to say alternative ways of achieving the same thing wouldn't be possible too... |
I am not sure about the internals or if relates to this specific case. by Guy Steele mentions how it was done differently in this in comparison to Scala: https://www.youtube.com/watch?v=EZD3Scuv02g Aside from this I am really hoping that Dotty borrows the operator overloading described in the above. |
I'm personally not a fan of default implicits, so a proposal that use an assignment like @sirinath suggested with |
@acjay I think your example would be unaffected by my proposal. To be clear, I don't advocate for getting rid of the status quo way of doing things (with a keyword and an additional parameter list). You can still encode your use case as: def mapAsync[B](f: A => B)(parallelism: Int = implicitlyOr(2)) = ... where we have: def implicitlyOr[A](default: A)(implicit a: A = default) = a or if we don't want to use the legacy way of declaring implicits, we can also define the above as: def implicitlyOr[A](default: A)(a: Defaulted[A] = implicitly) = a match {
case Default => default
case ImplicitValue(v) => v
}
trait Defaulted[+A]
case object Default extends Defaulted[Nothing]
case class ImplicitValue[A](value: A) extends Defaulted[A]
object Defaulted extends LowPriorityDefaulted {
implicit def apply[A](a: A = implicitly) = ImplicitValue(a) }
class LowPriorityDefaulted { implicit def default = Default } |
Let's allow any parameter block to contain zero or more implicits, and use
Before: xs.sorted(3) // I wanted element at index 3 but I got type mismatch?!
xs.sorted(myOrder)(3) // Okay During transition: xs.sorted(3) // Warning: bare passing of implicit parameters is deprecated. Try ( ;; 3).
// (Then error about scala.math.Ordering)
xs.sorted(myOrder)(3) // Also warning
xs.sorted( ;; myOrder)(3) // Works
xs.sorted(implicit MyOrder)(3) // Fine too, more explicit After transition: xs.sorted(3) // Works!
xs.sorted( ;; myOrder)(3) // Also works!
xs.sorted(implicit myOrder)(3) // Works too! |
Thanks for the proposal, @lihaoyi! I would love to hear more about how typechecking could work, particularly with respect to dependently-typed implicits. The ability to interleave implicit and explicit parameters would be very nice, but I think it introduces quite a bit more complexity than the proposal suggests (or at least, details which need to be ironed out). |
Very thought provoking proposal! It's true that implicits and defaults have overlapping use cases. As @acjay notes, it is already possible to combine them, like this: def mapAsync[B](f: A => B)(implicit parallelism: Int = 2) I am dubious about using named parameters as a disambiguation tool though. First, implicits coming from context bounds class C { def apply(x: T): T }
def foo(implicit x: T): C Then There's also an important difference between default parameters and implicits that seems to have been glossed over so far: they behave differently under partial application. Given: implicit val i: Int = 2
def f(implicit x: Int): Int
def g(x: Int = 1): Int we have: f maps to f(2) : Int
g maps to (x: Int) => g(x) : Int => Int |
@propensive My understanding is that today, parameter lists are typechecked one at a time, and so dependent types or values have to be fully resolved in the previous parameter list:
This is certainly pretty easy to reason about, but it's also quite limiting. Just this week, I wanted to do something where a parameter's default value depended on implicitly available context. I'll try to simplify the essence of it to be bite-sized:
This doesn't work, for reasons I'm sure we all understand.
It seems like it would solve my modeling issue. Although, with Li Haoyi's idea, I'd probably do:
Let me end by saying I've long pined for keyword args to have the same level of syntactic support as positional args, which could be a side-effect of exploring this proposal. |
@lihaoyi when I began scala, this his how I expected implicits to work. The power of this proposal is that it feels so natural. Great proposal ! On the other hand, the proposition of implicits with the |
I see a very simple solution to that: an implicit argument list can only be omitted if it's the last specified argument list. So What's more, I think the rule above is backward compatible: in current Scala, |
@odersky some responses:
Here's one scheme that fits well with my proposal: you can pass implicit parameters positionally, only if you use the def foo(a: Int, implicit b: String, c: Boolean) = ???
foo(1, true) // b is implicit
foo(1, b = "lol", true) // b is explicitly provided
foo(1, implicit "lol", true) // b is explicitly provided That will allow both passing arguments positionally while also allowing inference of interleaved implicit and non-implicit parameters. Another alternative, is to give up interleaved-implicit-and-non-implicit parameters-within-single parameter-list: then we could just require that implicit parameters always occur after non-implicit parameters in a parameter lists: def foo(a: Int, b: String, implicit c: Boolean) = ??? // implicits must come at the end
foo(1, "lol") // b is implicit
foo(1, "lol", c = true) // b is explicitly provided
foo(1, "lol", true) // b is explicitly provided There are probably other schemes we could come up with to allow passing in implicit parameters positionally, if we really wanted to explore that design space. I think both of the above syntaxes look a lot more natural than
I think this is a problem we can solve by decree. For example, we may say that given: class C { def apply(x: T): T }
def foo(implicit x: T): C Then This point is a bit like PEG parsers v.s. context-free-grammars: when there's ambiguity it's a bit arbitrary to pick one parse over the other, but as long as it's simple & predictable, it may be good enough or even better than an unambigious parse based around clever constraint-solving.
To be more precise, currently implicits behave differently under currying partial application, and there's an arbitrary restriction on using implicits for non-curried partial application: // non-curried partial application with default arguments
def foo(i: Int, b: Boolean = true) = ???
f(1)
// non-curried partial application with implicits (doesn't work at all in status quo)
def foo(i: Int, implicit b: Boolean) = ???
f(1) My proposal makes implicits and defaults behave the same under non-curried partial application (the second example above). Making them behave the same under non-curried partial application means someone learning about implicits doesn't need to learn about currying at the same time. Maybe for people with a Haskell/Scheme/OCaml background are already familiar, but multiple-argument-list currying is very foreign to people from Java/C#/Python/Javascript backgrounds (see e.g. the perpetual confusion around parametrized-decorators in Python). And ultimately, "i want to pick a default parameter value from the local scope" and "I want to use multiple sets of parentheses when i call this function" are entirely orthogonal concerns We could preserve the existing syntax & semantics (eliding entire argument lists, etc.) for curried implicit parameter lists, whether for compatibility or for other reasons, without impacting the main body of the proposal. |
I don't understand why not Also, I missed what your rationale was for disallowing passing implicit arguments positionally. Why not simply use exactly the same rules as are currently in place for default arguments? // current Scala:
def f(x: Int=0, y: Int) = x + y
f(0) // not enough arguments
f(y = 0) // ok
f(0, 1) // ok
// under new proposal:
def g(x: Int = implicit, y: Int) = x + y // or in your proposed syntax (implicit x: Int, y: Int)
g(0) // not enough arguments
g(y = 0) // ok
g(0, 1) // ok |
@Lihaoy OK. As far as I see it then, the proposal does not affect the curried implicits of current Scala since by-name parameters are not a good disambiguation mechanism. We still need something else, and The question is then, should we add another way to define implicits which makes them similar to default parameters? If we do, we have to solve the problem that implicits and defaults behave differently with respect to partial application. Here's a problematic scenario. Say you have def f(x: Int = 1, implicit y: C) Then Now say you want to turn the first parameter into an implicit as well, since you need more flexibility: def f(implicit x: Int, implicit y: C) Now
I also wanted to raise another red flag. All the examples we gave in this thread are extremely bad code since they pretend it's OK to define an implicit for a common type like |
While we are discussing implicit definition syntax, here's another thought. Currently when we write def f(x: Int)(implicit c: C) = ... implicitly[C] ... we get "two-way" implicitness - the second argument of def f(x: Int) implicit (c: C) = ... c ... This would still synthesize the second argument but class C_1 implicit (C_1_1, ..., C_1_m1)
...
class C_n implicit (C_n_1, ..., C_n_mn) Here {
implicit val c_1: C_1 = new C_1
...
implicit val c_n: C_n = new C_n
(c_1, ..., c_n)
} The reason why we do not want to do this with current implicits (and the reason why MacWire exists) is that we do not want to pollute the implicit namespace of the bodies of our components I am bringing this up here because it would also avoid the problem that implicits require curried parameter lists. (Although in fact I am not sure that's a strong argument. For better or for worse, curried parameter lists are pretty ubiquitous in Scala. You need them not just for implicits but also for better type inference, or for passing arguments in /cc @adamw |
Note: If we do adopt def f(x: Int) implicit (c: C) = ... then the current definition syntax def f(x: Int)(implicit c: C) = ... can be treated as syntactic sugar for def f(x: Int) implicit (c$: C) = { implicit val c: C = c$; ... } |
@LPTK No, the idea is that you should never be able to supply an implicit parameter "by accident" at the call site. It should be necessary that it's distinguished in some way. |
Is this actually a central goal of the design? At least it's not part of the original motivation explained in the opening message of this github issue. But I may be missing something (e.g., discussions happening offline). Though I can see that having to write |
@lihaoyi Interleaving implicit and non-implicit parameters doesn't work with current implementation of implicit function types which are encoded like this: trait ImplicitFunction1[-T0, R] extends Function1[T0, R] {
override def apply(implicit x: T0): R
}
implicit (T0) => R In the case of |
Hmm, just to make sure I'm understanding, this is because
I often use an idiom where services are modeled by Lastly, on a meta level, one question folks seem to be dancing around a bit is whether it makes sense to continue to think of implicits positionally at all. (I do get that you're largely talking about things that are additive to today's Scala, and I'm talking about breaking changes.) |
Sort of. They never were interchangeable. There was a one-way automatic conversion from one to the other, but that one's gone now as well. I believe the driving force here is that we want to be very clear about to which parameter(s) arguments are passed to. Implicits are hard enough, no need to throw an additional puzzler in the mix. |
Can you show an example? I don't see how having |
Yep that seems right!
This is true, but can be mitigated mechanically: rather than having the implicit be case class FooInt(value: Int)
object FooInt{
implicit def create(value: Int) = FooInt(value)
} This gives you the nice default-parameter syntax along with the nice specific type for implicit-resolution. It's a pattern I use pretty commonly (e.g. from |
@odersky Ah, you're right. I forgot the real reason I had needed |
Sorry in advanced for my inexperience. But 'implicitly[Foo[A]]' inside a function/method body is essentially summoning something in implicit scope, without it being Available in the visible function definition right? So, that's essentially a hidden, anonymous parameter (it's not named until you name it) that's not immediately obvious? If we call the concept of summoning a parameter in this form, without following implicit resolution, 'anonymous parameter passing' in that, it 1. Has no name/keyword 2. Has no position in the function definition. Then what would an anonymous parameter without implicits look like? (In that it's required to be passed in (has no default value defined) what would syntax look like to pass it in? I think if that was introduced orthogonal to implicit resolution, then you could have default values, implicits, and anonymous parameter passing all orthogonal to each other. |
@ryantheleach inside a method body, implicits are resolved based on the static scope of the body, so they do not behave like parameters. The resolution won't change depending on the call sites. |
@odersky 's suggestion for |
Motivation
The current syntax for implicit parameters has several shortcomings.
There can be only one implicit parameter section and it has to come at the end. Therefore
normal and implicit parameter types cannot depend on other implicit parameters except
by nesting in an inner object with an
apply
method.The syntax
(implicit x: T, y: U)
is a bit strange in thatimplicit
conceptually scopes overx
andy
but looks like a modifier for justx
.Passing explicit arguments to implicit parameters is written like normal application. This
clashes with elision of
apply
methods. For instance, if you havethen
f(a)
would pass the argumenta
to the implicit parameter and one has to writef.apply(a)
to applyf
to a regular argument.Proposal
Introduce a new symbolic delimiter,
?(
. This is one token, no spaces allowed between the?
and the(
.Write implicit parameter definitions with
?(
instead of(implicit
. E.g.instead of
Explicit arguments to implicit parameters have to be enclosed in
?(...)
. E.g.There can be several implicit parameter sections and they can be mixed with normal parameter sections. E.g.
Problems
?
can be already used as an infix operator. So the meaning of(a)?(b)
would change butonly if no space is written after the
?
, which should be rare.?
can be part of a symbolic operator. In this case the longest match rule applies. Sodef #?(x: T)
defines an operator#?
with a regular parameter list. To define#
withan implicit parameter list an additional space is needed:
def # ?(x: T)
.The text was updated successfully, but these errors were encountered: