Alexander Kuklev | 13 Aug 2012 09:04
Picon

Towards Scala 3

I'd like to share some ideas on features that might be included into next major Scala releases. Some of them might come in question already for Scala 2.11, some are probably more appropriate for Scala 3.

1. Type providers (and macro annotations)
Type providers (also known as macro types) are the most practice relevant feature because they can be used to generate safe types for database records, xml documents, FFI and other foreign entities, based on their schemata. Their other usage is to generate Scala-level types for DSLs with their own internal type system. In practice, a macro type is a type dependent on one or several values and/or type parameters in such a way that it's definition can be produced in compile-time (i.e. without knowing the runtime content of parameters).

type SqlRecord(s: SqlSchema) = macro sqlRecordImpl def sqlRecordImpl(c: Context)(s: c.Expr[SqlSchema]): c.TypeTag = ...
Type providers make it possible to implement current compiler plugins “lenses” and “zippers” as a part of standard library, without hardcoding them into compiler. Moreover, with help of type providers and macro annotations, we can implement side-effect typing including effect polymorphism with virtually no changes to the compiler, see this post!

Even more fascinating is that we even can create a type provider ✓(p: Boolean) which creates a type of evidences (“proofs”) that the predicate holds. Keep reading for a use case.

All code below is to be read as if this type providers including ✓ were already there.

2. Unification of generics and abstract type members
This is a thing proposed by Martin in SIP 18 and this discussion. Parameter-polymorphic types should be syntactic sugar over abstract type members:trait F[T] { ... } // Desugars to trait F { <at> Param(0) protected type T ... }
All code below is to be read as if this unification was already there.

3. Harmonization of trait and argument list signatures
A function of multiple arguments “def f(a: Int, b: Int): Int” is function from tuple “(Int, Int) => Int”.
Well, almost. The thing is, there are named and default arguments and parametric polymorphism, so actually it's a function from a trait:def f[T](t: T, a: Int, b: Int = 0): Int trait ArgTypeOfF[T] { <at> Arg(1) val t: T <at> Arg(2) val a: Int val b: Int = 0 } // Now holds: f: ArgTypeOfF => IntThere are things you can do only for traits:trait A {type T} trait B {val x: A; val y: x.T} // OK def f(x: A, t: x.T) // Not OK!
All code below is to be read as if we could use dependent types in function signatures to the same extent as in traits.

In Scala ≤2.10 types of the methods are non representable, you can try it out in REPL:scala> def f(x: Int): Int = 0 f: (x: Int)Int
With unification of traits and argument lists, method types become representable:scala> def f(x: Int): Int = 0 f: { <at> Arg(1) val x: Int} => Int
To be able to represent dependent method types, we need a little hack: “=>” should be implemented as an argumentless typeprovider “type =>[Source, Target] = macro functionImpl” allowing the right-hand argument to depend on left-hand argument's fields:{val x: A} => x.B // Desugars to {def apply(x: A): x.B}(“x.B” is meant as an example for any stable identifier dependent on agruments defined on the left side.)

4. Implicit arguments reform
Implicit arguments were introduced in times there were no named and default arguments. But think for a minute! An implicit argument is just an argument with a default value, namely “implicitly[Type]” which has to be evaluated on call site (not the definition site) of the function. So the more consistent syntax for implicits would be the following:def sort[T](xs: List[T], ord: Ordering[T] = implicit)
In connection with § 3 it also solves the problem of type interdependency:
def f[T](c: Context, tag: c.Tag[T] = implicit, builder: tag.Builder)
The function above cannot be defined in Scala ≤2.10 in any elegant way.

Now recall trait and function argument list signatures should stay harmonized. So we should allow the constructions liketrait ArgTypeOfSort[T] { <at> Arg(1) val xs: List[T] val ord: Ordering[T] = implicit }...where implicit means, the value should be by default acquired is “implicitly[Ordering[T]]” on the site of instantiation. This possibility will be essential for the next paragraph.

All code below is to be read as if the new implicit argument syntax was already there.

5. Context bounds for abstract type members
Now, as implicits are allowed everywhere, nothing stays in way of the proposal discussed here.

trait F[T: C] { ... } // Desugars to trait F { <at> Param(0) protected type T: C ... } // Desugars to trait F[T] { <at> Param(0) protected type T: C val T: C[T] = implicit ... }
In order for this notation to be overridable it is essential to allow only one context bound per type, so the cases like “[T: Ord : TypeTag]” should be deprecated. It's always possible to use manual implicit arguments/fields for one or more witnesses instead. I'd also insist on naming the witness same as the associated abstract type member because it plays the role similar to companion objects for concrete types. Here's a nice use case:def max[T: Ord](a: T, b: T): T = if (T.gt(a, b) > 0) a else b
6. Aliases for context bounds
As you probably know, Scala both has a trait Ordered and a typeclass Ordering[_]. The typeclass is the right way to impose an order, the trait that demands comparison operator inside objects is a wrong way that will be deprecated someday. However, it's easier to write sort(xs: List[Ordered]) than sort[T: Ordering](xs: List[T]). For many people, second notation is a burden. We can take best of both worlds if we allow Ordered to be alias for context bound, for example with the following natural syntax:type Ordered = T forSome {type T; val o: Ordering[T] = implicit} // Type provider for convinience: type Ordered = HasTypeclass[Ordering]
Such alias would work so that sort(xs: List[Ordered]) gets desugared to sort[T: Ordering](xs: List[T]). 

7. Infix operators
Scala gets more and more typeclass based, so comparison, addition and other infix operators travel from classes into their typeclasses. To compare two objects you now must write ordering.compare(a, b) instead of a.add(b) which is a highly positive development but a source of certain inconvenience. For infix notation “a < b” to work in this cases, library developers of Scala ≤2.10 use workarounds like implicit conversions of the first argument into a helper type. Such crutches make libraries intransparent and can cause problems with implicit conversion conflicts. We should provide a straight way to do it!

I would propose the following one: whenever you encounter unresolved expression of the kind “a OP b”, convert it into “(associate(a) merge associate(b))._OP(a, b)” where the associate(x) is the companion object of the type of x if it's concrete, or the associated typeclass instance, if the type of x is an context bound abstract type. “merge” is defined by default as a macro operation that combines all public methods of left and right objects into one, if there are conflicts, a compile time error is produced. (Of course, _merge can be overridden for particular object typeclasses, it's a usual infix operator.)

Usecase:trait Additive[T] { def _+(a: T, b: T): T ... } def foo[A: Additive](a: A, b: A) = a + b
8. Implicits providers
These are functions to be called in compile time (akin to macros) each time an implicit is resolved. One usage is described in this discussion, where we propose a solution for separating optimization details from the logic by using implicits.

The other major use case are fact checkers (AKA theorem provers) that seek for evidences for some facts of interest on demand. Some operations may be defined only in the case a certain fact is known. For example, if you have an (unordered) set, you can deterministically extract its element iff it's size == 1.
def the[T](x: Collection[T], uniqueness: ✓(size(x) == 1) = implicit): T

Now you can write an implicits provider (theorem prover) that performs static code analysis and produces an evidence of the fact above if it has found that the set must have precisely one element. In the case the prover has failed to check the condition, you can either make a proof by hand (see below), or do this:

if (size(x) == 1) the(x) else throw new RuntimeException("Shouldn't happen")
Knowing additional facts can often speed up operation. Say, you want to reduce a list by a binary operation. It always takes not more than n executions of the operation, but if the operation is associative, on a machine with data parallelism, you can perform the reduction in O(log n) operation, which is an exponential speedup! Associativity also enables reduction on incomplete lists, you can just “sum up” everything known and than add up new elements as soon as they appear. To use this advantage, you can use optional evidence argument. If the fact checker will be able to confirm associativity, it'll provide an evidence, otherwise None:def reduce[T](op: (T, T) => T, associativity: Option[ {val a: T, val b: T, val c: T} => ✓(op(op(a, b), c) == op(a, op(b, c)) ]): T
What can you do with such upgraded Scala?
I won't be talking about how great type providers are in the respect of interoperability with foreign world: SQL, XML, etc., for it's rather obvious, it's a breakthrough. It's also rather straightforward that with type providers and macros you can achieve lots of things directly in libraries instead of writing compiler plugins. With fact checkers and constraint-based optimization we'll also be able to gain some considerable speed-up.

The big deal for me is that upgraded Scala gets mathematics right:
– We can correctly encode theories: structures like group, ring, algebra, topological space, category, etc.
– We can amend the standard library in such a (backward compatible) way that the hierarchies of number types and collection types would respect their mathematical nature; Int would be an instance of a ring, Option[T] an endofunctor and so on.
– We'll be able reason about types and programs, see this post. In fact, Scala will gain another dimension, it'll be not only a programming language, but also a proof assistant with unprecedented flexibility of notation.

The other big deal is that we introduce side-effect typing and also the ideas proposed in Scala Virtualized can be extended much further! We'll be able to build very complicated DSLs in a typesafe way. With certain amount of effort it should be possible to represent a complicated dependently typed language such as Agda as a Scala DSL.

√iktor Ҡlang | 13 Aug 2012 11:27
Picon

Re: Towards Scala 3

Unifying tuples and Parameter lists?

Removing view bounds?

On Mon, Aug 13, 2012 at 9:04 AM, Alexander Kuklev <akuklev <at> gmail.com> wrote:

I'd like to share some ideas on features that might be included into next major Scala releases. Some of them might come in question already for Scala 2.11, some are probably more appropriate for Scala 3.

1. Type providers (and macro annotations)
Type providers (also known as macro types) are the most practice relevant feature because they can be used to generate safe types for database records, xml documents, FFI and other foreign entities, based on their schemata. Their other usage is to generate Scala-level types for DSLs with their own internal type system. In practice, a macro type is a type dependent on one or several values and/or type parameters in such a way that it's definition can be produced in compile-time (i.e. without knowing the runtime content of parameters).

type SqlRecord(s: SqlSchema) = macro sqlRecordImpl def sqlRecordImpl(c: Context)(s: c.Expr[SqlSchema]): c.TypeTag = ...
Type providers make it possible to implement current compiler plugins “lenses” and “zippers” as a part of standard library, without hardcoding them into compiler. Moreover, with help of type providers and macro annotations, we can implement side-effect typing including effect polymorphism with virtually no changes to the compiler, see this post!

Even more fascinating is that we even can create a type provider ✓(p: Boolean) which creates a type of evidences (“proofs”) that the predicate holds. Keep reading for a use case.

All code below is to be read as if this type providers including ✓ were already there.

2. Unification of generics and abstract type members
This is a thing proposed by Martin in SIP 18 and this discussion. Parameter-polymorphic types should be syntactic sugar over abstract type members: trait F[T] { ... } // Desugars to trait F { <at> Param(0) protected type T ... }
All code below is to be read as if this unification was already there.

3. Harmonization of trait and argument list signatures
A function of multiple arguments “def f(a: Int, b: Int): Int” is function from tuple “(Int, Int) => Int”.
Well, almost. The thing is, there are named and default arguments and parametric polymorphism, so actually it's a function from a trait:def f[T](t: T, a: Int, b: Int = 0): Int trait ArgTypeOfF[T] { <at> Arg(1) val t: T <at> Arg(2) val a: Int val b: Int = 0 } // Now holds: f: ArgTypeOfF => IntThere are things you can do only for traits:trait A {type T} trait B {val x: A; val y: x.T} // OK def f(x: A, t: x.T) // Not OK!
All code below is to be read as if we could use dependent types in function signatures to the same extent as in traits.

In Scala ≤2.10 types of the methods are non representable, you can try it out in REPL: scala> def f(x: Int): Int = 0 f: (x: Int)Int
With unification of traits and argument lists, method types become representable:scala> def f(x: Int): Int = 0 f: { <at> Arg(1) val x: Int} => Int
To be able to represent dependent method types, we need a little hack: “=>” should be implemented as an argumentless typeprovider “type =>[Source, Target] = macro functionImpl” allowing the right-hand argument to depend on left-hand argument's fields: {val x: A} => x.B // Desugars to {def apply(x: A): x.B}(“x.B” is meant as an example for any stable identifier dependent on agruments defined on the left side.)

4. Implicit arguments reform
Implicit arguments were introduced in times there were no named and default arguments. But think for a minute! An implicit argument is just an argument with a default value, namely “implicitly[Type]” which has to be evaluated on call site (not the definition site) of the function. So the more consistent syntax for implicits would be the following: def sort[T](xs: List[T], ord: Ordering[T] = implicit)
In connection with § 3 it also solves the problem of type interdependency:
def f[T](c: Context, tag: c.Tag[T] = implicit, builder: tag.Builder)
The function above cannot be defined in Scala ≤2.10 in any elegant way.

Now recall trait and function argument list signatures should stay harmonized. So we should allow the constructions liketrait ArgTypeOfSort[T] { <at> Arg(1) val xs: List[T] val ord: Ordering[T] = implicit }...where implicit means, the value should be by default acquired is “implicitly[Ordering[T]]” on the site of instantiation. This possibility will be essential for the next paragraph.

All code below is to be read as if the new implicit argument syntax was already there.

5. Context bounds for abstract type members
Now, as implicits are allowed everywhere, nothing stays in way of the proposal discussed here.

trait F[T: C] { ... } // Desugars to trait F { <at> Param(0) protected type T: C ... } // Desugars to trait F[T] { <at> Param(0) protected type T: C val T: C[T] = implicit ... }
In order for this notation to be overridable it is essential to allow only one context bound per type, so the cases like “[T: Ord : TypeTag]” should be deprecated. It's always possible to use manual implicit arguments/fields for one or more witnesses instead. I'd also insist on naming the witness same as the associated abstract type member because it plays the role similar to companion objects for concrete types. Here's a nice use case: def max[T: Ord](a: T, b: T): T = if (T.gt(a, b) > 0) a else b
6. Aliases for context bounds
As you probably know, Scala both has a trait Ordered and a typeclass Ordering[_]. The typeclass is the right way to impose an order, the trait that demands comparison operator inside objects is a wrong way that will be deprecated someday. However, it's easier to write sort(xs: List[Ordered]) than sort[T: Ordering](xs: List[T]). For many people, second notation is a burden. We can take best of both worlds if we allow Ordered to be alias for context bound, for example with the following natural syntax: type Ordered = T forSome {type T; val o: Ordering[T] = implicit} // Type provider for convinience: type Ordered = HasTypeclass[Ordering]
Such alias would work so that sort(xs: List[Ordered]) gets desugared to sort[T: Ordering](xs: List[T]). 

7. Infix operators
Scala gets more and more typeclass based, so comparison, addition and other infix operators travel from classes into their typeclasses. To compare two objects you now must write ordering.compare(a, b) instead of a.add(b) which is a highly positive development but a source of certain inconvenience. For infix notation “a < b” to work in this cases, library developers of Scala ≤2.10 use workarounds like implicit conversions of the first argument into a helper type. Such crutches make libraries intransparent and can cause problems with implicit conversion conflicts. We should provide a straight way to do it!

I would propose the following one: whenever you encounter unresolved expression of the kind “a OP b”, convert it into “(associate(a) merge associate(b))._OP(a, b)” where the associate(x) is the companion object of the type of x if it's concrete, or the associated typeclass instance, if the type of x is an context bound abstract type. “merge” is defined by default as a macro operation that combines all public methods of left and right objects into one, if there are conflicts, a compile time error is produced. (Of course, _merge can be overridden for particular object typeclasses, it's a usual infix operator.)

Usecase:trait Additive[T] { def _+(a: T, b: T): T ... } def foo[A: Additive](a: A, b: A) = a + b
8. Implicits providers
These are functions to be called in compile time (akin to macros) each time an implicit is resolved. One usage is described in this discussion, where we propose a solution for separating optimization details from the logic by using implicits.

The other major use case are fact checkers (AKA theorem provers) that seek for evidences for some facts of interest on demand. Some operations may be defined only in the case a certain fact is known. For example, if you have an (unordered) set, you can deterministically extract its element iff it's size == 1.
def the[T](x: Collection[T], uniqueness: ✓(size(x) == 1) = implicit): T

Now you can write an implicits provider (theorem prover) that performs static code analysis and produces an evidence of the fact above if it has found that the set must have precisely one element. In the case the prover has failed to check the condition, you can either make a proof by hand (see below), or do this:

if (size(x) == 1) the(x) else throw new RuntimeException("Shouldn't happen")
Knowing additional facts can often speed up operation. Say, you want to reduce a list by a binary operation. It always takes not more than n executions of the operation, but if the operation is associative, on a machine with data parallelism, you can perform the reduction in O(log n) operation, which is an exponential speedup! Associativity also enables reduction on incomplete lists, you can just “sum up” everything known and than add up new elements as soon as they appear. To use this advantage, you can use optional evidence argument. If the fact checker will be able to confirm associativity, it'll provide an evidence, otherwise None: def reduce[T](op: (T, T) => T, associativity: Option[ {val a: T, val b: T, val c: T} => ✓(op(op(a, b), c) == op(a, op(b, c)) ]): T
What can you do with such upgraded Scala?
I won't be talking about how great type providers are in the respect of interoperability with foreign world: SQL, XML, etc., for it's rather obvious, it's a breakthrough. It's also rather straightforward that with type providers and macros you can achieve lots of things directly in libraries instead of writing compiler plugins. With fact checkers and constraint-based optimization we'll also be able to gain some considerable speed-up.

The big deal for me is that upgraded Scala gets mathematics right:
– We can correctly encode theories: structures like group, ring, algebra, topological space, category, etc.
– We can amend the standard library in such a (backward compatible) way that the hierarchies of number types and collection types would respect their mathematical nature; Int would be an instance of a ring, Option[T] an endofunctor and so on.
– We'll be able reason about types and programs, see this post. In fact, Scala will gain another dimension, it'll be not only a programming language, but also a proof assistant with unprecedented flexibility of notation.

The other big deal is that we introduce side-effect typing and also the ideas proposed in Scala Virtualized can be extended much further! We'll be able to build very complicated DSLs in a typesafe way. With certain amount of effort it should be possible to represent a complicated dependently typed language such as Agda as a Scala DSL.




--
Viktor Klang

Akka Tech Lead
Typesafe - The software stack for applications that scale

Twitter: <at> viktorklang

Alexander Kuklev | 13 Aug 2012 11:36
Picon

Re: Towards Scala 3


понедельник, 13 августа 2012 г., 11:27:17 UTC+2 пользователь √iktor Klang написал:
Unifying tuples and Parameter lists?
§ 3 is exactly about this topic. You should unify parameter lists not with tuples but with traits. Tuples contradict named and default arguments, implicits and generics. Traits fit perfectly, see in the text above. Of course, we should generate implicit conversions from tuples into argument list traits, i.e. where all not initialized fields have <at> Arg(n) annotations and there are no abstract member types that cannot be inferred.
 
Removing view bounds?
Aren't they a special case of context bounds essentially?
√iktor Ҡlang | 13 Aug 2012 11:57
Picon

Re: Towards Scala 3



On Mon, Aug 13, 2012 at 11:36 AM, Alexander Kuklev <akuklev <at> gmail.com> wrote:

понедельник, 13 августа 2012 г., 11:27:17 UTC+2 пользователь √iktor Klang написал:
Unifying tuples and Parameter lists?
§ 3 is exactly about this topic. You should unify parameter lists not with tuples but with traits. Tuples contradict named and default arguments, implicits and generics. Traits fit perfectly, see in the text above. Of course, we should generate implicit conversions from tuples into argument list traits, i.e. where all not initialized fields have <at> Arg(n) annotations and there are no abstract member types that cannot be inferred.

So it's order by annotation? What happens when you mix in multiple traits?
 
 
Removing view bounds?
Aren't they a special case of context bounds essentially?

Is the conversion applied multiple times inside the body whenever the A needs to be a B?

Cheers,


--
Viktor Klang

Akka Tech Lead
Typesafe - The software stack for applications that scale

Twitter: <at> viktorklang

Alexander Kuklev | 13 Aug 2012 12:25
Picon

Re: Towards Scala 3



понедельник, 13 августа 2012 г., 11:57:05 UTC+2 пользователь √iktor Klang написал:
On Mon, Aug 13, 2012 at 11:36 AM, Alexander Kuklev <aku... <at> gmail.com> wrote:
понедельник, 13 августа 2012 г., 11:27:17 UTC+2 пользователь √iktor Klang написал:
Unifying tuples and Parameter lists?
§ 3 is exactly about this topic. You should unify parameter lists not with tuples but with traits. Tuples contradict named and default arguments, implicits and generics. Traits fit perfectly, see in the text above. Of course, we should generate implicit conversions from tuples into argument list traits, i.e. where all not initialized fields have <at> Arg(n) annotations and there are no abstract member types that cannot be inferred.
So it's order by annotation? What happens when you mix in multiple traits?

There's a full analogy between unification of traits and argLists and unification of generics and abstract type members. So let's see what happens in the second case. Assume, you have a trait A[TA] = {...} and a trait B[TB] = {...}. Equivalently A = { <at> Param(0) protected type TA} and the same for B.
Now you want to construct trait C extends A and B. In the default case the <at> Param annotation will be forgotten, because that's how existentials should work. But you can also supply them with new param annotations and optionally rename: trait C[T1, T2] extends A[T1] with B[T2].
The same should happen to the <at> Arg annotations: they are lost on inheritance, but the heir can provide new ones (also for inherited members, which should be not a problem, you can use override). I don't know how to perform renaming-on-inheritance elegantly, it seems to be a problem in both cases.
 
 
 
Removing view bounds?
Aren't they a special case of context bounds essentially?
Is the conversion applied multiple times inside the body whenever the A needs to be a B?

Oh, I see. Context bounds can be applied only to check if there is a direct conversion from A to B, view bounds accept also indirect ones.
Anyway why do you think view bounds should be deprecated?
Alexander Kuklev | 13 Aug 2012 11:43
Picon

Re: Towards Scala 3

I mean A <% B means A : Conv[B], where type Conv[B][A] = A => B.

Paul Phillips | 13 Aug 2012 17:10

Re: Towards Scala 3



On Mon, Aug 13, 2012 at 12:04 AM, Alexander Kuklev <akuklev <at> gmail.com> wrote:
I'd like to share some ideas on features that might be included into next major Scala releases.

Nice, I didn't know anyone else spent so much time thinking about things as they could be.

Josh Suereth | 13 Aug 2012 18:17
Picon
Gravatar

Re: Towards Scala 3

One idea i'd like to disagree while agreeing with is your unification of type parameters and member types.  I don't think we unify this by annotations.  I think we unify it the same way we unified OO members + functions.


Basically, the type equivalent of this:

object X {
  def x(y: String): Int = ....
}

def bar(f: String => Int) = ...

bar(X.x)  // Here we're lifting between the two concepts.


In other words, some kind of syntactically light construct for type-functions, and lifting type members into type functions, as well as direct type-lambda support.    Scala is blended OO/FP.   I think if we want to be "Scala" this should extend into the type system.

Note: I haven't thought about syntax much, but imho, it implies we have "kinds".  Essentially a way to abstract over Types with type members, the same way we define a trait to abstract over a a set of objects, you'd do the same to abstract over types.

I don't know the research very well, so I could sound like an idiot.  However, I do know Scala.   This blending is core to its existence.  Whatever changes go into the type system, should *blend* OO/FP type theory in a way only scala can.   Right now I think we're not quite in a good mix.  I mean, it's pretty nice, but I think it can get better.  

You should also look into the DOT papers for Scala to see where the research is going.









  

On Mon, Aug 13, 2012 at 11:10 AM, Paul Phillips <paulp <at> improving.org> wrote:


On Mon, Aug 13, 2012 at 12:04 AM, Alexander Kuklev <akuklev <at> gmail.com> wrote:
I'd like to share some ideas on features that might be included into next major Scala releases.

Nice, I didn't know anyone else spent so much time thinking about things as they could be.


Chris Hodapp | 13 Aug 2012 20:44
Picon
Gravatar

Re: Towards Scala 3

First, I like most of what I read here a lot. I would personally have no problem with something resembling any of these language changes going into a future version of Scala. I do have a few minor concerns, though:


1) I note that in your proposed implementation of function types as traits, you use an <at> Arg(idx: Int) notation. This continues an offense that Scala is repeatedly guilty of: supporting multiple parameter lists, but only as second class citizens. I argue that either the idea of multiple parameter lists needs to go away or we need to stop doing this. In this case, it would be easy to make it <at> Arg(list: Int, idx: Int).

Although... perhaps it is possible to make them dynamically? Since you are already allowing for the removal of implicit parameter lists further down, i guess you could just have foo(x: Int, y: Int) = x + y become a { <at> Arg(0) val y} => Int when referenced as "foo(3) _" or where you need Int => Int (so, you could actually do foo(3)(4), since Int <: Any and the (4) list means that you expect at least an Int => Any).. You could still get variadic to work through split-list or Seq-as-_* syntax... Kind of Hasekll-y... IDK. just a sudden thought I had, there are probably some edge cases that make this a bad idea,

2) You use the Arg annotation for values but the Param annotation for types. I think that it is in order to either motivate this decision or unify the naming into TArgs/VArgs and TParams/VParams.

3) You don't mention how *-params would work in this annotation-based system. I, for one, feel like this would be a good time to bring them into functions, since we are already bringing in polymorphism and default values.

4) Right now, implicitly has a valid method signature, namely [T](implicit x: T)T. It's not that big of a deal, but I think that it should be noted that the changes you propose would require it to become a macro to avoid making it circularly dependent.

5)  "In order for this notation to be overridable it is essential to allow only one context bound per type, so the cases like “[T: Ord : TypeTag]” should be deprecated. It's always possible to use manual implicit arguments/fields for one or more witnesses instead." :-\ . I really REALLY like that this overcomes the problem of context bounds not having a canonical name in the body scope and what you've described looks really good for single-bound types, but can't we just call them T_1, T_2, etc. to cover the multi-bound case?

Chris Hodapp | 13 Aug 2012 21:16
Picon
Gravatar

Re: Towards Scala 3

6) I do want to clarify: this trait-based function system does not require object creation every time the function is invoked, correct?

On Monday, August 13, 2012 1:44:07 PM UTC-5, Chris Hodapp wrote:

First, I like most of what I read here a lot. I would personally have no problem with something resembling any of these language changes going into a future version of Scala. I do have a few minor concerns, though:

1) I note that in your proposed implementation of function types as traits, you use an <at> Arg(idx: Int) notation. This continues an offense that Scala is repeatedly guilty of: supporting multiple parameter lists, but only as second class citizens. I argue that either the idea of multiple parameter lists needs to go away or we need to stop doing this. In this case, it would be easy to make it <at> Arg(list: Int, idx: Int).

Although... perhaps it is possible to make them dynamically? Since you are already allowing for the removal of implicit parameter lists further down, i guess you could just have foo(x: Int, y: Int) = x + y become a { <at> Arg(0) val y} => Int when referenced as "foo(3) _" or where you need Int => Int (so, you could actually do foo(3)(4), since Int <: Any and the (4) list means that you expect at least an Int => Any).. You could still get variadic to work through split-list or Seq-as-_* syntax... Kind of Hasekll-y... IDK. just a sudden thought I had, there are probably some edge cases that make this a bad idea,

2) You use the Arg annotation for values but the Param annotation for types. I think that it is in order to either motivate this decision or unify the naming into TArgs/VArgs and TParams/VParams.

3) You don't mention how *-params would work in this annotation-based system. I, for one, feel like this would be a good time to bring them into functions, since we are already bringing in polymorphism and default values.

4) Right now, implicitly has a valid method signature, namely [T](implicit x: T)T. It's not that big of a deal, but I think that it should be noted that the changes you propose would require it to become a macro to avoid making it circularly dependent.

5)  "In order for this notation to be overridable it is essential to allow only one context bound per type, so the cases like “[T: Ord : TypeTag]” should be deprecated. It's always possible to use manual implicit arguments/fields for one or more witnesses instead." :-\ . I really REALLY like that this overcomes the problem of context bounds not having a canonical name in the body scope and what you've described looks really good for single-bound types, but can't we just call them T_1, T_2, etc. to cover the multi-bound case?

Alexander Kuklev | 13 Aug 2012 21:55
Picon

Re: Towards Scala 3


Mon, 2012-08-13 <at> 21:16:15 UTC+2, Chris Hodapp:
5)  "In order for this notation to be overridable it is essential to allow only one context bound per type, so the cases like “[T: Ord : TypeTag]” should be deprecated. It's always possible to use manual implicit arguments/fields for one or more witnesses instead." :-\ . I really REALLY like that this overcomes the problem of context bounds not having a canonical name in the body scope and what you've described looks really good for single-bound types, but can't we just call them T_1, T_2, etc. to cover the multi-bound case?

I don't thing the naming scheme with underscores is good. We lose the analogy of "Class and companion object" and "abstract type and companion typeclass instance" and we lose the ability of restricting context bound in obvious way like in:
trait A {type T: Semigroup; ...}
trait B extends A {type T: Monoid; ...}


6) I do want to clarify: this trait-based function system does not require object creation every time the function is invoked, correct?
Correct. Its like value classes, something conceptually and syntactically nice which gets optimized away in compile-time completely.

Now about multiple argument lists. Why don't you like to see them as nested functions? I mean:
def f(n: Int)(s: String): Int
f: Int => String => Int

Although... perhaps it is possible to make them dynamically? Since you are already allowing for the removal of implicit parameter lists further down, i guess you could just have foo(x: Int, y: Int) = x + y become a { <at> Arg(0) val y} => Int when referenced as "foo(3) _" or where you need Int => Int (so, you could actually do foo(3)(4), since Int <: Any and the (4) list means that you expect at least an Int => Any).. You could still get variadic to work through split-list or Seq-as-_* syntax... Kind of Hasekll-y... IDK. just a sudden thought I had, there are probably some edge cases that make this a bad idea,
I don't like the idea of argument order-based partial application is good for multiple reasons, one is that it generally leads to ugly unreadable code. But we could think out some syntax for name based partial application. Actually, we can do it right now with an appropriate macro:
Say you have foo(x: Int, y: Int, z: Int): Int, write something like foo.partApply(z = 5) and get a function of the kind (x: Int, y: Int)Int.
Rex Kerr | 13 Aug 2012 22:57
Picon
Gravatar

Re: Towards Scala 3

Interesting ideas!  I have mixed impressions, however.

On Mon, Aug 13, 2012 at 3:04 AM, Alexander Kuklev <akuklev <at> gmail.com> wrote:

1. Type providers (and macro annotations)

Yes, please!
 
2. Unification of generics and abstract type members
This is a thing proposed by Martin in SIP 18 and this discussion.

Sure, if it works out as well as it seems like it might.
 
3. Harmonization of trait and argument list signatures
A function of multiple arguments “def f(a: Int, b: Int): Int” is function from tuple “(Int, Int) => Int”.
Well, almost. The thing is, there are named and default arguments and parametric polymorphism, so actually it's a function from a trait:def f[T](t: T, a: Int, b: Int = 0): Int trait ArgTypeOfF[T] { <at> Arg(1) val t: T <at> Arg(2) val a: Int val b: Int = 0 } // Now holds: f: ArgTypeOfF => Int
This is like tuple/parameter list harmonization, but both more ambitious and more awkward.

It would be nice to not have to worry about the difference between tuples and parameter lists, but with this proposal you do have to worry--now you have to make tuples look like these weird traits.

Also, it would be nice to be able to have functions that can take generic type parameters:

  def f[T](t: T) = t
  f _     // Tell me, do you honestly want Nothing => Nothing here?!

But the more parsimonious solution to that is to have an appropriate type-level encoding (e.g. Generic[T]):

  val g: Function1[Generic[T],Generic[T]] = f _
  // g is instantiated to have def apply[T](t: T): T

I don't think we want named parameters on functions; type-matching seems like a much more useful generalization than argument-name matching.  For implicits and defaults, traits (slightly extended) seem like a reasonable way to proceed, but I'm not sure the use case is sufficiently compelling to warrant the additional complexity.  You can always pass those parameters along and/or fill them in.

 
4. Implicit arguments reform
Implicit arguments were introduced in times there were no named and default arguments. But think for a minute! An implicit argument is just an argument with a default value, namely “implicitly[Type]” which has to be evaluated on call site (not the definition site) of the function.

No, that's not true.  Implicits and defaults are orthogonal concerns:

scala> def f(x: Int)(implicit y: Int) = x+y
f: (x: Int)(implicit y: Int)Int

scala> def g(x: Int)(implicit y: Int = 2) = x+y
g: (x: Int)(implicit y: Int)Int

scala> f(2)
<console>:9: error: could not find implicit value for parameter y: Int
              f(2)
               ^

scala> g(2)
res1: Int = 4

scala> implicit val default: Int = 7
default: Int = 7

scala> f(2)
res2: Int = 9

scala> g(2)
res3: Int = 9
 
Maybe you want to enable

  def f(x: Int, implicit y: Int) = ...

but your analysis and syntax misses an important use-case (and an important distinction between two concepts).

5. Context bounds for abstract type members
Now, as implicits are allowed everywhere, nothing stays in way of the proposal discussed here.

(This depends on earlier features that I question, so by extension I question this also.)
 
6. Aliases for context bounds
As you probably know, Scala both has a trait Ordered and a typeclass Ordering[_]. The typeclass is the right way to impose an order, the trait that demands comparison operator inside objects is a wrong way that will be deprecated someday. However, it's easier to write sort(xs: List[Ordered]) than sort[T: Ordering](xs: List[T]). For many people, second notation is a burden.

Crikey.  It's not that hard to do it the latter way.  On the list of annoyances, this is way, way down there.
 
7. Infix operators
Scala gets more and more typeclass based, so comparison, addition and other infix operators travel from classes into their typeclasses. To compare two objects you now must write ordering.compare(a, b) instead of a.add(b) which is a highly positive development but a source of certain inconvenience. For infix notation “a < b” to work in this cases, library developers of Scala ≤2.10 use workarounds like implicit conversions of the first argument into a helper type. Such crutches make libraries intransparent and can cause problems with implicit conversion conflicts. We should provide a straight way to do it!

I would propose the following one: whenever you encounter unresolved expression of the kind “a OP b”, convert it into “(associate(a) merge associate(b))._OP(a, b)” where the associate(x) is the companion object of the type of x if it's concrete, or the associated typeclass instance, if the type of x is an context bound abstract type. “merge” is defined by default as a macro operation that combines all public methods of left and right objects into one, if there are conflicts, a compile time error is produced. (Of course, _merge can be overridden for particular object typeclasses, it's a usual infix operator.)

Usecase:trait Additive[T] { def _+(a: T, b: T): T ... } def foo[A: Additive](a: A, b: A) = a + b
Making typeclass' companion object implicits automatically available adds much less complexity to the language and requires only marginally more boilerplate.
 
8. Implicits providers
These are functions to be called in compile time (akin to macros) each time an implicit is resolved. One usage is described in this discussion, where we propose a solution for separating optimization details from the logic by using implicits.

I guess you could stick this into implicits, but it's really a separate feature.  I'm unconvinced that adding a runtime argument for what is purely a compile-time concern is the right way to go.
 

What can you do with such upgraded Scala?

The big deal for me is that upgraded Scala gets mathematics right:
– We can correctly encode theories: structures like group, ring, algebra, topological space, category, etc.

Why can't we already, save for performance considerations, which this proposal doesn't directly address?  (Why can't the performance issues be solved with the current typeclass framework?)
 
– We can amend the standard library in such a (backward compatible) way that the hierarchies of number types and collection types would respect their mathematical nature; Int would be an instance of a ring, Option[T] an endofunctor and so on.

Likewise--how is it capability rather than desired feature set, efficiency, or some other concern that is the barrier now?
 
– We'll be able reason about types and programs, see this post. In fact, Scala will gain another dimension, it'll be not only a programming language, but also a proof assistant with unprecedented flexibility of notation.

Flexibility of notation is only valuable if there are many different good ways to do things.  Flexibility to use bad notation is not necessarily an advantage.  Thus--and this isn't really addressed in your post--the proof assistant should be very unobtrusive.  Pushing too much into implicits, for example, is only a good idea if the language can simultaneously handle them without requiring awkward boilerplate where you tell the compiler how to keep everything straight.

  --Rex

Paul Phillips | 13 Aug 2012 23:09

Re: Towards Scala 3



On Mon, Aug 13, 2012 at 1:57 PM, Rex Kerr <ichoran <at> gmail.com> wrote:
Pushing too much into implicits, for example, is only a good idea if the language can simultaneously handle them without requiring awkward boilerplate where you tell the compiler how to keep everything straight.

That reminds me, a while ago I implemented jason's idea to have an <at> implicitWeight annotation.  Look at all the code which fell away, but more importantly, look at how you can express the implicit ranking in a direct way with the implicits next to one another, rather than having to inject a meaningless superclass every time you want to draw a distinction.


Alexander Kuklev | 13 Aug 2012 23:18
Picon

Re: Towards Scala 3



Mon, 2012-09-12 <at> 22:57:06 UTC+2, Rex Kerr:

4. Implicit arguments reform
Implicit arguments were introduced in times there were no named and default arguments. But think for a minute! An implicit argument is just an argument with a default value, namely “implicitly[Type]” which has to be evaluated on call site (not the definition site) of the function.

No, that's not true.  Implicits and defaults are orthogonal concerns: 
[...] 
The rare case you mentioned was thought to de implemented this way:
def g(x: Int, y: Int = implicit(default = 2))

The analogy between implicit arguments and named arguments with default value is that both can be omited or overridden. The overriding for usual named arguments is very readable and intuitive: g(5, y = 0), while the same for implicits is not. The other thing which is very importaint is that by including implicits into the basic argument list instead of having a separate one, we make complicated type dependencies possible. I've already came across the inability to write foo[T: C](x: T.SomeType) multiple times.

5. Context bounds for abstract type members
Now, as implicits are allowed everywhere, nothing stays in way of the proposal discussed here.

(This depends on earlier features that I question, so by extension I question this also.)
This is the main issue for encoding mathematical concepts. In order to define, say, VectorSpace in a natural way, you need to be able to write
VectorSpace[V] {
 type GroundField : Field
 ...
}
 
What can you do with such upgraded Scala?

The big deal for me is that upgraded Scala gets mathematics right:
– We can correctly encode theories: structures like group, ring, algebra, topological space, category, etc.

Why can't we already, save for performance considerations, which this proposal doesn't directly address?
Mainly because of lack of context bounds for abstract type members and lack of propositions-as-types and predicates-as-types encoding.
Alexander Kuklev | 14 Aug 2012 00:50
Picon

Re: Towards Scala 3

Mon, 2012-08-13 <at> 22:57:06 UTC+2, Rex Kerr:
Also, it would be nice to be able to have functions that can take generic type parameters:

  def f[T](t: T) = t
  f _     // Tell me, do you honestly want Nothing => Nothing here?!

I missed this. In the proposal above, functions can have generic type parameters.
The function you wrote would have the type {type T; val t: T} => T. (I omit annotations for brevity.)

But the more parsimonious solution to that is to have an appropriate type-level encoding (e.g. Generic[T]):

  val g: Function1[Generic[T],Generic[T]] = f _
  // g is instantiated to have def apply[T](t: T): T

I have considered type level encodings instead of annotations <at> Param and <at> Arg. Something like representing trait F[T] = {...} as trait F extends Generic1 = {private type T = super._1}, and trait ArgListOfF extends Tuple2 {val x: Int = super._1, val y: Int = super._2} for argument list of f(x: Int, y: Int). Unfortunately, there are two issues:
a) we need renaming rather than copying, something like trait F extends Generic1 = {type super._1 as T} and {val super._1 as x: Int},
b) it doesn't work really well with inheritance. Often we inherit from two different generic types trait F[A, B] extends X[A] with Y[B], if both X[_] and Y[_] now inherit from Generic1, how could we prevent the name conflict?
 
I don't think we want named parameters on functions; type-matching seems like a much more useful generalization than argument-name matching.
Could you elaborate on this? What type matching and argument-name matching do you mean?
Rex Kerr | 14 Aug 2012 01:19
Picon
Gravatar

Re: Towards Scala 3

On Mon, Aug 13, 2012 at 6:50 PM, Alexander Kuklev <akuklev <at> gmail.com> wrote:
Mon, 2012-08-13 <at> 22:57:06 UTC+2, Rex Kerr:
Also, it would be nice to be able to have functions that can take generic type parameters:

  def f[T](t: T) = t
  f _     // Tell me, do you honestly want Nothing => Nothing here?!

I missed this. In the proposal above, functions can have generic type parameters.
The function you wrote would have the type {type T; val t: T} => T. (I omit annotations for brevity.)

I was just explaining what happened now, and why one might want something better.
 

But the more parsimonious solution to that is to have an appropriate type-level encoding (e.g. Generic[T]):

  val g: Function1[Generic[T],Generic[T]] = f _
  // g is instantiated to have def apply[T](t: T): T

I have considered type level encodings instead of annotations <at> Param and <at> Arg. Something like representing trait F[T] = {...} as trait F extends Generic1 = {private type T = super._1}, and trait ArgListOfF extends Tuple2 {val x: Int = super._1, val y: Int = super._2} for argument list of f(x: Int, y: Int). Unfortunately, there are two issues:
a) we need renaming rather than copying, something like trait F extends Generic1 = {type super._1 as T} and {val super._1 as x: Int},
b) it doesn't work really well with inheritance. Often we inherit from two different generic types trait F[A, B] extends X[A] with Y[B], if both X[_] and Y[_] now inherit from Generic1, how could we prevent the name conflict?

I don't think you quite understood my proposal.  My question is: what is the type of t in f[T](t: T) before the generic is made specific?  Your answer is: a type in a custom trait.  My answer is: sure, but you don't need a separate trait each time; you just need one marker/wrapper trait--let's call it G.  Then
  G[T] => R
is shorthand for
  Function1[G[T],R] {
    def apply[T](t: T) => R
  }
where G[T] pushes genericity from the class level to the method level (which with erasure can be both rigorously and trivially done).
 
 
I don't think we want named parameters on functions; type-matching seems like a much more useful generalization than argument-name matching.
Could you elaborate on this? What type matching and argument-name matching do you mean?

I mean that duplicate: (Int, String) => String is plenty of information for an interface.  You shouldn't need to know that Int was called n and String was called original--it clutters up the interface with dummy symbols.  What is the class corresponding to the supertype of the functions corresponding to these two methods?

  def f1(s: String): Int
  def f2(name: String): Int

By encoding these names into the trait it seems like you are presuming that I couldn't want to abstract across these two functions.

But I do.  Almost surely I do.

You've got the annotation backwards, I think:

trait ThreeArgs[T] extends (T,Int,Int) with FnArg {
  <at> named("t") val _1: T
  <at> named("a") val _2: Int
  <at> named("b") val _3: Int = 0
}

This is pretty much just tuple/function-arg unification.

  --Rex

Alexander Kuklev | 14 Aug 2012 01:31
Picon

Re: Towards Scala 3

OK, now I see your point. You're right, putting annotation backwards makes more sense. It should be this way around.


Tue, 2012-08-14 <at> 1:19:19 UTC+2, Rex Kerr:
I don't think you quite understood my proposal.  My question is: what is the type of t in f[T](t: T) before the generic is made specific?  Your answer is: a type in a custom trait.  My answer is: sure, but you don't need a separate trait each time; you just need one marker/wrapper trait--let's call it G.  Then
  G[T] => R
is shorthand for
  Function1[G[T],R] {
    def apply[T](t: T) => R
  }
where G[T] pushes genericity from the class level to the method level (which with erasure can be both rigorously and trivially done).
 
 
I don't think we want named parameters on functions; type-matching seems like a much more useful generalization than argument-name matching.
Could you elaborate on this? What type matching and argument-name matching do you mean?

I mean that duplicate: (Int, String) => String is plenty of information for an interface.  You shouldn't need to know that Int was called n and String was called original--it clutters up the interface with dummy symbols.  What is the class corresponding to the supertype of the functions corresponding to these two methods?

  def f1(s: String): Int
  def f2(name: String): Int

By encoding these names into the trait it seems like you are presuming that I couldn't want to abstract across these two functions.

But I do.  Almost surely I do.

You've got the annotation backwards, I think:

trait ThreeArgs[T] extends (T,Int,Int) with FnArg {
  <at> named("t") val _1: T
  <at> named("a") val _2: Int
  <at> named("b") val _3: Int = 0
}

This is pretty much just tuple/function-arg unification. 
Erik Osheim | 13 Aug 2012 23:18
Favicon

Re: Towards Scala 3

Hi Alexander,

I think I broadly agree with some of the ideas in your email. That
said, I'm just going to quote the part of your email that jumped out at
me.

On Mon, Aug 13, 2012 at 12:04:37AM -0700, Alexander Kuklev wrote:
> Scala gets more and more typeclass based, so comparison, addition and other 
> infix operators travel from classes into their typeclasses. To compare two 
> objects you now must write ordering.compare(a, b) instead of a.add(b) which 
> is a highly positive development but a source of certain inconvenience. For 
> infix notation “a < b” to work in this cases, library developers of Scala 
> ≤2.10 use workarounds like implicit conversions of the first argument into 
> a helper type. Such crutches make libraries intransparent and can cause 
> problems with implicit conversion conflicts. We should provide a straight 
> way to do it!

I'm curious how much you have used these things?

Right now, from a user perspective, the only real work is a single
wildcard import to pull in conversions to Ops. As of 2.10.0 these can
be done at compile-time, and have zero performance impact compared to
the usual typeclass approach. In the case of math and algebra, we can
have basically zero overhead compared to a direct (non-generic)
implementation.

As far as the library author's plight, I agree that the typeclass
encoding is still a little bit opaque. However, the solution to that
isn't to create even more rules and hacks to turn methods into infix
operators via an underscore! Rather, the solution would be to provide
first-class syntax for typeclasses (e.g. Ring), typeclass instances
(e.g. IntHasRing), and implicits used to add methods to member values
(e.g. 42).

> The big deal for me is that upgraded Scala gets mathematics right:
> – We can correctly encode theories: structures like group, ring, algebra, 
> topological space, category, etc.
> – We can amend the standard library in such a (backward compatible) way 
> that the hierarchies of number types and collection types would respect 
> their mathematical nature; Int would be an instance of a ring, Option[T] an 
> endofunctor and so on.
> – We'll be able reason about types and programs, see this
post<http://akuklev.livejournal.com/1068955.html>. 
> In fact, Scala will gain another dimension, it'll be not only a programming 
> language, but also a proof assistant with unprecedented flexibility of 
> notation.

I don't think this vision is totally correct. Int is not one ring, but
rather participates in many rings, with (Int,+,*) being the "usual"
one. Since it's not possible to have a single type extend a trait/class
more than once, we have to be careful of constructions like "Ring[A]
extends Monoid[A] ..." since a single ring contains two monoids,
(Int,+) and (Int,*).

It's precisely these kinds of issues that makes this a hard thing to
do. Having campaigned in the past to get numerical improvements into
the standard library I have come around to the idea that there isn't
enough consensus in the community yet, and that libraries are the best
place for this kind of work.

I'd love to get your opinion on Spire [1] which Tom Switzer and I have
been working on for awhile. We're definitely trying to "get mathematics
right" and I think we've already achieved a lot of progress. Having
spent a lot of time thinking about these issues and working on it, I'm
not convinced that most of your changes are necessary, although some
would obviously be nice.

-- Erik

[1] https://github.com/non/spire

Alexander Kuklev | 13 Aug 2012 23:40
Picon

Re: Towards Scala 3

Mon, 2012-08-13 <at>  23:18:15 UTC+2, Erik Osheim:

On Mon, Aug 13, 2012 at 12:04:37AM -0700, Alexander Kuklev wrote:
> Scala gets more and more typeclass based, so comparison, addition and other
> infix operators travel from classes into their typeclasses. To compare two
> objects you now must write ordering.compare(a, b) instead of a.add(b) which
> is a highly positive development but a source of certain inconvenience. For
> infix notation “a < b” to work in this cases, library developers of Scala
> ≤2.10 use workarounds like implicit conversions of the first argument into
> a helper type. Such crutches make libraries intransparent and can cause
> problems with implicit conversion conflicts. We should provide a straight
> way to do it!

I'm curious how much you have used these things?
Pretty often, I'm using Scala as a small custom computer algebra system for the stuff I'm doing in quantum field theory. :-)
> The big deal for me is that upgraded Scala gets mathematics right:
> – We can correctly encode theories: structures like group, ring, algebra,
> topological space, category, etc.
> – We can amend the standard library in such a (backward compatible) way
> that the hierarchies of number types and collection types would respect
> their mathematical nature; Int would be an instance of a ring, Option[T] an
> endofunctor and so on.
> – We'll be able reason about types and programs, see this post<http://akuklev.livejournal.com/1068955.html>.
> In fact, Scala will gain another dimension, it'll be not only a programming
> language, but also a proof assistant with unprecedented flexibility of
> notation.

I don't think this vision is totally correct. Int is not one ring, but
rather participates in many rings, with (Int,+,*) being the "usual"
one.
Exactly. Usual one is the default one, hence implicit objects. Other ones can be used exactly as good, but you have to specify them explicitly or override the implicit in your scope.
 
Since it's not possible to have a single type extend a trait/class
more than once, we have to be careful of constructions like "Ring[A]
extends Monoid[A] ..." since a single ring contains two monoids,
(Int,+) and (Int,*).

I don't really see the problem.

trait UnitalRing[T] {
  val additiveStructure: AbelianGroup[T]
  val multiplicativeStructure: Monoid[T]
  def _+(x: T, y: T): T = additiveStructure.compose(x, y)
  def _·(x: T, y: T): T = multiplicativeStructure.compose(x, y)
  val lDistributivity(a: T, b: T, c: T): ✓(a · (b + c) = (a · b) + (a · c))
  val rDistributivity(a: T, b: T, c: T): ✓((a + b· c = (a · c) + (b · c))
}

It's precisely these kinds of issues that makes this a hard thing to
do. Having campaigned in the past to get numerical improvements into
the standard library I have come around to the idea that there isn't
enough consensus in the community yet, and that libraries are the best
place for this kind of work.
Oh, I was not aware of it.
 
I'd love to get your opinion on Spire [1] which Tom Switzer and I have
been working on for awhile. We're definitely trying to "get mathematics
right" and I think we've already achieved a lot of progress. Having
spent a lot of time thinking about these issues and working on it, I'm
not convinced that most of your changes are necessary, although some
would obviously be nice.

Thank you. Just started to read your code, it beautiful. And I can't help thinking how much boilerplate could be spared if some of the features I proposed, were already there. :-)

Gmane