haskell - Generar analizador optparse-aplicativo a partir de funtor alternativo libre
applicative free-monad (1)
Como se señala en los comentarios, tienes que manejar many
explícitamente. Enfoque copiado de Earley
:
#!/usr/bin/env stack
-- stack --resolver=lts-5.3 runghc --package optparse-applicative
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Applicative
import qualified Options.Applicative as CL
import qualified Options.Applicative.Help.Core as CL
data Alt f a where
Pure :: a -> Alt f a
Ap :: f a -> Alt f (a -> b) -> Alt f b
Alt :: [Alt f a] -> Alt f (a -> b) -> Alt f b
Many :: Alt f a -> Alt f ([a] -> b) -> Alt f b
instance Functor (Alt f) where
fmap f (Pure x) = Pure $ f x
fmap f (Ap x g) = Ap x $ fmap (f .) g
fmap f (Alt x g) = Alt x $ fmap (f .) g
fmap f (Many x g) = Many x $ fmap (f .) g
instance Applicative (Alt f) where
pure = Pure
Pure f <*> y = fmap f y
Ap x f <*> y = Ap x $ flip <$> f <*> y
Alt xs f <*> y = Alt xs $ flip <$> f <*> y
Many x f <*> y = Many x $ flip <$> f <*> y
instance Alternative (Alt f) where
empty = Alt [] (pure id)
a <|> b = Alt [a, b] (pure id)
many x = Many x (pure id)
-- | Given a natural transformation from @f@ to @g@, this gives a canonical monoidal natural transformation from @''Alt'' f@ to @g@.
runAlt :: forall f g a. Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
runAlt u = go where
go :: forall b. Alt f b -> g b
go (Pure x) = pure x
go (Ap x f) = flip id <$> u x <*> go f
go (Alt xs f) = flip id <$> foldr (<|>) empty (map go xs) <*> go f
go (Many x f) = flip id <$> many (go x) <*> go f
-- | A version of ''lift'' that can be used with just a ''Functor'' for @f@.
liftAlt :: (Functor f) => f a -> Alt f a
liftAlt x = Ap x (Pure id)
mkParser :: Foo a -> CL.Parser a
mkParser (Foo n r) = CL.option (CL.eitherReader $ Right . r) ( CL.long n CL.<> CL.help n )
data Foo x = Foo {
name :: String
, reader :: String -> x
}
instance Functor Foo where
fmap f (Foo n r) = Foo n $ f . r
type Bar a = Alt Foo a
foo :: String -> (String -> x) -> Bar x
foo n r = liftAlt $ Foo n r
myFoo :: Bar [String]
myFoo = many $ foo "Hello" (/_ -> "Hello")
clFoo :: CL.Parser [String]
clFoo = runAlt mkParser $ myFoo
main :: IO ()
main = do
print $ CL.cmdDesc clFoo
print $ CL.cmdDesc $ mkParser (Foo "Hello" $ /_ -> "Hello")
Considere las siguientes firmas de tipo:
data Foo x = Foo {
name :: String
, reader :: String -> x
}
instance Functor Foo where
fmap f (Foo n r) = Foo n $ f . r
Ahora muestro una transformación natural de Foo
al tipo Parser
optparse-applicative
:
import qualified Options.Applicative as CL
mkParser :: Foo a -> CL.Parser a
mkParser (Foo n _) = CL.option CL.disabled ( CL.long n )
(Está bien, es un poco inútil, pero servirá para la discusión).
Ahora tomo a Bar
como el funtor alternativo gratuito sobre Foo
:
type Bar a = Alt Foo a
Dado que este es un functor gratuito, debería ser capaz de mkParser
en una transformación natural de Bar
a Parser
:
foo :: String -> (String -> x) -> Bar x
foo n r = liftAlt $ Foo n r
myFoo :: Bar [String]
myFoo = many $ foo "Hello" (/_ -> "Hello")
clFoo :: CL.Parser [String]
clFoo = runAlt mkParser $ myFoo
Y de hecho, esto funciona y me devuelve un Parser
. Sin embargo, es bastante inútil, porque tratar de hacer mucho con esto da como resultado un bucle infinito. Por ejemplo, si intento describirlo:
CL.cmdDesc clFoo
> Chunk {unChunk =
Y se cuelga hasta que se interrumpa.
La razón de esto parece ser que los optparse-applicative
en sus definiciones de many
y some
: utiliza análisis monádico debajo de las cubiertas.
¿Estoy haciendo algo mal aquí? No veo cómo, dado esto, es posible construir un analizador de esta manera. ¿Algunas ideas?