Написание экземпляров классов типов для вложенных классов в Scala - PullRequest
9 голосов
/ 16 октября 2011

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

Ответы [ 2 ]

4 голосов
/ 16 октября 2011

Я обычно добавляю неявное расширение к Parser в миксинах для 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], а с параметром грамматики это не то, что мы имеем.

Поэтому мой ответ будет , это невозможно сделать , но я не удивлюсь, если кто-нибудь что-нибудь придумает.

2 голосов
/ 16 октября 2011

Работа с зависимыми от пути типами довольно сложна. Вот один из способов:

https://github.com/retronym/scalaz7-experimental/commit/8bf1d2a090cf56d33e11c554e974ea3c82b7b37f

...