В недавнем вопросе переполнения стека автор хотел изменить список анализаторов некоторого типа на анализатор, который возвращает списки этого типа. Мы можем представить, что делаем это с помощью 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)
В этот момент миксиновый подход, очевидно, гораздо более привлекателен.
(Как примечание: я ожидал, что смогу просто написать 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)
}
Если у вас есть это в области видимости, вы получите экземпляр монады для Parser
в любом объекте, расширяющем Parsers
. Это своего рода обман из-за актерского состава, но все равно довольно аккуратный.