Something still not right with givens

Here is a request for removal of abstract given https://github.com/lampepfl/dotty/issues/10954 . I think it would be better addressed before RC1 and the whole planet starts using it. This associated feature request can wait a little bit more https://github.com/lampepfl/dotty-feature-requests/issues/156

Re-reading the “Given without as” PR, I think I understand better why abstract given is needed:

Now, one intriguing step further is whether we want to allow the same convention for normal defs. I.e

def foo(x: Int): T with {...}

as a slightly more powerful alternative to

def foo(x: Int) = T with {...}

or, using new

def foo(x: Int) = new T {...}

with the same expansion as for the given. This would give us parameterized objects, or functors in the SML sense,
without having to define a class. It also gives a nice way to avoid duplication between return type and RHS while stile having an explicitly defined return type

In that view, removing abstract given would also mean annihilate normal abstract definitions.

What we have here is a type ascription that can produce a value. It is shockingly irregular. While interesting, it is research material. Fortunately, it is not considered for 3.0, and maybe we could take a step back and disable abstract given too.

It turns out disallowing abstract given is not what we want, so https://github.com/lampepfl/dotty/issues/10954 is closed. I have updated my proposal in https://github.com/lampepfl/dotty-feature-requests/issues/156

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given C // instance; no confusion with abstract since abstract cannot be anonymous
given c extends C // named instance

I still think it would be good to have this in RC1. It’s just a syntax change - substitute extends for : in the given instance case, and eliminate with. So we might as well target 3.1 if there are more urgent issues.

My main concern is that currently the design of given is at odds with braceless syntax and gets in our way in that respect:

  1. It mandates braces if we want to use parameter vs member abstraction:
given WithParameters[Int](1) with {}
  1. It breaks the rule that (keyword, operator) starts an indented block and : starts a template body

Edit: what about new instead of extends ? Instance could be seen as an abridged version of alias:

given c: C = new C
given c new C

This time we could use it in anonymous, solving a lot of ambiguities:

given new C
given [T : Ord] new Foo[T]

Even better, we could use both - new for anonymous and extends for named instance:

given new C
given c extends C

Probably I’m the only person missing

given _: C = C()

so

given _ extends C
1 Like

That’s how I see it. Given syntax seems to be largely about making names optional, and Scala 2 already has a mechanism for omitting names. The most straightforward thing would be to just use it.

I think that with given we want to insist on the type, rather than the value, hence we want to make the latter as invisible as possible. And you would want to do that also for using.

using _: C

And yes, there’s the creator application question too.

Maybe

given new C

could be rewritten to

given C()

, which in the named case would read

given c new C
given c C()

A bit weird perhaps.

Thinking about it, what is “researchy” about the current design is related to member abstraction. As I understand it, that’s how it’s encoded in the underlying calculus (DOT).

given WithMembers with {
  type A = Int
  val foo = 1
}

Removing braces does not help that much.

given WithMembers with
  type A = Int
  val foo = 1

On the contrary, parameter abstraction is naturally braceless.

given new WithParameters[Int](1)

What about for ? It works in both named and anonymous.

given c for C
given for C

Edit: overall picture:

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given for C // instance
given c for C // named instance

Edit2: what about bringing back as just in the instance case?

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given as C // instance
given c as C // named instance

Advantages:

  1. We wouldn’t have to rewrite all material produced so far
  2. It is compatible with the main argument in the “given without as” discussion that we don’t want given value as type, as in the instance case it is not type but constructor

Edit3: why not with ?

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given with C // instance
given c with C // named instance

Just shift the current keyword.

1 Like

Here is my latest attempt to get rid of with in given, in order to smooth the discussion in the “other thread”. I think everybody will agree that extends makes most sense here:

given c extends C

The problem is with anonymous:

given extends C

Revolting might be the word (less so if we take given as a substantive but let’s ignore this for the moment). What if we take instance (is that a soft keyword ?) to fill the blank in the anonymous case:

given instance extends C
given instance [T : Ord] extends C[T]

A little strangeness to solve the bigger with strangeness…
Edit : or indeed underscore, just in the instance case:

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given c extends C // instance
given _ extends C // instance, anonymous

Edit2 : or maybe take instance also in the named case:

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given instance extends C
given instance c extends C // named

Credits : Feedback sought: Optional Braces

2 Likes

What about of ? There would be no ambiguity it is a substantive:

given of C
given c of C
given [T : Ord] of C[T]

Is of a keyword yet ? I think it is seldom used as a name anyway.
Edit : or from ?

given from C
given c from C
given [T : Ord] from C[T]

Problem : from is often used as a name.

Radical move : take instance instead of given (as I proposed long ago):

instance of C
instance c of C
instance [T : Ord] of C[T]

Pros:

  • it is a noun
  • it acknowledges Haskell’s anteriority

Cons:

  • it might be used in existing code

Regarding this last point, in Java it is often used for singletons:

public class MyClass {
  public static final MyClass instance = new MyClass();
}

In Scala, this pattern is replaced by object, so using instance as a name could be considered bad practice.

I was initially against instance because the term is too general (not just for type classes), and there is potential for confusion with isInstanceOf and asInstanceOf. But I admit your examples look nice.

instance is a pretty common variable name, especially in java libraries and “scala as a better java” code bases. I think it expresses the meaning very well, but the overlap with old code could cause nuanced dissonance.

extends, with , and : , (plus _ for anonymity) read very comfortably for me - all three are synonyms in my head. However, I think I’m late to this party.

1 Like

On the contrary, I think it makes it quite uniform and regular:

given instance c of C
c.isInstanceOf[C] // true

@dwalend We could swap the keywords:

instance given c of C

, making instance a soft modifier and avoiding clashes with existing code.

How do you explain that this works, then?

object Foo
Foo.isInstanceOf[AnyRef] // true

There were no instance declarations, yet the test returns true. Indeed, the name of isInstanceOf would now send the wrong signal about its meaning. As another example, some people will probably end up wanting to write a test that a type Foo has a type class instance for C as Foo.isInstanceOf[C].

1 Like

Well, instanceOf[C] instead of summon[C] does not sound a bad idea.

Looking at https://dotty.epfl.ch/docs/reference/soft-modifier.html , it seems instance after given is possible as a soft keyword. It looks very appealing.

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given instance of C
given instance c of C // named
given instance [T : Ord] of C[T] // with params

Edit : better syntax yet:

given instance C // remove `of` in anonymous
given instance c extends C // replace `of` by `extends`
given instance [T : Ord] extends C[T] // with params

Edit2 : maybe even better, remove instance in named case, to better match abstract in case of implement:

given c: C // abstract
given c: C = new C // alias
given C = new C // anonymous
given instance C // instance
given c extends C // instance, named ; abstract implement
given instance [T : Ord] extends C[T] // with params

The syntax is different if C is a type or a constructor. I think it might be better on a educational point of view.

What I meant is, it should be obvious to anyone that it is about a typeclass instance.
Edit : I would not see the point of looking at runtime if something is in the implicit scope, as it is a compile-time notion.

So, the use of instance as a (soft) keyword in given is absolutely not ambiguous, in my opinion. I am sorry to have to constantly revive this thread, but I think the issue is still not addressed. What is the problem with my instance-based solution above, compared to given with ? If none, what prevents us to implement it ? Thanks.

Personally, I think there’s more room for confusion and no actual gain. I dislike “summon” but it’s very peculiarity will make it easy for people to remember it after learning about it. Conversely, instanceOf looks primed to get newcomers to the language confused, particularly Java programmers. The fact that a familiar term is used can make them believe they know what it is doing, whereas the unusual summon will prompt them to look it up.

2 Likes

Sorry, as I was not actually proposing to substitute instanceOf for summon. This was in response to @LPTK 's concern that instance as a keyword is misleading. In my opinion, colon + constructor (that is, something else than type) will be much more misleading to newcomers:

given c: C with {...}

What is C with {...} ? a type ? Versus:

given c extends C {...}

Now it is regular and familiar. But, as in the anonymous case we can’t write:

given C {...}

, because in braceless syntax that would be:

given C:
  ...

, meaning too close to the alias syntax:

given c: C

, then we can (must) add a keyword:

given instance C {...}

Compare with the with syntax:

given C with {...}

Quite a simple change, really.

4 Likes

It turns out what I called “constructor inference” above already works:

class A
val a: A = new {}

It is mentioned here under the name “naked new”. I think it is not put forward because we want to get rid of new with creator applications. However, it has traits in common with the given with syntax:

  1. They both break the rule that (keyword, operator) starts an indented block and : starts a template body.
  2. They both mandate braces if an empty block is wanted
  3. naked new together with given alias can be seen as an alternative to given with:
given C = new {...}
given C with {...}

So, my main prevention against the given with syntax as a disgracious exception vanishes, as it is consistent with at least one other construct in the language. In consequence, I will stop trying to prevent it in RC1. I still think my alternative proposal is worth giving a try, but it could be done later under the regular SIP process.