给出以下代数数据类型:
data Foo = Bar Int | Baz Bool deriving Show
我尝试实现一个Functor实例。
instance Functor Foo where
fmap f (Bar x) = Bar (f x)
fmap f (Baz x) = Baz (f x)
但我得到了这个编译时错误:
ghci> :l explore/FunctorExplore.hs
[1 of 1] Compiling Main ( explore/FunctorExplore.hs, interpreted )
explore/FunctorExplore.hs:3:18:
The first argument of ‘Functor’ should have kind ‘* -> *’,
but ‘Foo’ has kind ‘*’
In the instance declaration for ‘Functor Foo’
Failed, modules loaded: none.
所以,然后我添加了a
以尝试满足编译器:
data Foo a = Bar Int | Baz Bool deriving Show
但后来我得到了两个错误,据我所知,这基本上意味着(a -> b)
函数无法应用于Int
或Bool
。 a
是通用类型,而其他两个是特定类型。
ghci> :l explore/FunctorExplore.hs
[1 of 1] Compiling Main ( explore/FunctorExplore.hs, interpreted )
explore/FunctorExplore.hs:4:31:
Couldn't match expected type ‘Int’ with actual type ‘b’
‘b’ is a rigid type variable bound by
the type signature for fmap :: (a -> b) -> Foo a -> Foo b
at explore/FunctorExplore.hs:4:9
Relevant bindings include
f :: a -> b (bound at explore/FunctorExplore.hs:4:14)
fmap :: (a -> b) -> Foo a -> Foo b
(bound at explore/FunctorExplore.hs:4:9)
In the first argument of ‘Bar’, namely ‘(f x)’
In the expression: Bar (f x)
explore/FunctorExplore.hs:4:33:
Couldn't match expected type ‘a’ with actual type ‘Int’
‘a’ is a rigid type variable bound by
the type signature for fmap :: (a -> b) -> Foo a -> Foo b
at explore/FunctorExplore.hs:4:9
Relevant bindings include
f :: a -> b (bound at explore/FunctorExplore.hs:4:14)
fmap :: (a -> b) -> Foo a -> Foo b
(bound at explore/FunctorExplore.hs:4:9)
In the first argument of ‘f’, namely ‘x’
In the first argument of ‘Bar’, namely ‘(f x)’
Failed, modules loaded: none.
在这种情况下,我觉得无法创建Functor。还有更多我的问题,即我错过了什么?
答案 0 :(得分:7)
函数式编程的一项关键技能是在特定情况下掌握无所事事的正确方法。例如,当[]
意味着需要“一个简单的解决方案”时,出现经典错误,例如,给出[[]]
意味着“无解”作为递归搜索的基本情况。因此,幻像类型的Functor
个实例,即常量函子,也可用作较大模式的明显基本情况。
我可以提供用于构建类似容器的结构的通用工具包,如下所示:
newtype K a x = K a deriving Functor -- K for konstant
{- fmap _ (K a) = K a -}
newtype I x = I x deriving Functor -- I for identity
{- fmap k (I x) = I (k x) -}
newtype P f g x = P (f x, g x) deriving Functor -- P for product
{- will give (Functor f, Functor g) => Functor (P f g), such that
fmap k (P (fx, gx)) = P (fmap k fx, fmap k gx) -}
newtype S f g x = S (Either (f x) (g x)) -- S for sum
instance (Functor f, Functor g) => Functor (S f g) where
fmap k (S (Left fx)) = S (Left (fmap k fx))
fmap k (S (Right gx)) = S (Right (fmap k gx))
现在,任何递归数据结构都可以表示为顶级节点,充当子结构的容器。
data Data f = Node (f (Data f))
例如,如果我想在树叶上创建带数字的二叉树,我可以 写
type Tree = S (K Int) (P I I)
表示树的节点结构是带有Int
和无子树的叶子或带有一对子树的叉子。我需要K
来指出递归子结构的缺席。那么树的类型是Data Tree
。
这些事情的通常递归方案是
fold :: Functor f => (f t -> t) -> Data f -> t
fold k (Node fd) = k (fmap (fold k) fd)
我们不需要做任何工作来实例化树,因为Tree
已经是Functor
,因为它是从functorial组件构建的。对于fmap
来说,K Int
的琐事PatternSynonyms
相当于说当你到达一片叶子时递归就会停止。
当然,这些“编码”数据类型使您在通过模式匹配编写普通程序时更难看到您在做什么。这就是pattern Leaf i = Node (S (Left (K i)))
pattern Fork l r = Node (S (Right (P (I l, I r))))
扩展来拯救你的地方。你可以说
Node
恢复通常的界面。我建议不要使用外fold
,以适合Node
条pattern Leaf i = S (Left (K i))
pattern Fork l r = S (Right (P (I l, I r)))
add :: Data Tree -> Int
add = fold $ \ t -> case t of
Leaf i -> i
Fork x y -> x + y
的方式。
K
我只是为I
,P
,S
和K
开发了一些通用功能的表面,而这些功能可以推广到很多数据类型。 Void
。 Data.Void
案件总是微不足道的,但他们必须在那里。
类似的注意事项适用于{{1}}数据类型(在{{1}}中)。为什么我们懒得引入一个没有值得说的元素的数据类型呢?模拟较大方案的不可能案例。
答案 1 :(得分:4)
由于a
是幻像类型,因此您根本无法应用f
,但您可以这样做:
instance Functor Foo where
fmap _ (Bar x) = Bar x
fmap _ (Baz x) = Baz x