Proposal to Add Implied Instances to the Language

On the left hand side of an assignment, _ means that the name is not useful:

  val (x, _) = pair

So I believe that my usage of _ is not new – it is consistent with previous Scala grammar.
My proposal makes all term-level definition in Scala follow the same syntax, making the language simple:

{val|var|def|implicit} name : Type
2 Likes

According to the dictionary, evidence of is wrong since it says that the thing after the “of” exists. I.e. evidence of Ord[Int] would effectively state “type Ord[Int] exists”. Well, we knew that!

Whereas evidence for means “in support of something”, so evidence for Ord[Int] would mean
there’s support for type Ord[Int]. It wouldn’t be too far fetched to say a value of type T is effectively the support for T.

I am preparing a third batch of files, this time with instance of. Then we can compare with that as well.

I’ve played a bit with Dotty 0.14.0-RC1 and pushed my experiment here: https://github.com/julienrf/dotty-endpoints. In case that helps to see concrete code and not just specifications…

I exercised several features related to implicit parameters and definitions. I can write an equivalent program in Scala, to make the comparison easier, if needed. However, my program is surely not representative of all the usages of implicits.

Overall, I’m happy with the improvements brought by Dotty. More specifically:

  • the ability to define anonymous implicit values,
  • the reduction of boilerplate associated with implicit definitions,
  • the fact that extension methods are implicitly applicable,

The only things I’m not happy with are mostly syntactic: the for and implied keywords. But also, the fact that we can not define abstract implicit values is a problem for my use case.

As it was previously suggested in this discussion, I would be in favor of the following syntax:

// Named instance
implicit monoidInt extends Monoid[Int] {
  def identity = 0
  def (lhs: Int) combine (rhs: Int): Int = lhs + rhs
}

// Anonymous instance
implicit _ extends Monoid[Int] {
  // ...
}

// Derived instance
implicit _ [A] given (ma: Monoid[A]) extends Monoid[Option[A]] {
  // ...
}

// However, alias instances don’t work very well:
implicit _ extends Monoid[Int] = monoidInt
// Maybe we should replace “extends” with “of” everywhere?
implicit _ of Monoid[Int] = monoidInt

And I would appreciate support for abstract instances, with the following syntax:

// Abstract instance
trait Foo {
  type Bar
  // An abstract instance may not be anonymous
  implicit monoidBar extends Monoid[Bar]
}

trait FooImpl extends Foo {
  type Bar = Int
  implicit monoidBar = the[Monoid[Int]]
}
2 Likes

While I agree in general, this is not another user of _. The same use that mentions the symbol is anonymous.

6 Likes

I like your proposal – however I think that you can just use : for of and abstract cases:

// new named instance
implicit optionMonoid[A] given (s: Semigroup[A]) extends Monoid[A] {
  ...
}

// from somewhere else
implicit intMonoid: Monoid[Int] = ???

// abstract implicit
implicit monoid: Monoid[A]

2 Likes

I actually ended up just directly using context as a keyword in my proposal in the other thread. I also used derived for typeclasses, which is a keyword I’m less confident about, but overall just using method-style syntax on that side feels more natural.

I did a another trial run, this time with instance of instead of implied for. Here are the doc pages: instance definitions and the start page that links to all the other pages. instance of was originally proposed by @jdegoes, was implemented for a while, and then retired in favor of implied since we felt the name was too generic.

First reactions on the second try:

+ It reads well.

- instance is a very generic term, so I took pain to spell it out in the text everywhere as implicit instance. With a bit of discipline that should work. There’s precedence for that: Other definition keywords like def or type are also more generic than what they define precisely.

+ instance is immediately familiar for people coming from Haskell. It does not mean precisely the same thing in Scala (i.e. in Scala instance relates a type and a term, whereas in Haskell it relates a type class and a type), but that does not seem to be a problem.

- of probably has to be a soft keyword. That’s OK, even though it will mean we cannot define an instance with name of. That should be survivable, however.

Opinions?

1 Like

I still think that having both instance ... of and isInstanceOf in the language is too great a potential confusion since they are not actually directly related.

5 Likes

I think that the biggest advantage of instance of over implied for is that I can easily tell which is the new name and which is the typeclass. Also, implied just felt like a new keyword for the only purpose of not using implicit. It doesn’t really feel much more descriptive than instance to me.

Is there any use case for isInstanceOf which cannot be met by a type check in a pattern match?

If not, it might be a useful simplification to remove it from the language and migrate x.isInstanceOf[T] to:

x match {
   case _: T => true
   case _ => false
}

If there is a sort of a consensus that implied should be replaced with something better, maybe assumed is worth considering. Works with implied imports too, at least as well as current Dotty syntax.

assumed ListOrd[T] given (ord: Ord[T])

assumed IntOrd for Ord[Int]

assumed for Ord[Int] { ... }

Another one for the nouns that sound like legal terms, or Prolog

fact [A] given Show[A] of Show[List[A]] =
  _.map(_.show).mkString("[", ",", "]")

import fact A._
1 Like

There are a lot of positive aspects about choosing instance, in particular alignment with Haskell terminology. I just wonder whether the keywords instance and object are too much interchangeable semantically and thus confusing for newcomers?

Regarding the other choices I’d have a preference for the slightly more generic terms like implied, implicit or inferred over evidence or the original witness suggestion. While the explanation given for evidence makes absolute sense, I agree with some other posters that it is not necessarily intuitive without having those two paragraphs of explanation and I would suspect it might, like witness, bring a subtle vibe of obscurity for newcomers.

1 Like

Unless I’m mistaken, doesn’t Dotty still use the Scala 2 convention of declaration implicit vals, implicit val x = someValue? If so, I’m against using implicit to mimic val, and I also think using that word in implicit x for Foo[Int]is less ideal than using implied or some unique keyword. The less ambiguity introduced the better in my opinion.

1 Like

This. Instance sounds good in isolation, but instance pretty universally means the same as object in large swaths of the JVM ecosystem (e. g. Java), so it might be quite confusing for people.

Also, it would be nice if the syntax would tell more about this construct than just that it is an instance, and most other proposals already accomplish this.

I also agree on this, while the explanation for evidence makes sense, it would feel alien (alienating?) to a lot of people, again the average-ish workplaces I referenced elsewhere.

In Java world object means generally the same as instance of a class. Scala’s object is a singleton (i.e. sole instance) of an anonymous class. Therefore there’s already a confusion. instance keyword would add another ambiguity when confronted with standard OOP terminology. Rust uses impl which doesn’t directly clash with anything Java or Scala related, so it would be a good candidate (unless you’re using a weird and useless convention like class RedundantInterfaceImpl extends RedundantInterface instead of just class NoArtificialHierarchy).

1 Like

The arguments that instance and object are interchangeable sunk the proposal the last time. But I am no longer convinced that they are incontrovertible. When I write

instance IntOrd of Ord[Int]

I do define an instance (in the sense of object) of type Ord[Int]. So the wording is not wrong, it’s just too generic. The wording does not imply that the instance I define is the canoncial, or implicit one. That can be mitigated by writing systematically implicit instance in all accompanying text, so that the two words get associated more closely.

There’s precedent to have generic words mean something more specialized. For instance, object in Scala. It means not an object in general, but a lazily instantiated singleton value of an anonymous class. Should we have used singleton instead? Maybe. But somehow, object worked well and is easier to remember. Or, take impl in Rust. It has a role very much like instance, but the word really means an arbitrary implementation.

I agree that the overlap with isInstanceOf/asInstanceOf is unfortunate. But we could also investigate whether we should come up with alternatives for isInstanceOf/asInstanceOf.

2 Likes

Perhaps…
a.asInstanceOf[B] to (a :! B) as unique syntax or a:![B] as a def
a.isInstanceOf[B] to (a :? B) as unique syntax or a:?[B] as a def

One thing that I would like to correct is remove the symmetry between isInstanceOf and asInstanceOf. The two behave not the same, in the sense that there are situations where
e.isInstanceOf[T] gives false but e.asInstanceOf[T] succeeds. This could happen in a number of situations: if e is null, or if T is a union or intersection type, or is prefixed such as in T = p.C.

x.isInstanceOf[T] means essentially: Apply the same algorithm as in pattern matching to determine whether x is a T. It’s not perfect because of erasure. But what can be reasonably checked will be.

x.asInstanceOf[T] means essentially: Trust me, I know what I am doing. As long as the JVM allows to cast x to the erasure of type T the operation will succeed.

So the two operations are quite different, and intentionally so. Which means that the symmetry between their names raises false expectations. In my experience, many people are confused by this.

3 Likes

IIRC long names like isInstanceOf and asInstanceOf were chosen to discourage using them. Changing them to short operators will increase their popularity which probably is not what we want.

isInstanceOf and asInstanceOf are as low level as they can be, so they are used for performance purposes. Pattern matching sometimes adds some overhead, but in typical case it should not be a concern of a programmer.

Maybe put those operations behind a language flag? I think they are used very rarely in business logic. Libraries tend to have some isInstanceOf and asInstanceOf, but they also tend to have uncheckedVariance and other hacks.