使用免费的 monad 和 GADT 进行漂亮的打印
Pretty printing with free monads and GADTs
考虑由以下 GADT 定义的表达式仿函数:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
import Control.Monad.Free
data ExprF :: * -> * where
Term :: Foo a -> (a -> r) -> ExprF r
instance Functor ExprF where
fmap f (Term d k) = Term d (f . k)
type Expr = Free ExprF
其中 Foo
定义为
data Foo :: * -> * where
Bar :: Int -> Foo Int
Baz :: Double -> Foo Double
instance Show a => Show (Foo a) where
show (Bar j) = show j
show (Baz j) = show j
ExprF
中的 (a -> r)
字段和(否则需要)限制性 GADT 构造函数的组合似乎使得编写漂亮的打印解释器变得不可能:
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty (k _)
类型孔是人们所期望的:
Found hole ‘_’ with type: a1
Where: ‘a1’ is a rigid type variable bound by
a pattern with constructor
Term :: forall r a. Foo a -> (a -> r) -> ExprF r,
in an equation for ‘pretty’
at Test.hs:23:15
Relevant bindings include
k :: a1 -> Free ExprF a (bound at Test.hs:23:22)
f :: Foo a1 (bound at Test.hs:23:20)
pretty :: Free ExprF a -> String (bound at Test.hs:22:1)
In the first argument of ‘k’, namely ‘_’
In the first argument of ‘pretty’, namely ‘(k _)’
In the second argument of ‘(++)’, namely ‘pretty (k _)’
似乎没有办法以其所需的类型为延续赋予一个值。该类型在 f
中编码,我正在使用所有 handle f
的其他解释器以某种方式提取适当类型的值。但是 String
表示的路径似乎被阻塞了。
这里有我遗漏的常见习语吗?如果确实有可能,如何漂亮地打印 Expr
的值?如果不可能,ExprF
的什么替代结构可能会捕获相同的结构,但也支持漂亮的打印机?
只是 f
上的模式匹配。如果这样做,k
的类型将被细化以匹配 Foo
:
中包含的类型
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty r where
r = case f of
Bar a -> k a
Baz a -> k a
您可能想分解出这种模式:
applyToFoo :: (a -> r) -> Foo a -> r
applyToFoo f (Bar a) = f a
applyToFoo f (Baz a) = f a
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty (applyToFoo k f)
嗯,这并非不可能。至少你可以在 f
:
上进行模式匹配
pretty :: (Show a) => Expr a -> String
pretty (Pure r) = show r
pretty (Free (Term f@(Bar x) k)) = "Term " ++ show f ++ pretty (k x)
pretty (Free (Term f@(Baz x) k)) = "Term " ++ show f ++ pretty (k x)
但这不是很令人满意,因为您已经在 Foo
的 show
实例中做到了。
因此,挑战在于适当地抽象。
考虑由以下 GADT 定义的表达式仿函数:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
import Control.Monad.Free
data ExprF :: * -> * where
Term :: Foo a -> (a -> r) -> ExprF r
instance Functor ExprF where
fmap f (Term d k) = Term d (f . k)
type Expr = Free ExprF
其中 Foo
定义为
data Foo :: * -> * where
Bar :: Int -> Foo Int
Baz :: Double -> Foo Double
instance Show a => Show (Foo a) where
show (Bar j) = show j
show (Baz j) = show j
ExprF
中的 (a -> r)
字段和(否则需要)限制性 GADT 构造函数的组合似乎使得编写漂亮的打印解释器变得不可能:
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty (k _)
类型孔是人们所期望的:
Found hole ‘_’ with type: a1
Where: ‘a1’ is a rigid type variable bound by
a pattern with constructor
Term :: forall r a. Foo a -> (a -> r) -> ExprF r,
in an equation for ‘pretty’
at Test.hs:23:15
Relevant bindings include
k :: a1 -> Free ExprF a (bound at Test.hs:23:22)
f :: Foo a1 (bound at Test.hs:23:20)
pretty :: Free ExprF a -> String (bound at Test.hs:22:1)
In the first argument of ‘k’, namely ‘_’
In the first argument of ‘pretty’, namely ‘(k _)’
In the second argument of ‘(++)’, namely ‘pretty (k _)’
似乎没有办法以其所需的类型为延续赋予一个值。该类型在 f
中编码,我正在使用所有 handle f
的其他解释器以某种方式提取适当类型的值。但是 String
表示的路径似乎被阻塞了。
这里有我遗漏的常见习语吗?如果确实有可能,如何漂亮地打印 Expr
的值?如果不可能,ExprF
的什么替代结构可能会捕获相同的结构,但也支持漂亮的打印机?
只是 f
上的模式匹配。如果这样做,k
的类型将被细化以匹配 Foo
:
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty r where
r = case f of
Bar a -> k a
Baz a -> k a
您可能想分解出这种模式:
applyToFoo :: (a -> r) -> Foo a -> r
applyToFoo f (Bar a) = f a
applyToFoo f (Baz a) = f a
pretty (Pure r) = show r
pretty (Free (Term f k)) = "Term " ++ show f ++ pretty (applyToFoo k f)
嗯,这并非不可能。至少你可以在 f
:
pretty :: (Show a) => Expr a -> String
pretty (Pure r) = show r
pretty (Free (Term f@(Bar x) k)) = "Term " ++ show f ++ pretty (k x)
pretty (Free (Term f@(Baz x) k)) = "Term " ++ show f ++ pretty (k x)
但这不是很令人满意,因为您已经在 Foo
的 show
实例中做到了。
因此,挑战在于适当地抽象。