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

Unambiguous ops (syntax extensions) for scalaz 7. #1479

Merged
merged 2 commits into from Oct 11, 2017

Conversation

Projects
None yet
2 participants
@TomasMikula
Member

TomasMikula commented Oct 8, 2017

The goal is to allow the user to structure imports so that ambiguous syntactic extensions are avoided.

Example:

import scalaz.syntax.monad._

def foo[F[_]: Monad: Traverse, A, B](fa: F[A], f: A => B): F[B] =
  fa map f // no ambiguous implicits, even though there are two implicit Functor instances

Typeclass encoding is left untouched, thus ambiguous typeclass instances are not avoided, just ambiguous ops. As such, this approach is less ambitious than scalaz8's, but maybe could be considered for scalaz7? On one hand, it can have great benefits for the client code, but on the other hand it increases the complexity of syntax extensions. I'm myself not yet 100% convinced that this is the better tradeoff.

The idea is that the import

import scalaz.syntax.traverse0._

imports syntax for Traverse, but doesn't include syntax for Functor or Foldable, whereas the (usual) import

import scalaz.syntax.traverse._

also brings in syntax for Functor and Foldable.
There is one change, though: for this import to provide Functor (or Foldable) syntax, there needs to be a Traverse instance in scope; Functor (or Foldable) instance will not suffice, even when only Functor (or Foldable) operations are performed. (This is why there is no ambiguity in the example at the top - for Functor ops imported via scalaz.syntax.monad._, a Monad instance is required, and there is only one.)

This allows us to import

import scalaz.syntax.monad._
import scalaz.syntax.traverse0._

and then use both Monad and Traverse syntax extensions without ambiguity.

For some examples, see the diff of SyntaxUsage.scala.

Implementation comments

Where previously we had

trait ToTraverseOps extends ToFunctorOps with ToFoldableOps

we now have (to be refined shortly)

trait ToTraverseOps0

trait ToTraverseOps extends ToTraverseOps0 with ToFunctorOps with ToFoldableOps

That is, we provide ToTraverseOps0 which doesn't inherit Functor and Foldable ops.

In order for ToTraverseOps's inherited ToFunctorOps and ToFoldableOps to actually require a Traverse instance even for Functor or Foldable operations, we pass the required typeclass as a type argument. Thus we have

trait ToTraverseOps0[TC[F[_]] <: Traverse[F]]

trait ToTraverseOps[TC[F[_]] <: Traverse[F]] extends ToTraverseOps0[TC] with ToFunctorOps[TC] with ToFoldableOps[TC]

and then in ToFunctorOps, we require an instance of TC (e.g. Traverse) instead of Functor:

trait ToFunctorOps0[TC[F[_]] <: Functor[F]] {
  implicit def ToFunctorOps[F[_], A](v: F[A])(implicit F0: TC[F]) = ???
}

There is one more point worth mentioning.
Consider syntax for MonadTell extending syntax for Monad, using the above scheme:

trait ToMonadOps[TC[F[_]] <: Monad[F]]

trait ToMonadTellOps[TC[F[_], S] <: MonadTell[F, S]] extends ToMonadOps[???]

The kinds of type parameters of ToMonadOps and ToMonadTellOps do not match. Therefore, we cannot just pass the TC type parameter from ToMonadTellOps to ToMonadOps. The solution is to use an existential

trait ToMonadTellOps[TC[F[_], S] <: MonadTell[F, S]] extends ToMonadOps[λ[F[_] => TC[F, S] forSome { type S }]]

and, despite many problems with existentials in Scala, the compiler does consider MonadTell[F, S] forSome { type S } to be a subtype of Monad[F].

Unambiguous ops (syntax extensions).
The goal is to allow the user to structure imports so that ambiguous
syntactic extensions are avoided.

The idea is that the import

  import scalaz.syntax.traverse0._

imports syntax for Traverse, but doesn't include syntax for Functor and Foldable.
The usual import

  import scalaz.syntax.traverse._

also brings in the syntax for Functor and Foldable.
There is one change, though: for this import to provide Functor (or Foldable)
syntax, there needs to be a Traverse instance in scope; Functor (or Foldable)
instance will not suffice, even when only Functor (or Foldable) operations
are performed.

This allows us to import

  import scalaz.syntax.monad._
  import scalaz.syntax.traverse0._

and then use both Monad and Traverse syntax extensions without ambiguity.

For some examples, see the diff of SyntaxUsage.scala.

IMPLEMENTATION COMMENTS:

Where previously we had

  trait ToTraverseOps extends ToFunctorOps with ToFoldableOps

we now have (to be refined shortly)

  trait ToTraverseOps0

  trait ToTraverseOps extends ToTraverseOps0 with ToFunctorOps with ToFoldableOps

That is, we provide ToTraverseOps0 which doesn't inherit Functor and Foldable ops.

In order for the inherited ToFunctorOps and ToFoldableOps to actually require
a Traverse instance to work, we pass the required typeclass as a type argument.
Thus we have

  trait ToTraverseOps0[TC[F[_]] <: Traverse[F]]

  trait ToTraverseOps[TC[F[_]] <: Traverse[F]] extends ToTraverseOps0[TC] with ToFunctorOps[TC] with ToFoldableOps[TC]

and then in ToFunctorOps, we require an instance of TC instead of Functor:

  trait ToFunctorOps0[TC[F[_]] <: Functor[F]] {
    implicit def ToFunctorOps[F[_], A](v: F[A])(implicit F0: TC[F]) = ???
  }

There is one more point worth mentioning.
Consider syntax for MonadTell extending syntax for Monad, using this scheme:

  trait ToMonadOps[TC[F[_]] <: Monad[F]]

  trait ToMonadTellOps[TC[F[_], S] <: MonadTell[F, S]] extends ToMonadOps[???]

The kinds of type arguments of ToMonadOps and ToMonadTellOps don't match.
Therefore, we cannot just pass the `TC` type parameter from ToMonadTellOps
to ToMonadOps. The solution is to use an existential

  trait ToMonadTellOps[TC[F[_], S] <: MonadTell[F, S]] extends ToMonadOps[λ[F[_] => TC[F, S] forSome { type S }]]

and, despite many problems with existentials in Scala, the compiler does
consider MonadTell[F, S] forSome { type S } to be a subtype of Monad[F].
@TomasMikula

This comment has been minimized.

Show comment
Hide comment
@TomasMikula

TomasMikula Oct 8, 2017

Member

Might be of interest to @adelbertc.

Member

TomasMikula commented Oct 8, 2017

Might be of interest to @adelbertc.

@TomasMikula

This comment has been minimized.

Show comment
Hide comment
@TomasMikula

TomasMikula Oct 8, 2017

Member

Failing tasks are

coreJVM/*:checkGenTypeClasses
effectJVM/*:checkGenTypeClasses

which I don't understand.

Member

TomasMikula commented Oct 8, 2017

Failing tasks are

coreJVM/*:checkGenTypeClasses
effectJVM/*:checkGenTypeClasses

which I don't understand.

@xuwei-k

This comment has been minimized.

Show comment
Hide comment
@xuwei-k

xuwei-k Oct 8, 2017

Member

Failing tasks are

coreJVM/*:checkGenTypeClasses
effectJVM/*:checkGenTypeClasses

which I don't understand.

${TypeClass}.scala and ${TypeClass}Syntax.scala are partially auto generated from project/GenTypeClasses.scala.
The checkGenTypeClasses task validate consistency of GenTypeClass.scala and generated sources. We should update GenTypeClass.scala.

xuwei-k@4b214ff

Member

xuwei-k commented Oct 8, 2017

Failing tasks are

coreJVM/*:checkGenTypeClasses
effectJVM/*:checkGenTypeClasses

which I don't understand.

${TypeClass}.scala and ${TypeClass}Syntax.scala are partially auto generated from project/GenTypeClasses.scala.
The checkGenTypeClasses task validate consistency of GenTypeClass.scala and generated sources. We should update GenTypeClass.scala.

xuwei-k@4b214ff

@TomasMikula

This comment has been minimized.

Show comment
Hide comment
@TomasMikula

TomasMikula Oct 8, 2017

Member

Thanks, Kenji!

Member

TomasMikula commented Oct 8, 2017

Thanks, Kenji!

@xuwei-k xuwei-k merged commit d6d8f6d into scalaz:series/7.3.x Oct 11, 2017

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

@TomasMikula TomasMikula deleted the TomasMikula:unambiguous-ops branch Oct 11, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment