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:
- It mandates braces if we want to use parameter vs member abstraction:
given WithParameters[Int](1) with {}
- 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
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:
- We wouldn’t have to rewrite all material produced so far
- 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.
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
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.
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]
.
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.
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.
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:
- They both break the rule that (keyword, operator) starts an indented block and
:
starts a template body. - They both mandate braces if an empty block is wanted
-
naked new
together with given alias can be seen as an alternative togiven 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.