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

9
最近的Stack Overflow问题中,作者想要将某种类型的解析器列表更改为返回该类型列表的解析器。我们可以使用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没有为Parser提供Applicative实例,因此这段代码无法编译,但很容易修复:
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时都这样做会很好,但我不清楚如何更普遍地为Parsers#Parser获得一个Applicative实例。下面的天真方法当然行不通,因为我们需要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)

在那个时候,混合方法显然更具吸引力。

(顺便说一句:我希望能够简单地编写Applicative.applicative [g.Parser],但会出现错误,表示编译器找不到Pure [g.Parser] 的隐式值——尽管它就在旁边。因此,对于相关方法类型,隐式工作方式明显有所不同。)


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

如果你有这个范围,你可以在任何扩展Parsers对象中获得Parser的monad实例。这有点像作弊,因为涉及到转换,但仍然很棒。
2个回答

4

我通常在Parsers的mixin中为Parser添加隐式扩展。

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内部操作,因此在语法完成并且不能再混合内容时,不太可能需要使用mixin。在你的例子中,你只需要执行

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],但是带有语法参数,这并不是我们所拥有的。
所以我的答案是不能做到,但如果有人能想出什么东西,我也不会感到惊讶。

2

这个方法很聪明,比我依赖的方法类型版本好多了,但我仍然想不使用 implicit val M: Monad[Parser] = parserMonad(testParser)。你认为这不可能吗? - Travis Brown
1
我需要实例化 Parsers,才能调用 success。你可以将 parser 本身设为隐式以将其提供给 parserMonad,但这听起来并不是一个好主意。 - retronym
1
如果你愿意承认一个 asInstanceOf,你实际上可以完成这个任务:https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 - retronym

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接