I cannot predict what people will have a hard time understanding in general, other than looking at my own experience, but I believe instance is much closer to the stated purpose of implicit values:
Implied instance definitions define “canonical” values of given types that serve for synthesizing
arguments to inferable parameters
To me the ideal syntax for beginners would be something like
canonical instance of Ord[Int] { ... }
Of course newcomers will ask “how is this different from a regular instance?”, but I believe the resulting explanation is easy to understand and remember, in part precisely because it allows people to reuse their existing intuition with normal instances. We’re just saying that this instance is very special.
It seems that a common theme is context (hence the title “Contextual Abstractions”). This is used in two related but slightly different meanings:
As a domain concept independent of a particular programming language conctruct, where something in the program you are writing is regarded as the “context” for something else, e.g. ExecutionContext.
Particular programming language constructs, i.e. context bounds, context queries.
I assume the latter are named as such due to being meant to support the former.
To me it seems that this would make context a good candiate for a keyword, as in:
context a for B (to be read as “context instance a for B”)
This would have the following advantages:
it is both asymmetric and complementary with given, i.e. the parameter is given by the context, or context-given
it is coherent with the central theme avoiding introducing additional concepts
The story could be told like this (in informal language): In additional to the normal scope, Scala also has a “context scope”. With a definition like def f given T, the argument of type T will be given by the current context, i.e. the compiler will look up the context scope for an instance of that type. Context instances are put into the context scope by marking them with the keyword context, as in context t for T { }…
So we would end up with a smaller number of concepts, all united by a single theme:
contextual abstractions
context instances
context-given parameters/arguments
Context Bounds
Context Queries
It has one drawback: that “context a for B” could be read as “a is the context for B”. context instance a for B would be more precise, but the former might be an acceptable short-hand.
@kavedaa Proposed something very similar to this in the other thread (Proposal To Revise Implicit Parameters)! The two meanings of context you described align pretty well with the context and derived keywords I proposed there.
Cannot we just omit implicit parameter modifier in implicit definitions? It doesn’t add any information there.
Do we really need a new syntax for inferred terms and not a new kind of definition better suited to express “canonicity” of instances?
Let’s imagine we are able to define objects which are very much like object, but may have parameters. For example, they may have the following restrictions:
They are always final.
They should have stable path.
They doesn’t have any initialization logic (similar to what we have for value classes).
All parameters have singleton types.
Then any application of objects definition may be treated as a singleton type itself. I think it is pretty much desired semantics for typeclass instances in most of the cases. It also gives exact meaning to “canonicity” and decouples it from “inferrability”.
IntOrd, ListOrd(IntOrd) and Descending(IntOrd) are all typeclass instances, but the last one is not inferrable and should be created explicitly. They are just values and much easier to reason about. Static nature of these values probably make it possible to avoid allocation overhead mentioned in this discussion.
With this new definition we may promote implicit object and implicit objects as a default way to create inferrable typeclass instances and shorten them to just implicit before name.
I understand that this proposal may be much more complicated to spec and implement, but I don’t think we may solve existing problems by just introducing new syntax.
It’s somewhat out of topic here, but I do think there’s value in parameterized object definitions, especially if these objects can be used as stable paths, as in:
object Geometry[N: Numeric] {
case class Point(x: N, y: N) {
def sum = x + y
def map[M: Numeric](f: N => M): Geometry[M].Point =
Geometry.Point(f(x), f(y))
}
}
For it to be sound, we’d probably need to ensure no state can be stored in parameterized objects. That, or opt-in memoization of instances if one wants to hold state.
At least for me, “implicit” is a very scannable flag, and removing it would make it much harder to pick out the definitions which are part of the implicit scope.
Changing that to a different (unique) keyword is fine, the mental retraining is worth it for more coherent implicits, but dropping it altogether for a parameterized object seems like a big step backward for readability.
Alas, I have to echo and amplify @sjrd’s objection to the word “instance”.
It is already very difficult to talk about the difference between the type of a class and a particular instantiation of the class in memory. In Java, you call the latter an “object”. But in Scala, object is something else–a unique instantiation of a unique type that also can be associated with a (different) type. So if you say “object” it’s ambiguous. I always try to avoid it.
Luckily there is another widely-used term to refer to the I-have-one case instead of the this-kind-of-thing case: instance! Now we can say that class X {} is a class, and new X is an instance of X. Now we’re about to make that ambiguous too!
So if I can’t call new X an object X because object X is different, and I can’t call it an instance because instance x of Foo is also different, what can I call it?!
This is further compounded by it already being called an instance in many situations, and it not being easily disambiguated from the I-am-the-one-that-can-be-given flavor that we’re seeking to introduce.
So while I love how it reads on its own, due to the context of the rest of the language and how we speak of it I am forced to be strongly against instance as the keyword.
If none of the available suggestions work, it seems that the things that suggest giving or being given have not been discussed here: assume, offer, provide. supply.
If none of the available suggestions work, it seems that the things that suggest giving or being given have not been discussed here: assume , offer , provide . supply .
If you look at it in all required situations, I believe none of the verbs work, in particular aliases look weird.
assume for Context = localCtx
That does not hang together. We need ideally a noun, but if none is available an adjective would also work.
I see. However, I feel that not having a mandatory connective like for or as is problematic for generic instances:
assume [T] Context[T] = localCtx
assume [T] Context[T] given Evidence { def ctx = localCtx }
assume x[T] as Context[T] = localCtx
assume x[T] as Context[T] given Evidence { def ctx = localCtx }
Maybe always require as, nevertheless?
assume as Context = localCtx
assume as Context given Evidence { def ctx = localCtx }
assume x as Context = localCtx
assume x as Context given Evidence { def ctx = localCtx }
assume [T] as Context[T] = localCtx
assume [T] as Context[T] given Evidence { def ctx = localCtx }
assume x[T] as Context[T] = localCtx
assume x[T] as Context[T] given Evidence { def ctx = localCtx }
I don’t either, but I dislike them less than implied x for Context, and they don’t have the major drawback of instance preventing us from talking about single allocated copies of classes.
the Context = localCtx
the Context given Evidence = new { def ctx = localCtx }
the Context x = localContext
// or: the x: Context = localContext (doesn't read as nicely though)
the Context x given Evidence = new { def ctx = localCtx }
the [T] Context[T] = localCtx
the [T] Context[T] given Evidence = new { def ctx = localCtx }
the [T] Context[T] x = localCtx
the [T] Context[T] x given Evidence = new { def ctx = localCtx }
Reasons:
Unifies syntactically with the[Context[Foo]] used to summon.
Three-letter word aligns nicely with val and def.
Explicitly describes that the single value of type Context is being defined.
Drawbacks:
the is a hugely common word and might not carry enough weight given the importance of the construct.
If the had to become a keyword, it would make for a lot of problems
Edit: It would also be nice if the first [T] could be omitted, but I guess that would cause ambiguities if there was already a type T in lexical scope:
the Context[T] given Evidence[T] = new { ... }
// etc
Edit again: Maybe omitting the leading type parameter block could be solved by using similar rules to match types – use a lower-case name for scrutinees:
I think the version currently in Dotty pretty much nailed it, it is the best way to express what actually happens here. Assumed might be slightly better, maybe it feels a bit more natural as a kind of a dual to given. But they (assumed and implied) are pretty much the same).
I get the argument against the “for” part. although I don’t see it as a big issue, but if it is, then
assumed as Context = localCtx
assumed as Context given Evidence { def ctx = localCtx }
assumed x as Context = localCtx
assumed x as Context given Evidence { def ctx = localCtx }
assumed [T] as Context[T] = localCtx
assumed [T] as Context[T] given Evidence = new { def ctx = localCtx }
assumed [T] as Context[T] x = localCtx
assumed [T] as Context[T] x given Evidence = new { def ctx = localCtx }
or
implied as Context = localCtx
implied as Context given Evidence { def ctx = localCtx }
implied x as Context = localCtx
implied x as Context given Evidence { def ctx = localCtx }
implied [T] as Context[T] = localCtx
implied [T] as Context[T] given Evidence = new { def ctx = localCtx }
implied [T] as Context[T] x = localCtx
implied [T] as Context[T] x given Evidence = new { def ctx = localCtx }
also works.
I’ve been reading this thread since the beginning, but I still don’t really get what is the problem with “implied”. Now with “for” out of the way, What would still be wrong with the above?
Notation for methods without assignment operator was dismissed on many reasons and the imposed alternative is to replace def method { ... } with def method: Unit = { ... } which is 6 non-blank characters longer. Remember that methods returning Unit are quite frequent.
Conflict between abstract implied instances and short class notation.
Objects and classes definitions can omit curly braces if there’s nothing to add or change in object / class definition, i.e. we can write:
class A // {} not needed
class A extends B // {} not needed
object A // {} not needed
object A extends B // {} not needed
If implied instances notations are analogous to objects and classes notations then following lines would mean concrete definitions, not abstract members:
implied A
implied A given B
But then how to define abstract implied instances?
abstract class TypeclassesAggregate {
implied A // does not look like abstract member
abstract implied B // well, here 'B' refers in fact to method name, but methods don't need 'abstract' modifier
}
If we always require = (assignment operator) in implicit instances definitions then above problems don’t exist or are reduced.
Once again, there are a lot of assumptions, risks, questionable advantages, and unclear choices. Do we really need this to go into 3.0? It seems to me much safer to let the dust settle on all the clear wins that 3.0 will bring, including any part of this that is straightforward, like allowing to omit the val or def keyword when it’s inferrable, or allowing _ instead of a pointless identifier in more places (maybe including method parameters as well). At that point we’ll be in a much better position to debate changes that alter the language beyond recognition.
In the other thread I mentioned the risks of effect on scala usage metrics but there really are many more risks to consider. Again, I hear the case being made for all the benefits, but risks are usually hard to predict which is why in software you never want to make too many changes at once. Hasn’t our industry learned that lesson enough times yet?
IMO underscore as a word[1] always means the same thing, for a looser definition of meaning: “An identifier would go here if I had one in mind, but I don’t, so whatever.” That said, for a more precise sense of meaning, underscore in imports doesn’t mean the same thing as say in patterns, because in patterns it means “I would put an identifier here to give this thing a name but I won’t use the name anyway so I’ll pass,” while in imports it means “I would put an identifier here if I wanted that specific thing but I want everything.” There are a number of other cases in the first category (see https://stackoverflow.com/a/8001132/333643), but they all convey the same thing. Of course, some of them have different semantics, but they all follow from the context. For instance if you have an expression _ + _ and you read it as “<some thing which I won’t bother naming> + <some other thing which I won’t bother naming>” then it only makes sense as a function with those being its parameters. After all, a function is just an expression with holes punched in it; it’s the usual syntax that obfuscates that, but sometimes it’s necessary (e.g., x => x + x).
Put differently, underscore is always exactly what it looks like, a blank for the the compiler to fill in, and the behavior is usually the only one that makes sense in the context.
From a more technical perspective I can appreciate why people see it as overloading, but unfortunately I think that characterization has negatively affected how Scala is taught.
Anyway, it would be great if there were more places that names were made optional. This has been discussed before. I think it would be useful in a number of places. One example is, sometimes due to designs based on overriding you end up with unused parameters. It would make sense to use _ instead of a name there.
[1] as opposed to when part of an identifier or a number
implied OptionMonoid[T] given Monoid[T] for Monoid[Option] = ...
And let’s throw in alternative keywords at the beginning and see which scan well. If your first language is not English, then I’m particularly interested, since I’m very heavily english-biassed. Firstly, given goes to the end:
implied OptionMonoid[T] for Monoid[Option] given Monoid[T]] = ...
Then the word implied doesn’t grammatically sit nicely at the beginning. You could say A implied B to link two things, but implied A, B is out of order. You need either a word that introduces a name/particular, or a verb to describe manufacturing one:
// nouns
the OptionMonoid[T] for Monoid[Option] given Monoid[T]]= ...
instance OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
//verbs
derive OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
summon OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
define OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
assume OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
give OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
offer OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
provide OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
synthesise OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
Personally I would lean towards the and give for the symmetry, and derive for capturing what we are actually doing. There are a bunch of these, like synthesise that i think are really poor choices, but I put them in anyway just to list options.