1
votes

Edit: I updated the question to be more descriptive.

Note: I use the Scala 2.11 compiler, because that is the compiler version used by the LMS tutorial project.

I am porting a DSL written in Haskell to Scala. The DSL is an imperative language, so I used monads with do-notation, namely WriterT [Stmt] (State Label) a. I had trouble porting this to Scala, but worked around it by using the ReaderWriterState monad and just using Unit for the Reader component. I then went about searching for an alternative for the do-notation found in Haskell. For-comprehensions are supposed to be this alternative in Scala, but they are tailored towards sequences, I e.g. could not pattern match a tuple, it would insert a call to filter. So I started looking for alternatives and found multiple libraries: effectful, monadless, and each. I tried effectful first which does exactly what I wanted to achieve, I even prefer it over Haskell's do-notation, and it worked well with the ReaderWriterState monad from ScalaZ I had been using. In my DSL I have actions like Drop() (a case class) that I want to be able to use directly as a statement. I hoped to use implicits for this, but because the ! method of effectful (or monadless equivalent for that matter) is too general, I cannot get Scala to automatically convert my Action case classes to something of type Stmt (the ReaderWriterState that returns Unit).

So if not implicits, would there be a different way to achieve it?

As shown in Main.passes2, I did figure out a workaround that I would not mind using, but I am curious whether I stumbled upon is some limitation of the language, or is simply my lack of experience with Scala.

With Main.fails1 I will get the following error message: Implicit not found: scalaz.Unapply[scalaz.Monad, question.Label]. Unable to unapply type question.Label into atype constructor of kind M[_] that is classified by the type class scalaz.Monad. Check that the type class is defined by compiling implicitly[scalaz.Monad[type constructor]] and review the implicits in object Unapply, which only cover common type 'shapes.'

Which comes from ScalaZ its Unapply: https://github.com/scalaz/scalaz/blob/d2aba553e444f951adc847582226d617abae24da/core/src/main/scala/scalaz/Unapply.scala#L50

And with Main.fails2 I will just get: value ! is not a member of question.Label

I assume it is just a matter of writing the missing implicit definition, but I am not quite sure which one Scala wants me to write.

The most important bits of my build.sbt are the version:

scalaVersion := "2.11.2",

And the dependencies:

libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.2",
libraryDependencies += "org.scala-lang" % "scala-library" % "2.11.2",
libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.11.2",
libraryDependencies += "org.pelotom" %% "effectful" % "1.0.1",

Here is the relevant code, containing the things I tried and the code necessary to run those things:

package question

import scalaz._
import Scalaz._

import effectful._

object DSL {
  type GotoLabel = Int
  type Expr[A] = ReaderWriterState[Unit, List[Action], GotoLabel, A]
  type Stmt = Expr[Unit]

  def runStmt(stmt: Stmt, startLabel: GotoLabel): (Action, GotoLabel) = {
    val (actions, _, updatedLabel) = stmt.run(Unit, startLabel)
    val action = actions match {
      case List(action) => action
      case _ => Seq(actions)
    }
    (action, updatedLabel)
  }

  def runStmt(stmt: Stmt): Action = runStmt(stmt, 0)._1

  def getLabel(): Expr[GotoLabel] =
    ReaderWriterState((_, label) => (Nil, label, label))

  def setLabel(label: GotoLabel): Stmt =
    ReaderWriterState((_, _) => (Nil, Unit, label))

  implicit def actionStmt(action: Action): Stmt =
    ReaderWriterState((_, label) => (List(action), Unit, label))
}

import DSL._

final case class Label(label: String) extends Action
final case class Goto(label: String) extends Action
final case class Seq(seq: List[Action]) extends Action
sealed trait Action {
  def stmt(): Stmt = this
}

object Main {
  def freshLabel(): Expr[String] = effectfully {
    val label = getLabel.! + 1
    setLabel(label).!
    s"ants-$label"
  }

  def passes0() =
    freshLabel()
      .flatMap(before => Label(before))
      .flatMap(_ => freshLabel())
      .flatMap(after => Label(after));

  def passes1() = effectfully {
    unwrap(actionStmt(Label(unwrap(freshLabel()))))
    unwrap(actionStmt(Label(unwrap(freshLabel()))))
  }

  def fails1() = effectfully {
    unwrap(Label(unwrap(freshLabel())))
    unwrap(Label(unwrap(freshLabel())))
  }

  def pasess2() = effectfully {
    Label(freshLabel.!).stmt.!
    Label(freshLabel.!).stmt.!
  }

  def fails2() = effectfully {
    Label(freshLabel.!).!
    Label(freshLabel.!).!
  }

  def main(args: Array[String]): Unit = {
    runStmt(passes0())
  }
}
1

1 Answers

1
votes

Question quality

I want to start with complaining about the question quality. You provide almost no textual description of what you are trying to achieve and then just show us a wall of code but don't explicitly reference your dependencies. This is far from what could count as a Minimal, Complete, and Verifiable example. And typically you get much more chances to get some answers if you provide a clear problem that is easy to understand and reproduce.

Back to the business

When you write something like

unwrap(Label(unwrap(freshLabel())))

you ask too much from the Scala compiler. Particularly unwrap can unwrap only something that is wrapped into some Monad, but Label is not a monad. It is not just the fact that there is no Monad[Label] instance, it is the fact that it structurally doesn't fit that kills you. Simply speaking an instance of ScalaZ Unapply is an object that allows you to split an applied generic type MonadType[SpecificType] (or other FunctorType[SpecificType]) into an "unapplied"/"partially-applied" MonadType[_] and SpecificType even when (as in your case) MonadType[_] is actually something complicated like ReaderWriterState[Unit, List[Action], GotoLabel, _]. And so the error says that there is no known way to split Label into some MonadType[_] and SpecifictType. You probably expected that your implicit actionStmt would do the trick to auto-convert Label into a Statement but this step is too much for the Scala compiler because for it to work, it also implies splitting the composite type. Note that this conversion is far from obvious for the compiler as the unwrap is itself a generic method that can handle any Monad. Actually in some sense ScalaZ needs Unapply exactly because the compiler can't do such things automatically. Still if you help the compiler just a little bit by specifying generic type, it can do the rest of the work:

def fails1() = effectfully {
  // fails
  // unwrap(Label(unwrap(freshLabel()))) 
  // unwrap(Label(unwrap(freshLabel())))

  // works
  unwrap[Stmt](Label(unwrap(freshLabel())))
  unwrap[Stmt](Label(unwrap(freshLabel())))
}

There is also another possible solution but it is a quite dirty hack: you can roll out your custom Unapply to persuade the compiler that Label is actually the same as Expr[Unit] which can be split as Expr[_] and Unit:

  implicit def unapplyAction[AC <: Action](implicit TC0: Monad[Expr]): Unapply[Monad, AC] {
    type M[X] = Expr[X]
    type A = Unit
  } = new Unapply[Monad, AC] {
    override type M[X] = Expr[X]
    override type A = Unit

    override def TC: Monad[Expr] = TC0

    // This can't be implemented because Leibniz really witness only exactly the same types rather than some kind of isomorphism
    // Luckily effectful doesn't use leibniz implementation         
    override def leibniz: AC === Expr[Unit] = ???
  }

The obvious reason why this is a dirty hack is that Label is actually not the same as Expr[Unit] and you can see it by the fact that you can't implement leibniz in your Unapply. Anyway, if you import unapplyAction, even your original fails1 will compile and work because effectful doesn't use leibniz inside.

As for your fails2, I don't think you can make it work in any simple way. Probably the only way that you may try is to create another macro that would convert in-place call to ! on your action (or its implicit wrapper) into the effectful's ! on the action.stmt. This might work but I didn't try it.