Skip to content

Trial: Use with instead of given for context parameters #7973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jan 12, 2020

This is the third experiment to change the current syntax that uses given for both instances and parameters. Instead of fiddling with the instance keyword (witness or default) we change the parameter syntax by going back to with, which we had before given.

Using given for parameters has several potential problems:

  • Many commenters felt that this was an over-use of given with too many similarities to the current status where implicit is used for everything.
  • Parameters in given instances were particularly burdensome. This was fixed in Better syntax for conditional given instances #7788, but the fix introduces problems on its own. First, it introduces another irregularity. Second, commenters felt the syntax was confusing because it looked too much like function types.

To start with browsing:

https://github.com/dotty-staging/dotty/blob/try-with/docs/docs/reference/contextual/motivation.md

@odersky
Copy link
Contributor Author

odersky commented Jan 12, 2020

Changes in a nutshell:

  • Definition site context parameters are expressed with (x: T) or with T.
  • Use site context arguments are expressed .with(e).
  • Context function types (aka implicit function types) are written S? => T.
  • Given instances go back to the given x as T syntax instead of given x: T before. This
    avoids the subtly different roles "x: " labels play for givens as compared to everywhere else. It also
    avoids another problem in that a standalone given instance given x as T for a concrete class T
    can no longer be confused with an abstract type.
  • To avoid confusion, with is dropped from collective extensions where it was mandatory before.

One area where I feel with T works much better than (given T) are convoluted context parameter lists. If you have many context parameters it is awkward to format them with (given ...) parameters, same as with (implicit...). As a consequence, may people will use extra long lines since they feel it's too difficult to break them into multi-line definitions. For instance, this beauty from #7859.

given [Left, Right](given lubLeft: Lub[Right], lubRight: Lub[Right])(given lub2: Lub2[lubLeft.Output, lubRight.Output]): Lub[Left | Right] ...

I believe this can be much more legible if we change to with:

given [Left, Right]
  with (lubLeft: Lub[Right], lubRight: Lub[Right])
  with (lub2: Lub2[lubLeft.Output, lubRight.Output])
  as Lub[Left | Right] ...

with does have slightly weirder application syntax than given. But since these are implicit parameters a definition syntax that flows well is much more important than the application syntax, IMO.

Another downside is that normal parameters following with clauses look weird, even if there's a space after the with clause, which is recommended in the docs in all cases. E.g.:

def f(x: T) with (y: U) (z: V) = ...

We might simply disallow it, except that this definition could be the result of expanding a collective extension method:

extension of (x: T) with (y: U) {
  def f(z: V) = ....
}

Still, these are in my opinion minor issues. The main advantage of the scheme is that it works well where it matters most: Defining context parameters. Everything else is second or third priority.

@soronpo
Copy link
Contributor

soronpo commented Jan 12, 2020

I like that we get less of given everywhere. I'm unsure about with. Although used for annotations, but I would like to propose to use @, which actually means "at condition".

@bmeesters
Copy link

bmeesters commented Jan 13, 2020

I quite like it. Pulling the with (or given or whatever) outside the parenthesis reads much better IMO. Though I think this is true regardless of the keyword(s) chosen. Using with might be a bit confusing initially, but I think after a while it is easy to get used to. Also a big 👍 for using two different keywords for providing and expecting.

@odersky
Copy link
Contributor Author

odersky commented Jan 13, 2020

Note that this looks a bit like C# constraints, only with with instead of where. This is intentional. A good way to think about many classes of context parameters is as constraints over the type parameters of a method.

@morgen-peschke
Copy link

Note that this looks a bit like C# constraints, only with with instead of where.

Has where already been considered and rejected? This seems to read quite nicely:

def foo(a: Bar)(where b: Baz): Foo = ???

foo(a)
foo(a)(where b)

@odersky
Copy link
Contributor Author

odersky commented Jan 14, 2020

In the group meeting yesterday there were concerns about with regarding visual binding precedence.
In

      def max(x: T, y: T) with Ord[T] : T = ...

one is tempted to read : like a symbolic infix operator with higher precedence than with. I recognize the problem, so I tried to come up three other alternatives, given below. For each alternative, we study a horizontal layout and a vertical layout, for the cases where there are many context parameters.

// infix with

      def max(x: T, y: T) with Ord[T] : T = 
        if x < y then y else x

      def max(x: T, y: T)
          with Ordering[T],
               Numeric[T])
          : T =
        if x < y then y else x

// tag in front of (...). instead of `where`, one can also use `implicit` or `given`, 
// the layout appearance is similar in each case.

      def max(x: T, y: T)(where Ord[T]): T = 
        if x < y then y else x

      def max(x: T, y: T)(
          where
            ord: Ordering[T], 
            n: Numeric[T]
        ): T =
        if x < y then y else x

// Context parameters in `{...}`

      def max(x: T, y: T){ Ord[T] }: T = 
        if x < y then y else x

      def max(x: T, y: T) {
          ord: Ordering[T], 
          n: Numeric[T]
        }: T =
        if x < y then y else x

// Have implicit parameters and normal parameters in one list, separated by `where` (or whatever).

      def max(x: T, y: T where Ord[T]): T = 
        if x < y then y else x

      def max(x: T, y: T
          where
            ord: Ordering[T], 
            n: Numeric[T]
        ): T =
        if x < y then y else x

Comparing all of these, I believe with still has the edge. I also believe one can get used to the fact that : has lower precedence than with when reading code, in particular since this is already the case when with is placed on the other side of :. I.e.

  e : A with B

is parsed e : (A with B) and not (e: A) with B.

@liufengyun
Copy link
Contributor

liufengyun commented Jan 14, 2020

Previously given without parenthesis is a problem in the following example:

  def dyni given QuoteContext  :  PV[Int] => Expr[Int] = dyn[Int]

One parsing obstacle in the above is that given can also start implicit function types. With with, it parses much better if we put two spaces around ::

  def dyni with QuoteContext  :  PV[Int] => Expr[Int] = dyn[Int]

@liufengyun
Copy link
Contributor

Or, we may use return as an alternative:

  def dyni with QuoteContext return PV[Int] => Expr[Int] = dyn[Int]

  def max(x: T, y: T) with Ord[T] return T = ...

@kavedaa
Copy link

kavedaa commented Jan 15, 2020

What if as is allowed as a synonym for : everywhere?

def max(x: T, y: T) with Ord[T] as T = ...
def dyni with QuoteContext as PV[Int] => Expr[Int] = dyn[Int]
def foo(i: Int)
  with Bar
  with Zip
  as String = ...

It would have to be allowed in simple constructs as well:

val x as Int = 1
def f(i as Int) as Int = i * 2

But would preferrably only be used where : would be visually ambiguous or confusing.

(I don't think the particular word as is perfect here - it has some connotations of "casting" or "conversion". of type might be more precise. But as has the advantage of being short. :) )

@odersky
Copy link
Contributor Author

odersky commented Jan 16, 2020

I think in the end using : with a space in front works reasonably well. Having a separate alternative syntax has its own downsides. I'll go ahead with the idea and work out a full PR.

@He-Pin
Copy link
Contributor

He-Pin commented Jan 16, 2020

extension <extension name> on <type> {
  (<member definition>)*
}

Cool

For given instances:

    given ...

For context parameters

    ... with ...

For context functions

    A ?=> B

Change doc pages accordingly
@odersky
Copy link
Contributor Author

odersky commented Jan 18, 2020

Superseded by #8017

@odersky odersky closed this Jan 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants