Tra*_*own 9 scala typeclass scalaz
在最近的Stack Overflow问题中,作者想要将某种类型的解析器列表更改为返回该类型列表的解析器.我们可以想象使用Scalaz sequencefor applicative functors做到这一点:
import scala.util.parsing.combinator._
import scalaz._
import Scalaz._
object parser extends RegexParsers {
val parsers = List(1, 2, 3).map(repN(_, """\d+""".r))
def apply(s: String) = parseAll(parsers.sequence, s)
}
Run Code Online (Sandbox Code Playgroud)
这里我们列出三个返回整数列表的解析器,并将其转换为一个返回整数列表列表的解析器.不幸的是,Scalaz没有提供Applicative实例Parser,所以这段代码不能编译,但这很容易解决:
import scala.util.parsing.combinator._
import scalaz._
import Scalaz._
object parser extends RegexParsers {
val parsers = List(1, 2, 3).map(repN(_, """\d+""".r))
def apply(s: String) = parseAll(parsers.sequence, s)
implicit def ParserPure: Pure[Parser] = new Pure[Parser] {
def pure[A](a: => A) = success(a)
}
implicit def ParserFunctor: Functor[Parser] = new Functor[Parser] {
def fmap[A, B](p: Parser[A], f: A => B) = p.map(f)
}
implicit def ParserBind: Bind[Parser] = new Bind[Parser] {
def bind[A, B](p: Parser[A], f: A => Parser[B]) = p.flatMap(f)
}
}
Run Code Online (Sandbox Code Playgroud)
这按预期工作:例如,parser("1 2 3 4 5 6")给我们List(List(1), List(2, 3), List(4, 5, 6)).
(我知道我可以给一个Apply实例,但Bind实例更简洁.)
每次我们扩展时都不必这样做很好Parsers,但我不清楚如何更普遍地获取Applicative实例Parsers#Parser.以下天真的方法当然不起作用,因为我们需要相同的实例Parsers:
implicit def ParserBind: Bind[Parsers#Parser] = new Bind[Parsers#Parser] {
def bind[A, B](p: Parsers#Parser[A], f: A => Parsers#Parser[B]) = p.flatMap(f)
}
Run Code Online (Sandbox Code Playgroud)
我很清楚这应该是可能的,但是我对Scala的类型系统不太满意,知道如何去做.有什么简单的东西我错过了吗?
回答下面的答案:我确实尝试-Ydependent-method-types了这条路线,并且做到了这一点:
implicit def ParserApplicative(g: Parsers): Applicative[g.Parser] = {
val f = new Functor[g.Parser] {
def fmap[A, B](parser: g.Parser[A], f: A => B) = parser.map(f)
}
val b = new Bind[g.Parser] {
def bind[A, B](p: g.Parser[A], f: A => g.Parser[B]) = p.flatMap(f)
}
val p = new Pure[g.Parser] {
def pure[A](a: => A) = g.success(a)
}
Applicative.applicative[g.Parser](p, FunctorBindApply[g.Parser](f, b))
}
Run Code Online (Sandbox Code Playgroud)
问题(正如didierd指出的那样)是不清楚如何implicit开始使用.所以这种方法确实有效,但你必须在你的语法中添加如下内容:
implicit val applicative = ParserApplicative(this)
Run Code Online (Sandbox Code Playgroud)
那时mixin方法显然更具吸引力.
(作为旁注:我希望能够简单地Applicative.applicative[g.Parser]在上面写,但是这给出了一个错误,说编译器找不到Pure[g.Parser]-even 的隐含值,尽管有一个坐在它旁边.所以显然有一些不同的东西.这种方式意味着依赖方法类型的工作.)
感谢retronym指出了一个完成我想要的技巧.我从他的代码中抽象出以下内容:
implicit def parserMonad[G <: Parsers with Singleton] =
new Monad[({ type L[T] = G#Parser[T] })#L] {
def pure[A](a: => A): G#Parser[A] = {
object dummy extends Parsers
dummy.success(a).asInstanceOf[G#Parser[A]]
}
def bind[A, B](p: G#Parser[A], f: (A) => G#Parser[B]): G#Parser[B] =
p.flatMap(f)
}
Run Code Online (Sandbox Code Playgroud)
如果你在范围内有这个,你可以Parser在任何扩展的对象中获得一个monad实例Parsers.这是一种作弊,因为演员,但仍然很整洁.
我通常会Parser在 mixin 中添加隐式扩展Parsers
trait BindForParser extends Parsers {
implicit def ParserBind = new Bind[Parser] {
def bind[A,B](p: Parser[A], f: A => Parser[B]) = p flatMap f
}
}
Run Code Online (Sandbox Code Playgroud)
然后你只需要在你的语法(Parsers)中混合它,并且由于Parser实例通常只在内部操作Parsers,当语法完成并且你不能再混合一些东西时,之后不太可能需要混合。例如,你只是这样做
object parser extends Parsers with BindForParser
Run Code Online (Sandbox Code Playgroud)
关于更普遍的问题,是否可以“从外部”做到这一点,最直接的方法可能是这样的
implicit def ParserBind(grammar: Parsers) = new Bind[grammar.Parser] {
def bind[A,B](p: grammar.Parser[A], f: A => grammar.Parser[B]) = p flatMap f
}
Run Code Online (Sandbox Code Playgroud)
但这是不允许的,方法参数(此处grammar)不被视为稳定标识符,因此grammar.Parser不允许作为类型。然而,通过 option 是可能的-Xexperimental。但即便如此,我还是看不出隐式在需要时会如何发挥作用。我们想要的是一个隐式的 Bind[grammar.Parser],而对于语法参数,这不是我们所拥有的。
所以我的答案是它无法完成,但如果有人能想出一些办法,我不会感到惊讶。