2017-12-27 1313 views
1

编辑:我更新了问题以便更具描述性。斯卡拉隐式转换为有效宏内的一元值

注:我使用Scala 2.11编译器,因为这是LMS教程项目使用的编译器版本。

我正在将用Haskell编写的DSL移植到Scala。 DSL是一种命令式语言,所以我使用了单引号,即WriterT [Stmt] (State Label) a。我无法将其移植到Scala,但是通过使用ReaderWriterState monad和仅使用Unit来获得Reader组件,解决了这个问题。然后,我开始寻找Haskell中的符号替代方法。理解被认为是Scala中的这种替代方法,但它们是针对序列量身定制的,例如,无法匹配一个元组,它会插入一个调用filter。所以我开始寻找替代品并找到了多个库:effectful,monadlesseach。我首先尝试了effectful,这正是我想要达到的目标,我甚至更喜欢Haskell的do-notation,并且它与我一直使用的ScalaZReaderWriterState monad运行良好。在我的DSL中,我有类似Drop()(案例类)的操作,我希望能够直接作为语句使用。我希望用implicits这一点,但由于effectful!方法(或monadless等同为此事)太一般了,我不能让斯卡拉我Action case类自动转换为Stmt类型(即返回UnitReaderWriterState)的东西。

所以,如果没有牵连,会有不同的方式来实现它?

Main.passes2所示,我弄清楚了一个我不介意使用的解决方法,但我很好奇我是偶然遇到语言的一些限制,还是仅仅是我缺乏Scala的经验。

随着Main.fails1我会收到以下错误信息:隐未找到:scalaz.Unapply[scalaz.Monad, question.Label]。无法将类型question.Label无法应用到按类型scalaz.Monad分类的类型M[_]的类型构造函数中。检查是否通过编译implicitly[scalaz.Monad[type constructor]]来定义类型类别,并检查对象Unapply中的含义,该对象仅涵盖常见类型的“形状”。

它来自ScalaZ其Unapplyhttps://github.com/scalaz/scalaz/blob/d2aba553e444f951adc847582226d617abae24da/core/src/main/scala/scalaz/Unapply.scala#L50

而且随着Main.fails2我只会得到:值!不是question.Label

成员,我认为它是只写失踪隐含定义的问题,但我不确定Scala希望我写的是哪一个。

我的构建中最重要的部分。SBT是版本:

scalaVersion := "2.11.2", 

而且依赖:

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", 

下面是相关的代码,包含我试过必要的东西和代码运行的那些事:

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

问题质量

我想先抱怨质量问题。你几乎不提供你想要实现的内容的文本描述,然后向我们展示一段代码,但不明确引用你的依赖关系。这远远不能算作Minimal, Complete, and Verifiable example。通常,如果您提供易于理解和重现的明确问题,您将有更多机会获得一些答案。

回到业务

当你喜欢写东西

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

您从Scala编译器要求太多。特别是unwrap只能解包一些Monad,但Label不是一个monad。这不仅仅是因为没有Monad[Label]实例,事实上它在结构上不适合杀死你。简单来说,ScalaZ Unapply的一个实例是一个对象,允许您将应用的泛型类型MonadType[SpecificType](或其他FunctorType[SpecificType])拆分为“未应用”/“部分应用”MonadType[_]SpecificType,即使(在您的情况下)MonadType[_]实际上像ReaderWriterState[Unit, List[Action], GotoLabel, _]这样复杂的东西。所以错误表示没有已知的方法将Label分成MonadType[_]SpecifictType。你可能希望你的implicitactionStmt会做的伎俩来自动转换LabelStatement但是这一步是太多Scala编译器,因为它的工作,这也意味着分裂复合型。请注意,由于unwrap本身就是一种可处理任何Monad的通用方法,因此编译器的转换远不是明显的。实际上ScalaZ在某种意义上需要Unapply,因为编译器不能自动执行这些操作。不过,如果你帮助编译器只是一点点通过指定泛型类型,它可以做的工作休息:

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

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

也有另一种可能的解决方案,但它是一个非常肮脏的黑客:你可以滚了你定制Unapply说服编译器Label实际上是一样的Expr[Unit]可以被分割为Expr[_]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] = ??? 
    } 

最明显的原因,这是一个肮脏的黑客是Label实际上是不一样的Expr[Unit],你可以看到它,因为你不能在你的Unapply中实现leibniz。无论如何,如果您导入unapplyAction,即使您的原始fails1也会编译并工作,因为有效的内部不会使用leibniz

至于你的fails2,我认为你不能以任何简单的方式工作。可能您尝试的唯一方法是创建另一个宏,将您的action(或其隐式包装器)上的!就地调用转换为action.stmt上的effectful!。这可能工作,但我没有尝试。

+0

我觉得你对问题质量的第一个评论很不公平,考虑到我努力去做你所抱怨的事情。限制代码大小,但仍提供足够的空间来提供一些上下文。关于文本描述,我问了这个问题,我提到了一个特定的代码片段,在文本中也没有提及这一点。诚然,我展示了我试图做的不是文本上的,而是通过代码,但我希望更清楚。我怀疑我是否应该提供build.sbt,但希望我的进口清楚。 –

+0

那边,谢谢你彻底的回答!特别是不适用的定义有助于理解我应该如何去做。考虑到我的目标是减少语法噪音,我可能会尝试你的第三种方法,看看我是否可以制作自己的调用其他宏的宏。 –

+0

@OttidMes,关于这个问题的部分可能不公平,但我分享了我在尝试回答时的经验。而且我认为在我之前缺乏其他答案是一个迹象,表明你的问题远非完美。我仍然坚信,对你的意图的文本描述是任何非平凡问题的必须要求,因为我相信“[阅读代码比编写代码更难。](https://www.joelonsoftware .COM/2000/04/06 /东西 - 你 - 应该 - 永远-DO部分-I /)_”。 – SergGr