New implicit parameter & argument syntax
See original GitHub issueMotivation
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 havedef f(implicit x: C): A => B
then
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.def f(x: Int)?(ctx: Context) = ...
instead of
def f(x: Int)(implicit ctx: Context) = ...
-
Explicit arguments to implicit parameters have to be enclosed in
?(...)
. E.g.f(3)?(ctx)
-
There can be several implicit parameter sections and they can be mixed with normal parameter sections. E.g.
def f ?(ctx: Context)(tree: ctx.Expr) = ...
Problems
?
can be already used as an infix operator. So the meaning of(a)?(b)
would change but only 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#
with an implicit parameter list an additional space is needed:def # ?(x: T)
.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:28
- Comments:71 (41 by maintainers)
Top GitHub Comments
I think @sirinath’s and @stuhood’s ideas on unifying default params & implicits are worth considering. As I understand it, the core of the idea is this:
Implicits as Defaults
A function defined as:
Would be called via:
As you can see, this provides a neat, already-familiar syntax for anyone who wants to provide a subset of implicit params while letting the others get inferred: something that is impossible/awkward under status quo Scala. Also note the symmetry with how Scala (and every other programming language under the sun) already treats default params:
Effectively, implicit parameters become a special case of default params: rather than the default being hardcoded, the default is implicitly resolved at the call-site, but otherwise the call-site syntax & semantics are identical.
Implicit params with defaults would also compose nicely:
Where you would be able to call
f(1)
without an implicitInt
in scope, andb
andc
would be assigned to default values. With an implicitInt
, that value would be used forb
andc
. Or you could passb
andc
explicitly. This is in fact exactly the current semantics in status-quo Scala!Implicits-as-defaults unifies two currently similar-but-awkwardly-different ways of eliding, or manually providing, values for function params: implicits & default params. It does so in a way that makes implicit params more familiar to anyone coming from any other language that supports default params: Python, Javascript, C#, C++, etc…
Implicits-as-params also decouples “implicit params” with “multiple parameter lists” entirely. Nevertheless, you can still have implicit params in a separate parameter list, as is the status quo requirement:
But you can have it as part of an existing parameter list (as shown above) and still have it be resolve implicitly. Implicits-as-defaults neatly generalizes the status quo implicit-param-list-must-always-come-last requirement: current code using implicits-always-come-last will continue to work unchanged[1].
Use Case
Consider a subprocess function, that takes in a list of command-line args, a working directory, and spawns a subprocess:
A library author may want to make the
cwd
have a default value: they may think “most” commands the user will want to run in the same directory they started the process in:However, at a later date, they may decide that it’s better if the “default” is configurable, but yet still want the user to be able to override it on a case-by-case basis. In the status quo, and under the top-level proposal above, that will require re-writing all call-sites to:
However, when you think about it, in both cases both the library author and user want the same thing: a default value, that the user can override. Does the fact that the default value is configurable really make such a big difference that the user should go and re-write all their callsites mechanically into a different syntax?
Using implicits-as-defaults, this would look like:
With implicits-as-defaults, the identical syntax in these three code samples properly reflects the identical semantics that a user wants to convey: that they sometimes want the value to be inferred, and sometimes they want to provide it explicitly. Whether the inferred value is hardcoded, computed in the default value getter, picked up from the receiver of the
subprocess
method or it is resolved from the enclosing scope implicitly, isn’t really that important w.r.t. the intent of the code.(Note that this is a real issue I’ve hit several times; ammonite-ops code is full of ugly curried function calls like
%%('git, "rev-parse", "HEAD")(pwd)
that have no real reason to be curried, except to make implicit-passing possible)Compatibility
Implicits-as-default-params can be specced out to be compatible with both the status quo Scala syntax/semantics* and the @odersky’s proposal’s call-site semantics, with two minor tweaks:
Parameter lists for which every parameter is implicit can be elided. i.e.
This will allow backwards-compatibility[1] with the existing Scala 2.x implicit-parameter-list syntax & semantics, rather than forcing people to call it via
f(1)()
as would a naive reading of the above description.Implicit parameters cannot be passed positionally:
This will allow for interspersed implicit-and-non-implicit params:
Note that keyword-only/non-positional params are themselves not a new idea, being common in Python as well as languages like Ruby or Javascript where passing arguments as dictionaries is commonplace. This should be the only tweak necessary in order to make possible all of the semantics that @odersky describes above.
Conclusion
To sum up, implicits-as-defaults would:
Allow us to get all the semantics of @odersky’s original proposal above
Provide 100% semantic backwards compatibility with the status-quo Scala semantics and syntactic backwards compatibility with the status-quo Scala syntax, needing only a trivial[1] syntactic migration.
It decouples implicit parameters with currying, separate parameter lists, and other confusing-to-newcomers language features that they have no business being coupled to and tend to make learning about implicits more confusing than is truly necessary
While at the same time associating them with default parameters, a language feature that they are very similar to (both in semantics & in use case) and that we can expect newcomers to be already familiar with
Be instantly familiar to the people already using Scala! They don’t know what
?()
or.explicitly
are, but they know you can mark parameters asimplicit
and you can pass default arguments explicitly. No re-training requiredProvides a neat, already-understood syntax/semantic for providing a subset of implicit params explicitly.
It would make implicit parameters easier to learn, not by adding special syntax & features, but by removing unnecessary coupling & arbitrary restrictions that hide the fact that implicits really aren’t that different from default/named parameters, a feature everyone already knows & loves.
I’m not going to pretend that it won’t be a lot of work tweaking the spec/parser/typechecker/inferencer/ASTs/etc. to make this implicits-as-defaults work, but as far as I can tell this proposal gives us the best of both worlds, with zero migration-costs, and some huge additional benefits in learnability of implicit parameters for newcomers to the language. So if we’re going put in the effort to do anything, we might as well do this.
[1] You would need a trivial syntactic transform to prepend the
implicit
keyword to every argument in an implicit parameter list, but that’s straightforward. Even a regex would probably do. Note that we cannot preserve the existing “every parameter after the implicit keyword is itself implicit” property without making it impossible to interleave implicit/non-implicit params.Or we could throw new syntax at it to make the new implicit-per-argument semantic opt-in, making this proposal truly zero-migration, and leaving the existing
def foo(implicit x: Int)
syntax to remain with it’s current meaning of “all params in list are implicit”.def foo(x: Int = implicit)
ordef foo(x: Int = implicit 123)
as described above, ordef foo(x: Int = implicitly)
all seem plausible,Like the proposal, I disagree with the
?
syntax though.?
implies optional, or maybe unknown, it just seems kinda random in the context of implicits.In fact, I don’t really see the problem with having stuff like
and
or maybe
Note that I have just replaced
?
withimplicit
/implicitly
and just added whitespace to make it look cleaner.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
%
would be better, since I don’t think its used anywhere else and its also visually easy to spot. Not really a fan of @propensive idea of using a period (.
), since its really hard to spot and easy to visually confuse with method invocation.#
could also work, I mean its already used for type projections but in context of implicit its somewhere else (you could also argue that they are somewhat related)