Escrevendo instâncias de classe de tipo para classes aninhadas no Scala
Dentrosta recente pergunta sobre estouro de pilha, o autor queria alterar uma lista de analisadores de algum tipo para um analisador que retorne listas desse tipo. Podemos imaginar fazer isso com o @ Scalsequence
para funções de aplicação:
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)
}
Aqui pegamos uma lista de três analisadores que retornam listas de números inteiros e a transformamos em um analisador que retorna listas de listas de números inteiros. Infelizmente, Scalaz não fornece umApplicative
instância paraParser
, então esse código não é compilado, mas é fácil de corrigir:
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)
}
}
Isso funciona como esperado:parser("1 2 3 4 5 6")
nos dáList(List(1), List(2, 3), List(4, 5, 6))
, por exemplo
(Eu sei que eu poderia dar uApply
instância, mas oBind
instância é mais concisa.)
Seria bom não ter que fazer isso toda vez que estendermosParsers
, mas não sei como obter umApplicative
instância paraParsers#Parser
De forma geral. A seguinte abordagem ingênua, obviamente, não funciona, pois precisamos das instâncias deParsers
para ser o mesmo:
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)
}
É bastante claro para mim que isso deve ser possível, mas não estou confortável o suficiente com o sistema de tipos da Scala para saber como fazê-lo. Estou sentindo falta de algo simples?
Em resposta às respostas abaixo: Tentei o-Ydependent-method-types
rota, e chegou até aqui:
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))
}
O problema (como didierd aponta) é que não está claro como obter oimplicit
para entrar. Portanto, essa abordagem funciona, mas você deve adicionar algo como o seguinte à sua gramática:
implicit val applicative = ParserApplicative(this)
esse momento, a abordagem mixin é obviamente muito mais atraent
(Como observação: eu esperava poder escrever simplesmenteApplicative.applicative[g.Parser]
acima, mas isso indica que o compilador não pode encontrar um valor implícito para oPure[g.Parser]
- mesmo que alguém esteja sentado ao lado dele. Então, claramente, há algo diferente na maneira como os implícitos funcionam para tipos de métodos dependentes.)
Graças a retronym por apontar um truque que realiza o que quero aqui. Eu abstraí o seguinte dehis 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)
}
Se você tiver isso no escopo, obterá uma instância de mônad paraParser
em qualquer objeto que se estendaParsers
. É meio trapaceiro por causa do elenco, mas ainda bem lega