2011-10-16 8 views
9

En this recent Stack Overflow question, el autor quería cambiar una lista de analizadores de algún tipo en un analizador que devuelve listas de ese tipo. Podemos imaginar haciendo esto con Scalaz de sequence de funtores aplicativos:Escribiendo instancias de clase de tipo para clases anidadas en Scala

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

Aquí damos una lista de los tres programas de análisis que devuelven listas de números enteros y convertirlo en un programa de análisis que devuelve una lista de listas de números enteros. Desafortunadamente Scalaz no proporciona una instancia Applicative para Parser, por lo que este código no se compila, pero eso es fácil de solucionar:

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

Esto funciona como se esperaba: parser("1 2 3 4 5 6") nos da List(List(1), List(2, 3), List(4, 5, 6)), por ejemplo.

(sé que sólo podía dar una instancia Apply, pero la instancia Bind es más conciso.)

que sería bueno no tener que hacer esto cada vez que extendemos Parsers, pero no me queda claro sobre cómo obtener una instancia Applicative para Parsers#Parser de manera más general. El siguiente enfoque ingenuo, por supuesto, no funciona, ya que necesitamos los casos de Parsers a ser la misma:

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

Es bastante claro que esto debería ser posible, pero no estoy lo suficientemente cómodo con Scala de escriba el sistema para saber cómo hacerlo. ¿Hay algo simple que me estoy perdiendo?


En respuesta a las respuestas a continuación: Yo probé la ruta -Ydependent-method-types, y llegado hasta aquí:

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

El problema (como didierd señala) es que no está claro cómo obtener el implicit . en arrancar lo que este enfoque funciona, pero hay que añadir algo como lo siguiente a la gramática:

implicit val applicative = ParserApplicative(this) 

en ese momento la mezcla en enfoque es obviamente mucho más atractivo.

(Como nota al margen: Me espera que sea capaz de escribir simplemente Applicative.applicative[g.Parser] anterior, pero que da un error diciendo que el compilador no puede encontrar un valor implícito para el Pure[g.Parser] -aun cuando uno está sentado al lado de ella. por lo tanto es evidente que hay algo diferente en la forma implícitos de trabajo para este tipo de método dependientes.)


Gracias a retronym para señalar un truco que logra lo que quiero aquí. He abstraje lo siguiente de 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) 
    } 

Si usted tiene este alcance, se obtiene una instancia mónada para Parser en los objetos que se Parsers. Es una especie de trampa por el elenco, pero sigue siendo bastante ordenada.

Respuesta

4

Yo suelo añadir la extensión implícita a Parser en mixins para 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 
    } 
} 

A continuación, sólo tiene que mezclar que, en su gramática (Parsers), y como Parser casos suelen ser manipulados sólo dentro Parsers, no hay hay muchas posibilidades de que la mixina se necesite después, cuando la gramática esté lista y ya no se pueda mezclar algo. En su ejemplo, solo hace

object parser extends Parsers with BindForParser 

Sobre la cuestión más general, si es posible hacerlo "desde el exterior", la forma más directa sería probablemente algo así como

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 
} 

Pero esto no está permitido, un parámetro de método (aquí grammar) no se considera un identificador estable y, por lo tanto, grammar.Parser no está permitido como tipo. Sin embargo, es posible con la opción -Xexperimental. Pero incluso entonces, no veo cómo funcionaría lo implícito cuando sea necesario. Lo que queremos es un Bind [grammar.Parser] implícito, y con el parámetro de gramática esto no es lo que tenemos.

Así que mi respuesta sería no se puede hacer, pero no me sorprendería que alguien pudiera encontrar algo.

2

Tratar con los tipos dependientes de la ruta es bastante complicado. Aquí está una manera:

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

+0

Este es inteligente y mucho mejor que mi dependiente versión tipos de métodos, pero todavía me gustaría hacer sin que 'implícita Val M: mónada [Analizador] = parserMonad (testParser)' . ¿Crees que eso no es posible? –

+1

Necesito una instancia de 'Parsers' para llamar' success'. Puedes hacer que el 'analizador' esté implícito para alimentarlo' parserMonad', pero eso no suena como una buena idea. – retronym

+1

Si está dispuesto a admitir un 'asInstanceOf', puede hacerlo: https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 – retronym

Cuestiones relacionadas