Dans this recent Stack Overflow question, l'auteur souhaitait modifier une liste d'analyseurs de type quelconque dans un analyseur renvoyant des listes de ce type. Nous pouvons imaginer faire cela avec sequence
de Scalaz pour foncteurs applicatifs:Ecriture d'instances de classe de type pour des classes imbriquées dans 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)
}
Ici, nous prenons une liste de trois parseurs qui renvoient des listes d'entiers et de le transformer en un analyseur qui retourne des listes de listes d'entiers. Malheureusement Scalaz ne fournit pas une instance Applicative
pour Parser
, donc ce code ne compile pas, mais qui est facile à corriger:
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)
}
}
Cela fonctionne comme prévu: parser("1 2 3 4 5 6")
nous donne List(List(1), List(2, 3), List(4, 5, 6))
, par exemple.
(je sais que je pouvais donner un exemple Apply
, mais l'instance Bind
est plus concise.)
Ce serait bien de ne pas avoir à faire cela chaque fois que nous étendons Parsers
, mais je ne suis pas clair sur comment obtenir une instance Applicative
pour Parsers#Parser
plus généralement. L'approche naïve des cours suivant ne fonctionne pas, car nous avons besoin des instances de Parsers
être le même:
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)
}
Il est assez clair pour moi que cela devrait être possible, mais je ne suis pas assez à l'aise avec Scala système de type pour savoir comment s'y prendre. Y a-t-il quelque chose de simple qui me manque?
En réponse aux réponses ci-dessous: J'ai essayé la route -Ydependent-method-types
, et a obtenu jusqu'ici:
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))
}
Le problème (comme didierd souligne) est qu'il est difficile de savoir comment obtenir le implicit
. de botter donc cette approche fonctionne, mais vous devez ajouter quelque chose comme ce qui suit à votre grammaire:
implicit val applicative = ParserApplicative(this)
A ce moment, le mélange dans l'approche est évidemment beaucoup plus attrayant.
(Comme une note de côté: Je pensais pouvoir écrire simplement Applicative.applicative[g.Parser]
ci-dessus, mais cela donne une erreur indiquant que le compilateur ne peut pas trouver une valeur implicite de la Pure[g.Parser]
-Même si on est assis juste à côté. il est donc clair qu'il ya quelque chose de différent de la façon dont implicits travail pour les types de méthode à charge.)
Merci à retronym pour remarquer un truc qui accomplit ce que je veux ici. J'ai Abstraite les éléments suivants 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 vous avez cette portée, vous obtenez une instance monade pour Parser
dans tout objet extension Parsers
. C'est une sorte de tricherie à cause de la distribution, mais quand même assez chouette.
Ceci est intelligent et beaucoup plus agréable que ma version de types de méthode dépendante, mais je voudrais tout de même faire sans que 'implicite val M: Monad [Parser] = parserMonad (testParser)' . Pensez-vous que ce n'est pas possible? –
J'ai besoin de l'exemple de 'Parsers' pour appeler' success'. Vous pourriez rendre le 'parser' lui-même implicite pour le nourrir' parserMonad', mais cela ne semble pas être une bonne idée. – retronym
Si vous êtes prêt à admettre un 'asInstanceOf', vous pouvez réellement faire ceci: https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 – retronym