This page is no longer maintained — Please continue to the home page at www.scala-lang.org

An idea for reducing typeclass-related boilerplate

20 replies
Pavel Pavlov
Joined: 2011-12-01,
User offline. Last seen 42 years 45 weeks ago.
Hi all,

Just have an idea how to make code which uses typeclasses better.
Consider an example:

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

I suppose this is verbose and ugly. The alternative which do not use context bound is not good as well:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

In fact, what do we want here is to call `zero` method from something that is `Integral`.
Context bound syntax clearly expresses that T is Integral here.
With another bit of syntaxic sugar we can express our intensions straightforwardly:

def sum[T : Integral](list: List[T]): T = {
    list.foldLeft(T.zero)(_ + _)
}

As I can see now such syntax does not conflict with anything.
For me it's clear, easy to read and more 'high-level".
It hides the remaining typeclass implementation details (some of them are already hidden by context bound syntax) from the user.

What do you think?

Regards,
Pavel
Jason Zaugg
Joined: 2009-05-18,
User offline. Last seen 38 weeks 5 days ago.
Re: An idea for reducing typeclass-related boilerplate
On Sat, Jan 21, 2012 at 4:08 PM, Pavel Pavlov <pavel [dot] e [dot] pavlov [at] gmail [dot] com> wrote:
Hi all,

Just have an idea how to make code which uses typeclasses better.
Consider an example:

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

I suppose this is verbose and ugly. The alternative which do not use context bound is not good as well:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

In fact, what do we want here is to call `zero` method from something that is `Integral`.
Context bound syntax clearly expresses that T is Integral here.
With another bit of syntaxic sugar we can express our intensions straightforwardly:

def sum[T : Integral](list: List[T]): T = {
    list.foldLeft(T.zero)(_ + _)
}

As I can see now such syntax does not conflict with anything.
For me it's clear, easy to read and more 'high-level".
It hides the remaining typeclass implementation details (some of them are already hidden by context bound syntax) from the user.

We've  gone down a similar direction in scalaz, and name the implicit parameter T.
def sum[T](list: List[T])(implicit T: Integral[T]): T = {
    import integral._
    list.foldLeft(T.zero)(_ + _)
}
What do you propose if there are multiple context bounds for a type parameter? I don't see a straightforward rewriting in that case.
-jason
Tom Switzer
Joined: 2011-07-19,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate
I think you are really just missing that there is no importable function zero in the standard scala lib.
import Numeric.Implicits._def zero[A](implicit n: Numeric[A]): A = n.zerodef sum[A: Numeric](xs: Seq[A]): A = xs.foldLeft(zero)(_ + _)
Scalaz does have this though with its monoid typeclass:
def sum[A: Monoid](xs: Seq[A]): A = xs.fold(mzero)(_ |+| _)
Cheers,Tom

On Sat, Jan 21, 2012 at 10:21 AM, Jason Zaugg <jzaugg [at] gmail [dot] com> wrote:
On Sat, Jan 21, 2012 at 4:08 PM, Pavel Pavlov <pavel [dot] e [dot] pavlov [at] gmail [dot] com> wrote:
Hi all,

Just have an idea how to make code which uses typeclasses better.
Consider an example:

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

I suppose this is verbose and ugly. The alternative which do not use context bound is not good as well:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._
    list.foldLeft(integral.zero)(_ + _)
}

In fact, what do we want here is to call `zero` method from something that is `Integral`.
Context bound syntax clearly expresses that T is Integral here.
With another bit of syntaxic sugar we can express our intensions straightforwardly:

def sum[T : Integral](list: List[T]): T = {
    list.foldLeft(T.zero)(_ + _)
}

As I can see now such syntax does not conflict with anything.
For me it's clear, easy to read and more 'high-level".
It hides the remaining typeclass implementation details (some of them are already hidden by context bound syntax) from the user.

We've  gone down a similar direction in scalaz, and name the implicit parameter T.
def sum[T](list: List[T])(implicit T: Integral[T]): T = {
    import integral._
    list.foldLeft(T.zero)(_ + _)
}
What do you propose if there are multiple context bounds for a type parameter? I don't see a straightforward rewriting in that case.
-jason

Pavel Pavlov
Joined: 2011-12-01,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate


суббота, 21 января 2012 г. 22:21:22 UTC+7 пользователь Jason Zaugg написал:

What do you propose if there are multiple context bounds for a type parameter? I don't see a straightforward rewriting in that case.
-jason



Well, we can use explicit context annotations for that (with syntax that mimics type annotations):

def foo[T : A : B]() = {
(T: A).zero
(T: B).one
}

That gives us alternative for `implicitly` as well:

val x = implicitly[Integral[T]]
val x = (T: Integral) // read this: T as Integral


Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate

Looks good in my opinion.

milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Sat, Jan 21, 2012 at 3:58 PM, Pavel Pavlov wrote:
> суббота, 21 января 2012 г. 22:21:22 UTC+7 пользователь Jason Zaugg написал:
>> What do you propose if there are multiple context bounds for a type
>> parameter? I don't see a straightforward rewriting in that case.
>
> Well, we can use explicit context annotations for that (with syntax that
> mimics type annotations):
>
> def foo[T : A : B]() = {
> (T: A).zero
> (T: B).one
> }
>
>
> That gives us alternative for `implicitly` as well:
>
> val x = implicitly[Integral[T]]
> val x = (T: Integral) // read this: T as Integral

I really like the idea! :-)

I think I'd prefer something more like this though,

def foo[T : A : B]() = {
  A[T].zero
  B[T].one
}

ie. have A[T].foo be sugar for,

implicitly[A[T]].foo

Cheers,

Miles

d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Sat, Jan 21, 2012 at 04:49:21PM +0000, Miles Sabin wrote:
> I think I'd prefer something more like this though,
>
> def foo[T : A : B]() = {
>   A[T].zero
>   B[T].one
> }
>
> ie. have A[T].foo be sugar for,
>
> implicitly[A[T]].foo

Yes, this is nice. I think Scalaz7 has moved in this direction.

I mean to go back and add similar apply() methods to my projects.

Itwould be valuable for many of us who use this kind of pattern to try
to agree on some conventions, just to make the pattern easier for
people to recognize and use correctly.

Pavel Pavlov
Joined: 2011-12-01,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate


суббота, 21 января 2012 г. 23:49:21 UTC+7 пользователь Miles Sabin написал:
>
> That gives us alternative for `implicitly` as well:
>
> val x = implicitly[Integral[T]]
> val x = (T: Integral) // read this: T as Integral

I really like the idea! :-)

I think I'd prefer something more like this though,

def foo[T : A : B]() = {
  A[T].zero
  B[T].one
}

ie. have A[T].foo be sugar for,

  implicitly[A[T]].foo

I think your syntax is more problematic than mine because it redefines the syntax which we already have in Scala.
As A is type, it can have companion object, while for type skolem T we can safely define the value with the same name: automatically (as in my proposal) or by hand (as Jason does in scalaz).
Moreover, expansion `A[T]` to `implicitly[A[T]]` looks plain dangerous to me because the expanded term contains original term inside. This can produce some tricky problems, y'know. And you loose the possibility to write type `A[T]` that will not expand to value `implicitly[A[T]]`.

I'm starting to think that analogy between pairs (value literal/variable: its type) and (type name/type skolem: its context bound object) is fruitful enough. For all uses of `x: T` in Scala you can provide direct analogy `T: CB` with obvious meaning. Some of these cannot be implemented in Scala for now (for example, `T: CB` in pattern matcher), all others are ok.

Leading that way we should change syntax for multiple context bounds to:
  def foo[T : A with B]()
which is nicer for me.

Regards,
Pavel
ichoran
Joined: 2009-08-14,
User offline. Last seen 2 years 3 weeks ago.
Re: An idea for reducing typeclass-related boilerplate
On Sat, Jan 21, 2012 at 12:41 PM, Pavel Pavlov <pavel [dot] e [dot] pavlov [at] gmail [dot] com> wrote:

Leading that way we should change syntax for multiple context bounds to:
  def foo[T : A with B]()
which is nicer for me.

How do you distinguish between
  (implicit ab: A with B)
  (implicit a: A, b: B)
with that notation?

  --Rex

Pavel Pavlov
Joined: 2011-12-01,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate
You caught me :)
Yes, this is a problem.
I don't know how to solve it.

As a workaround, we can always expand the syntax `[T : A with B]` to one of the alternatives (as it's sugar after all).
Then the other case can be written only in more verbose form (as implicit parameter list).


воскресенье, 22 января 2012 г. 1:11:51 UTC+7 пользователь Rex Kerr написал:
On Sat, Jan 21, 2012 at 12:41 PM, Pavel Pavlov <pavel [dot] e [dot] [dot] [dot] [at] gmail [dot] com> wrote:

Leading that way we should change syntax for multiple context bounds to:
  def foo[T : A with B]()
which is nicer for me.

How do you distinguish between
  (implicit ab: A with B)
  (implicit a: A, b: B)
with that notation?

  --Rex

ichoran
Joined: 2009-08-14,
User offline. Last seen 2 years 3 weeks ago.
Re: An idea for reducing typeclass-related boilerplate


On Sat, Jan 21, 2012 at 1:18 PM, Pavel Pavlov <pavel [dot] e [dot] pavlov [at] gmail [dot] com> wrote:
You caught me :)
Yes, this is a problem.
I don't know how to solve it.

As a workaround, we can always expand the syntax `[T : A with B]` to one of the alternatives (as it's sugar after all).
Then the other case can be written only in more verbose form (as implicit parameter list).

Or we could use some other separator than "with", since that actually means the wrong thing.

Perhaps : would work?

  [T: A:B]

If you put your spaces in the right spot it looks okay to me.

  --Rex

Pavel Pavlov
Joined: 2011-12-01,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate


воскресенье, 22 января 2012 г. 1:23:26 UTC+7 пользователь Rex Kerr написал:

Perhaps : would work?

  [T: A:B]

It definitely will work :) I hope there are enough regression tests for this feature in scala repo.
Just looks somewhat strange for my eyes. But this is minor issue.
 
Ben Hutchison 3
Joined: 2009-11-02,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate

On Sun, Jan 22, 2012 at 2:08 AM, Pavel Pavlov wrote:
> In fact, what do we want here is to call `zero` method from something that
> is `Integral`.
> Context bound syntax clearly expresses that T is Integral here.
> With another bit of syntaxic sugar we can express our intensions
> straightforwardly:
>
> def sum[T : Integral](list: List[T]): T = {
>     list.foldLeft(T.zero)(_ + _)
> }
>
>
> What do you think?

I fully agree that Scala's current typeclass encoding is a little
cumbersome and could benefit from further syntactic sugar.

An alternative proposal that has been raised a few times allows
implicit params to be imported from the parameter list. Eg in the
example below, 'zero' can be used unqualified due to the import
modifier on the Integral context bound:

def sum[T : import Integral](list: List[T]): T = {
  list.foldLeft(zero)(_ + _)
}

I don't think it needs to be context bound specific, any parameter
could be imported. Eg this would be legal syntax:

def sum[T](list: List[T])(implicit import integral: Integral[T]): T = {
list.foldLeft(zero)(_ + _)
}

Conflicting/ambiguous imports could be handled the same way they are currently.

-Ben

d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Mon, Jan 30, 2012 at 02:21:55PM +1100, Ben Hutchison wrote:
> An alternative proposal that has been raised a few times allows
> implicit params to be imported from the parameter list. Eg in the
> example below, 'zero' can be used unqualified due to the import
> modifier on the Integral context bound:
>
> def sum[T : import Integral](list: List[T]): T = {
>   list.foldLeft(zero)(_ + _)
> }

This could be nice. I think the behavior would need to be pretty well
spec'd though. For instance:

def xyz[T:Numeric, U:Numeric](t:T, u:U) = { ... }

We'd need to be able to support this, so I am guessing it would be:

def xyz[T, U](t:T, u:U)(implicit et:Numeric[T], eu:Numeric[U]) = {
import et._
import eu._
...
}

This would mean that you could say "zero" to get eu.zero, but you'd
have to say "implicit[Numeric[T]].zero" or something similar for
et.zero, right?

In some ways, the T.zero syntax is nicer only because you don't have to
worry about accidentally masking names, or deal with this case.

geoff
Joined: 2008-08-20,
User offline. Last seen 1 year 25 weeks ago.
Re: An idea for reducing typeclass-related boilerplate
On Jan 29, 2012, at 10:19 PM, Erik Osheim wrote:
This could be nice. I think the behavior would need to be pretty well
spec'd though. For instance:

def xyz[T:Numeric, U:Numeric](t:T, u:U) = { ... }

[…]

In some ways, the T.zero syntax is nicer only because you don't have to
worry about accidentally masking names, or deal with this case.

How about reusing some syntax from pattern matching to introduce names for context bound instances:

def xyz[T:nT@Numeric, U:nU@Numeric](t:T, u:U) = { … }

or some other syntax options:

def xyz[T:Numeric=nT, U:Numeric=uT](t:T, u:U) = { … }

The name binding would be a natural place to add an import keyword

def xyz[T:import nT@Numeric, U:nU@Numeric](t:T, u:U) = { … }

the name could be optional or use an underscore when you want to import a bound's member but not give it a name

def xyz[T:import@Numeric, U:nU@Numeric](t:T, u:U) = { … }
def xyz[T:import _@Numeric, U:nU@Numeric](t:T, u:U) = { … }

What do you all thing about this option?

-- Geoff
d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Tue, Jan 31, 2012 at 09:43:16AM -0700, Geoff Reedy wrote:
> What do you all thing about this option?

It's neat, although it won't help my case.

The thing I learned working on Numeric is that these sorts of features
are great when you use them once to enable something that was
impossible. However, in this case the goal is removing some boilerplate
(e.g. an import, or use of implicitly, or something).

In these cases this new syntax doesn't really "fix" things but just
transfers the boilerplate from the method body to the type parameter.
Given that you can just write out the implicit params manually to bind
them to a name, this doesn't seem like a huge improvement. Compare:

def foo[A:Numeric](a:A) = {
import implicitly[Numeric[A]]._
...
zero
}

def foo[A](a:A)(implicit n:Numeric[A]) = {
...
n.zero
}

def foo[A:n@Numeric](a:A) = {
...
n.zero
}

None of these seem amazingly great, but I find the 2nd the most
readable in some ways. The first can be OK but many people get thrown
by implicitly. Maybe I would get used to the 3rd, and if it existed I'd
use it, but I'm not sure it's worth the change, especially if it ended
up being more heavyweight, e.g.:

def foo[A:import Numeric](a:A) = ...

I have enough problems with things like the @specialized type
annotation cluttering up my type parameters that I end up doing things
like this (hat tip to Marc Millstone who showed it to me):

import scala.{specialized => spec}

The solution that is most attractive to me right now (which I first
saw in Scalaz) is to have:

object Numeric {
def apply[T](implicit ev:Numeric[T]) = ev
}

This means that in my method I can say:

def foo[A:Numeric](a:A) = {
...
Numeric[A].zero
}

I think the code here is pretty clear and even people who don't totally
understand the context bound/implicit params/etc will have a pretty
good idea of what is happening.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Tue, Jan 31, 2012 at 9:02 AM, Erik Osheim wrote:
> The solution that is most attractive to me right now (which I first
> saw in Scalaz) is to have:
>
>    object Numeric {
>      def apply[T](implicit ev:Numeric[T]) = ev
>    }

For the accuracy of the historical record only, and not to point out
that I'm cooler than retronym, even though I totally am, regardless of
whether he was even working on scalaz 2 years 9 months ago:

https://github.com/scala/scala/commit/abd87fb19d

(Also, it is easily possible many uses predate Ordering, it's just
that scalaz actually pioneers so much stuff, I'm not going to give
them THIS one.)

Chris Sachs
Joined: 2011-06-04,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate

On Jan 31, 9:02 am, Erik Osheim wrote:
> The solution that is most attractive to me right now (which I first
> saw in Scalaz) is to have:
>
>     object Numeric {
>       def apply[T](implicit ev:Numeric[T]) = ev
>     }

If you're willing to factor out Numeric#Ops into a separate NumericOps
class
then Numeric can extend (T => NumericOps[T]). Now when you use Numeric
as
a context bound for some type parameter you also get a view bound that
implicitly converts the parameterized type to the ops class--without
having
to first import the typeclass's namespace. Like so:

class NumericOps[T](lhs: T)(implicit T: Numeric[T]) {
def + (rhs: T): T = T.plus(lhs, rhs)
}

trait Numeric[T] extends(T => NumericOps[T]) {
def apply(lhs: T) = new NumericOps[T](lhs)(this)

def plus(x: T, y: T): T

def zero: T
}

If we combine the trick you mentioned,

object Numeric {
def apply[T](implicit T: Numeric[T]) = T
}

then we get this nice clean sum function with no imports:

def sum[T : Numeric](list: List[T]): T = list.foldLeft(Numeric[T].zero)
(_ + _)

d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: Re: An idea for reducing typeclass-related boilerplate

On Tue, Jan 31, 2012 at 10:01:26AM -0800, Christopher Sachs wrote:
> If you're willing to factor out Numeric#Ops into a separate NumericOps
> class
> then Numeric can extend (T => NumericOps[T]). Now when you use Numeric
> as
> a context bound for some type parameter you also get a view bound that
> implicitly converts the parameterized type to the ops class--without
> having
> to first import the typeclass's namespace. Like so:

Oh, that's really interesting!

In practice I do usually factor the Ops classes out (to interact well
with specialization). It would be really nice to get away from the
wildcard imports which I currently do to get the implicit infix
operators working.

I will need to test this to see how well it works with my existing code
(e.g. I don't want to lose performance) but thanks for showing this to
me.

d_m
Joined: 2010-11-11,
User offline. Last seen 35 weeks 2 days ago.
Re: An idea for reducing typeclass-related boilerplate

On Tue, Jan 31, 2012 at 09:11:18AM -0800, Paul Phillips wrote:
> For the accuracy of the historical record only, and not to point out
> that I'm cooler than retronym, even though I totally am, regardless of
> whether he was even working on scalaz 2 years 9 months ago:
>
> https://github.com/scala/scala/commit/abd87fb19d
>
> (Also, it is easily possible many uses predate Ordering, it's just
> that scalaz actually pioneers so much stuff, I'm not going to give
> them THIS one.)

Duly noted, sorry for the confused attribution. :)

Chris Sachs
Joined: 2011-06-04,
User offline. Last seen 42 years 45 weeks ago.
Re: An idea for reducing typeclass-related boilerplate

On Jan 31, 10:14 am, Erik Osheim wrote:
> On Tue, Jan 31, 2012 at 10:01:26AM -0800, Christopher Sachs wrote:
> In practice I do usually factor the Ops classes out (to interact well
> with specialization). It would be really nice to get away from the
> wildcard imports which I currently do to get the implicit infix
> operators working.
>
> I will need to test this to see how well it works with my existing code
> (e.g. I don't want to lose performance) but thanks for showing this to
> me.
>

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland