Skip to content
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

Support additional type args in @free and @tagless algebras #247

Open
raulraja opened this issue Apr 26, 2017 · 7 comments
Open

Support additional type args in @free and @tagless algebras #247

raulraja opened this issue Apr 26, 2017 · 7 comments
Labels
Projects

Comments

@raulraja
Copy link
Contributor

Currently no type args are supported in @free and @tagless algebras forcing to define Algebras nested in dependent types such as in the reader or state effects in the effects module. We should remove this limitation if possible to allow any number of arbitrary type args beside the implicit F[_] that they all have.

@diesalbla
Copy link
Contributor

diesalbla commented Apr 26, 2017

Here is a simple example in which I am seeing the problems:

@free trait Ann[A]{
  def bob(i: Int): FS[A]
  def karl(a: A): FS[Int]
}

The macro expands this @free to the following Scala code:

trait Ann[FF1[_], A] extends freestyle.EffectLike[FF1] {
  def bob(i: Int): FS[A]
  def karl(a: A): FS[Int]
}

object Ann {

  sealed abstract trait Op[AA1] 
  case class BobOP[A](i: Int) extends Op[A] 
  case class KarlOP[A](a: A) extends Op[Int]

  abstract trait Handler[MM1[_], A] extends FunctionK[Op, MM1] {
    protected[this] def bob(i: Int): MM1[A]
    protected[this] def karl(a: A): MM1[Int]
    override def apply[AA1](fa1: Op[AA1]): MM1[AA1] = fa1 match {
      case (l @ BobOP(_)) => bob(l.i)
      case (l @ KarlOP(_)) => karl(l.a)
    }
  }

  class To[LL1[_], A](ii1: Inject[Op, LL1]) extends Ann[LL1, A] {
    private[this] val toInj1 = FreeS.inject[Op, LL1](ii1)
    override def bob(i: Int): FS[A] = toInj1(BobOP(i))
    override def karl(a: A): FS[Int] = toInj1(KarlOP(a))
  }

  implicit def to[LL1[_], A](implicit ii1: Inject[Op, LL1]): To[LL1, A] = new To[LL1, A]()
  def apply[LL1[_], A](implicit ev1: Ann[LL1, A]): Ann[LL1, A] = ev1
}

The generated Scala code gives the following two errors:

  • The first error message it gives is at the case l@BobOp line, in which it gives a type mismatch error, in which it requires MM1[AA1], but it finds MM1[A] instead.
  • The second error is also a type mismatch at the case l@KarlOp line, but in this case the message is that, in the argument of the call to karl, it expects an A but instead it gets a l.a.type. In other words, it does not recognise that the field l.a is also of type A.

@diesalbla
Copy link
Contributor

diesalbla commented Apr 26, 2017

Fiddling with the example program above, I have found so far two directions, that could allow us to put the type parameters back:

  • To add the type parameters A to every method in the trait, and every delegate method in the handler, and every method in the To.
  • To make the object Ann into a class AnnImpl[A], since the class can have type parameters.

@diesalbla
Copy link
Contributor

diesalbla commented Apr 26, 2017

With the second first option mentioned, i.e. to add the type parameters from the @free trait to the elements in the companion object:

trait Ann[FF1[_]] extends freestyle.EffectLike[FF1] {
  def bob[A](i: Int): FS[A]
  def karl[A](a: A): FS[Int]
}

object Ann {

  sealed abstract trait Op[AA1] 
  case class BobOP[A](i: Int) extends Op[A]
  case class KarlOP[A](a: A) extends Op[Int]

  trait Handler[MM1[_]] extends FunctionK[Op, MM1] {
    protected[this] def bob[A](i: Int): MM1[A]
    protected[this] def karl[A](a: A): MM1[Int]

    override def apply[AA1](fa1: Op[AA1]): MM1[AA1] = fa1 match {
      case (l @ BobOP(_) ) => bob(l.i)
      case (l @ KarlOP(_)) => karl(l.a)
    }
  }

  class To[LL1[_]](implicit ii1: Inject[Op, LL1]) extends Ann[LL1] {
    private[this] val toInj1 = FreeS.inject[Op, LL1](ii1)
    override def bob[A](i: Int): FS[A] = toInj1(BobOP(i))
    override def karl[A](a: A): FS[Int] = toInj1(KarlOP(a))
  }

  implicit def to[LL1[_]](implicit ii1: Inject[Op, LL1]): To[LL1] = new To[LL1]()
  def apply[LL1[_], A](implicit ev1: Ann[LL1]): Ann[LL1] = ev1
}

@diesalbla
Copy link
Contributor

diesalbla commented Apr 26, 2017

The second first alternative, to add the type parameter to the common container (thus now has to be a class), it would then become:

trait Ann[FF1[_], A] extends freestyle.EffectLike[FF1] {
  def bob(i: Int): FS[A]
  def karl(a: A): FS[Int]
}

class AnnProvider[A] {

  sealed abstract trait Op[AA1] 
  case class BobOP(i: Int) extends Op[A]
  case class KarlOP(a: A) extends Op[Int]

  trait Handler[MM1[_]] extends FunctionK[Op, MM1] {
    protected[this] def bob(i: Int): MM1[A]
    protected[this] def karl(a: A): MM1[Int]

    override def apply[AA1](fa1: Op[AA1]): MM1[AA1] = fa1 match {
      case (l @ BobOP(_) ) => bob(l.i)
      case (l @ KarlOP(_)) => karl(l.a)
    }
  }

  class To[LL1[_]](implicit ii1: Inject[Op, LL1]) extends Ann[LL1, A] {
    private[this] val toInj1 = FreeS.inject[Op, LL1](ii1)
    override def bob(i: Int): FS[A] = toInj1(BobOP(i))
    override def karl(a: A): FS[Int] = toInj1(KarlOP(a))
  }

  implicit def to[LL1[_]](implicit ii1: Inject[Op, LL1]): To[LL1] = new To[LL1]()

  def apply[LL1[_]](implicit ev1: Ann[LL1, A]): Ann[LL1, A] = ev1
}

object Ann {
  def apply[A] = new AnnProvider[A]
}

@diesalbla
Copy link
Contributor

@raulraja ¿Have you tried before any of these alternatives? ¿Do you have any preference?

The first option may have the problem that, since each method now becomes parametrised, it may be necessary to add type parameters to every method call. Another possible problem is that the Handler and the To classes have each a separate type parameter, so they may not be aligned.

The second option may have the problem of introducing this new class. Also, the syntax for using parametrised type-classes would not be too different from the current one. As in the current effects, we would still need to write something like

val p = Ann[Double]
def program: p[p.Op]

Which is to say, we would still need to use a middle declaration for accessing the To or Handler.

@raulraja
Copy link
Contributor Author

I think both approaches affect so much to the final syntax and affect the user experience that we should settle this for now until we have a better way to deal with this problem. The current workaround is to define a wrapper class as in the state and reader effects and that is a much more sane approach for users than having to pass type args on each call or referring to explicit instances for To or Handler. I'll leave this issue as open untagged to revisit down the road. We have bigger issues right now such as the scala meta support to get proper syntax highlighting working on IntelliJ.

@raulraja raulraja removed this from the Release 0.2.0 milestone Apr 26, 2017
@anamariamv anamariamv added this to Backlog in freestyle May 8, 2017
@diesalbla
Copy link
Contributor

Update: in a recent PR and issue, we now support type parameters in the @tagless annotation without the stacksafe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
freestyle
Backlog
Development

No branches or pull requests

3 participants