在Scala中为嵌套类编写类型类实例

时间:2011-10-16 16:46:13

标签: scala typeclass scalaz

this recent Stack Overflow question中,作者想要将某种类型的解析器列表更改为返回该类型列表的解析器。我们可以想象使用Scalaz的sequence用于应用函子:

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)
}

这里我们列出三个返回整数列表的解析器,并将其转换为一个返回整数列表列表的解析器。不幸的是,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)
  }
}

这可以按预期工作: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)
}

我很清楚这应该是可能的,但我对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))
}

问题(正如didierd所指出的)是不清楚如何让implicit启动。所以这种方法确实有效,但你必须在你的语法中添加如下内容:

implicit val applicative = ParserApplicative(this)

此时,mixin方法显然更具吸引力。

(作为旁注:我希望能够简单地在上面写Applicative.applicative[g.Parser],但这会产生错误,说编译器找不到Pure[g.Parser]的隐含值 - 即使一个它正好紧挨着它。显然,对依赖方法类型的工作方式有不同之处。)


感谢retronym指出了一个完成我想要的技巧。我从his code中抽取了以下内容:

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)
  }

如果你在范围内有这个,你可以在任何扩展Parser的对象中获得Parsers的monad实例。这是一种作弊,因为演员,但仍然非常整洁。

2 个答案:

答案 0 :(得分:4)

我通常会在Parser

的mixins中为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
  }
}

然后你只需要在你的语法(Parsers)中混合它,并且由于Parser个实例通常只在Parsers内操作,因此混合的可能性很小之后,当语法完成并且你再也无法混合时。在你的例子中,你只是做

object parser extends Parsers with BindForParser

关于更一般的问题,是否可以“从外部”进行,最直接的方式可能是

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
}

但是这是不允许的,方法参数(此处grammar)不被视为稳定标识符,因此不允许grammar.Parser作为类型。但是可以使用选项-Xexperimental。但即使在那时,我也看不到隐含会在需要时如何发挥作用。我们想要的是一个隐式的Bind [grammar.Parser],并且使用语法参数,这不是我们所拥有的。

所以我的回答是它无法完成,但如果有人想出一些东西我就不会感到惊讶。

答案 1 :(得分:2)