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.
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? –
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
Si está dispuesto a admitir un 'asInstanceOf', puede hacerlo: https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 – retronym