2011-10-16 1 views
9

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.

Répondre

4

-je ajouter habituellement l'extension implicite à Parser dans mixins pour 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 
    } 
} 

Il vous suffit de mélanger que dans votre grammaire (Parsers), et comme Parser cas sont habituellement manipulés seulement à l'intérieur Parsers, il n'y a pas beaucoup de chances que le mixin soit nécessaire après, quand la grammaire est faite et que vous ne pouvez plus mélanger quelque chose. Dans votre exemple, vous faites juste

object parser extends Parsers with BindForParser 

Sur la question plus générale, s'il est possible de le faire « de l'extérieur », la manière la plus directe serait probablement quelque chose comme

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 
} 

Mais ce n'est pas autorisé, un paramètre de méthode (ici grammar) n'est pas considéré comme un identifiant stable et donc grammar.Parser n'est pas autorisé en tant que type. Il est cependant possible avec l'option -Xexperimental. Mais même alors, je ne vois pas comment l'implicite se déclencherait en cas de besoin. Ce que nous voulons, c'est un Bind [grammar.Parser] implicite, et avec le paramètre grammar, ce n'est pas ce que nous avons.

Donc, ma réponse serait cela ne peut être fait, mais je ne serais pas surpris si quelqu'un pouvait trouver quelque chose.

2

Le traitement des types dépendants du chemin est assez compliqué. Voici une façon:

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

+0

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? –

+1

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

+1

Si vous êtes prêt à admettre un 'asInstanceOf', vous pouvez réellement faire ceci: https://github.com/retronym/scalaz7-experimental/commit/aa80e4792799a509c728eecff771ec74518720e7 – retronym

Questions connexes