Proposal to Add Implied Instances to the Language

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 }

… but I still don’t like the alias forms.

2 Likes

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.

1 Like

To throw another color at the bikeshed:

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:

the Context[t] given Evidence[t] = new { ... }
3 Likes

Call a spade a spade…

How about define...for...given?

define Context[T] = localCtx
define Context[T] given Evidence { def ctx = localCtx }
define x[T] for Context[T] = localCtx
define x[T] for Context[T] given Evidence { def ctx = localCtx }

For me that would be the original

implied..for...given

or my previous suggestion

assumed...for...given

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?

1 Like

I think original proposal that omits assignment operator in typeclass instance definition has some drawbacks.

  1. Implied instance definition looks almost the same as implied alias definition:
implied instanceDefinition for A[B] { ... }
implied aliasDefinition for A[B] = { ... }

Only one non-blank char of difference. Just like with method returning Unit and method with inferred types:

def methodReturningUnit { ... }
def methodWithInferredReturnType = { ... }

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.

  1. 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?

5 Likes

Just to comment more on the underscore thing:

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

1 Like

So let’s consider this:

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.

2 Likes

allow?

allow OptionMonoid[T] for Monoid[Option] given Monoid[T] = …

How about declare instead of derive?

declare OptionMonoid[T] for Monoid[Option] given Monoid[T] = ...
1 Like

Must admit to liking this one, it’s suitably declarative

4 Likes

implicit as a noun reads quite well if for goes before given, including anonymous definitions. And it obviously feels much more familiar.

implicit OptionMonoid[T] for Monoid[Option[T]] given Monoid[T] = ... 

implicit OptionMonoid[T] for Monoid[Option[T]] given (monoidT: Monoid[T]) = ... 

implicit [T] for Monoid[Option[T]] given Monoid[T] = ... 

implicit global for ExecutionContext = ...

implicit for ExecutionContext = ...
4 Likes

Let’s go full circle and change implied to given:

given OptionMonoid[T]: Monoid[Option[T]] given Monoid[T] = ... 

given OptionMonoid[T]: Monoid[Option[T]] given (monoidT: Monoid[T]) = ... 

given _[T]: Monoid[Option[T]] given Monoid[T] = ... 

given global: ExecutionContext = ...

given _: ExecutionContext = ...

:smiley:

In fact implied acts as a given anyway, you just have to remember when to use which. That feels unnecessary.

1 Like

Taking a step back…

The fact that we use implied, implicit or even given for these things points to an open question: If Scala’s implicit system is fundamentally term inference, what is a good name for the term that is inferred for a type? We say “canonical instance”, or “implicit instance”, or “implied instance”, but can’t we find something crisper? Something that is a noun by itself?

  • evidence works if we identify types with properties, but not everyone is comfortable with that.
  • instance is too generic by itself.

One word that would work is representative. Term inference constructs the representative of a given type. When I define IntOrd, that’s then a representative for the type Ord[Int].

The terminology around representative also seems to make sense. One can say, a type is represented (i.e. there is an an implicit value of that type), or that a representative is eligible in some context.

To explore this further, I have made yet another copy of the doc pages (overview, definitions) based on the new terminology. It uses repr as a keyword, since representative is too long.

Conclusions that I have taken so far from this thread:

  • The basic structure of having one syntax construct to define representatives (or whatever) works well.
  • But given should come last
  • And given in expressions should be used with method call syntax instead of infix.

The main contenders for implicit definitions right now is to use either repr or implicit as a noun. One can form an opinion how everything hangs together by reading carefully the two variants of the doc pages. For implicit as a noun, these are (overview, definitions).

2 Likes

How about:

introduce ListOrd as Ord[List[T]] given (ord: Ord[T])

I want a noun :wink:

I think repr usually stands for representation, not representative. For example see Python’s use of repr and even similar usage in Haskell.

Moreover, a representative is “an example of a class or group”; representatives have the same kind as the things they represent. So a representative of a set of elements would be one element of the set, and a representative of a type would be a value of that same type, not of another type. For example, the union-find data structure has a concept of a representative node, which is used to represent all nodes whose element values belong to a certain subset.

Therefore I think the “representative” terminology is inadequate, in addition to being too verbose and its shorthand form repr too ambiguous.

Personally I strongly support the use of implicit, but without the strange novel syntax. While using for or of may be marginally more aesthetically pleasing when defining implicit instances, it also makes the connection to what they desugar to unnecessarily obfuscated.

Granted, one should focus on the intent and not the mechanism, but surely the following syntax expresses intent just as well, while also being clear about what it does behind the scenes:

implicit IntOrd extends Ord[Int] {
  def compare(x: Int, y: Int) = ...
}
implicit ListOrd[T].given(ord: Ord[T]) extends Ord[List[T]] {
  def compare(xs: List[T], ys: List[T]): Int = ...
}

// stands for:

implicit object IntOrd extends Ord[Int] {
  def compare(x: Int, y: Int) = ...
}
class ListOrd[T].given(ord: Ord[T]) extends Ord[List[T]] {
  def compare(xs: List[T], ys: List[T]): Int = ...
}
final implicit def ListOrd[T].given(ord: Ord[T]): ListOrd[T] =
  new ListOrd[T]

Anonymous instances:

implicit _ extends Ord[Int] {
  def compare(x: Int, y: Int) = ...
}

Abstract instances:

implicit IntOrd: Ord[Int]
2 Likes

I quite like representative. Using it in sentences feels like natural, semantically correct English.

The representative Ordering for Ord[Int] is IntOrd.

2 Likes