spire.syntax.Ops's EqOps

12 views
Skip to first unread message

Kevin Meredith

unread,
May 1, 2015, 4:29:04 PM5/1/15
to spire...@googlegroups.com
spire.syntax.Ops shows:

final class EqOps[A](lhs:A)(implicit ev:Eq[A]) {
  def ===(rhs:A): Boolean = macro Ops.binop[A, Boolean]
  def =!=(rhs:A): Boolean = macro Ops.binop[A, Boolean]
}

Where can I find the body of the `Ops.binop` method?

k@kevin /cygdrive/c/users/k/workspace/spire
$ grep -r "def binOp" * | grep -v target
$

Also, why is a macro used here?

I read Tom's Typelevel blog `An Intro to Generic Numeric Programming with Spire`, which shows the following:

trait Addable[A] {
  // Both arguments must be provided. Addable works with the type A, but
  // does not extend it.
  def plus(x: A, y: A): A
}

// This class adds the + operator to any type A that is Addable,
// by delegating to that Addable's `plus` method.
implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) {
  def +(rhs: A): A = ev.plus(lhs, rhs)
}

// We use a context bound to require that A has an Addable instance.
def add[A: Addable](x: A, y: A): A = x + y

I have not used typeclasses much, nor have I implemented this type of `implicit Ops` before. In general, I'd like to learn (1) where to find binOp's body and (2) why a macro is used here.

Thanks

Erik Osheim

unread,
May 1, 2015, 4:42:51 PM5/1/15
to Kevin Meredith, spire...@googlegroups.com
Hi Kevin,

This is somewhat hairy, so you are probably not the only person who
isn't clear on how this works. I'll try to trace through what is
happening for a simple code example:

The author writes:

import spire.algebra.Eq
import spire.implicits._
def qux[A: Eq](x: A, y: A): Boolean =
x === y

Leaving out the imports, this corresponds to a more verbose signature
which I'll write as:

def qux[A](x: A, y: A)(implicit ev: Eq[A]): Boolean =
x === y

The first thing the compiler will do is to look for a way to transform
x (an A value) to some type that has a === method. As you saw, it will
find EqOps and use that:

def qux[A](x: A, y: A)(implicit ev: Eq[A]): Boolean =
new EqOps(x)(ev).===(y)

Since the implementation is a macro, it looks it up in the
spire.macros.Ops object (which is located in the macros subproject, in
the Syntax.scala file). spire.macros.Ops is an instance of
machinist.Ops, which exists to transform the previous line into:

def qux[A](x: A, y: A)(implicit ev: Eq[A]): Boolean =
ev.eqv(x, y)

Machinist [1] observes that for a project like Spire, certain symbolic
operators (e.g. "+") are always mapped to particular method names
(e.g. "plus"). So instead of having to write tons of repetitive
boiler-plate (and be sure that there aren't any typos mapping "-" to
"plus" for example) we just use Machinist.

However, the strategy is a little bit opaque, as you've seen.

Hopefully this helps explain how the transformation works. You can
check out Machinist [1] to learn more.

-- Erik

[1] https://github.com/typelevel/machinist

On Fri, May 01, 2015 at 01:29:04PM -0700, Kevin Meredith wrote:
> spire.syntax.Ops
> <https://github.com/non/spire/blob/master/core/src/main/scala/spire/syntax/Ops.scala#L10-L12>
> shows:
>
> final class EqOps[A](lhs:A)(implicit ev:Eq[A]) {
> def ===(rhs:A): Boolean = macro Ops.binop[A, Boolean]
> def =!=(rhs:A): Boolean = macro Ops.binop[A, Boolean]
> }
>
> Where can I find the body of the `Ops.binop` method?
>
> k@kevin /cygdrive/c/users/k/workspace/spire
> $ grep -r "def binOp" * | grep -v target
> $
>
> Also, why is a macro used here?
>
> I read Tom's Typelevel blog `An Intro to Generic Numeric Programming with
> Spire`
> <http://typelevel.org/blog/2013/07/07/generic-numeric-programming.html>,
> which shows the following:
>
> trait Addable[A] {
> // Both arguments must be provided. Addable works with the type A, but
> // does not extend it.
> def plus(x: A, y: A): A}
> // This class adds the + operator to any type A that is Addable,// by delegating to that Addable's `plus` method.implicit class AddableOps[A](lhs: A)(implicit ev: Addable[A]) {
> def +(rhs: A): A = ev.plus(lhs, rhs)}
> // We use a context bound to require that A has an Addable instance.def add[A: Addable](x: A, y: A): A = x + y

Erik Osheim

unread,
May 1, 2015, 4:47:12 PM5/1/15
to Kevin Meredith, spire...@googlegroups.com
Also, I just realized you have been using the spire-math list.

We have been trying to move conversation over to the Typelevel list
at: type...@googlegroups.com.

In the future, can you post your questions there instead? Many of
these questions (and answers) are probably applicable or interesting
to other Typelevel projects and users too.

I need to update the Github project to make this change clear.

-- Erik
Reply all
Reply to author
Forward
0 new messages