这可能适用于任何类型类,但我们可以更好地了解Functors。我不想构建这个列表。
l = [Just 1, [1,2,3], Nothing, Right 4]
然后
map (fmap (+1)) l
获取
[Just 2, [2,3,4], Nothing, Right 5]
我知道它们都是含有Ints的Functors,所以它可能是有可能的。我怎么能这样做?
修改
事实证明这比看起来更加混乱。在Java或C#中,您将声明IFunctor
接口,然后只需编写
List<IFunctor> l = new List<IFunctor> () {
new Just (1),
new List<Int>() {1,2,3},
new Nothing<Int>(),
new Right (5)
}
假设Maybe
,List
和Either
实施IFunctor
。自然Just
和Nothing
延伸Maybe
,Right
和Left
延伸Either
。不满意这个问题更容易解决这些语言!!!
Haskell应该采用更清洁的方式:(
答案 0 :(得分:14)
在Haskell中,不允许向下转换。您可以使用AnyFunctor
,但问题在于无法再回到您知道的仿函数。当你有一个AnyFunctor a
时,你知道的是f a
f
fmap
,所以你只能做AnyFunctor
(给你另一个AnyFunctor a
()
})。因此,AnyFunctor
实际上等同于{-# LANGUAGE TypeOperators #-}
infixl 1 :+: -- declare this to be a left-associative operator
data (f :+: g) a = FLeft (f a) | FRight (g a)
instance (Functor f, Functor g) => Functor (f :+: g) where
-- left as an exercise
。
您可以将结构添加到f :+: g
以使其更有用,稍后我们会看到一点。
但首先,我将分享我最终可能在真实程序中执行此操作的方式:使用仿函数组合器。
f a
如数据类型所示,g a
是一个仿函数,其值可以是l :: [ (Maybe :+: []) Int ]
l = [ FLeft (Just 1), FRight [2,3,4], FLeft Nothing ]
或getMaybe :: (Maybe :+: g) a -> Maybe a
getMaybe (FLeft v) = v
getMaybe (FRight _) = Nothing
。
然后你可以使用,例如:
l :: [ (Maybe :+: [] :+: Either Int) Int ]
l = [ FLeft (FLeft Nothing), FRight (Right 42) ]
-- Remember that we declared :+: left-associative.
你可以通过模式匹配来观察:
Functor
添加更多仿函数时会变得很丑:
(:+:)
但我推荐它,只要你能处理丑陋,因为它跟踪类型中可能的仿函数列表,这是一个优点。 (也许你最终需要超出data MyFunctors a
= FMaybe (Maybe a)
| FList [a]
| FEitherInt (Either Int a)
| ...
所能提供的更多结构;只要你能为Functor
提供它,你就处于良好的领域。)
您可以通过创建显式联合使术语更清晰,正如Ganesh建议的那样:
{-# LANGUAGE DeriveFunctor #-}
但是你需要为它重新实施FLeft (FLeft ...)
而付费(Data.Functor.
可以提供帮助)。我更喜欢忍受丑陋,并且在一个足够高的抽象层次上工作,它不会太难看(即一旦你开始写AnyFunctor
,就需要重构和推广)。
如果你不想自己实现它,可以在comonad-transformers包中找到Coproduct(尽管这是很好的练习)。其他常见的仿函数组合器位于transformers包中的Typeable
命名空间中。
Typeable1
也可以扩展为允许向下转发。必须通过将Typeable
类添加到您想要转发的任何内容来明确启用向下转换。每个具体类型都是AnyFunctor
的实例; type构造函数是{-# LANGUAGE GADTs #-}
import Data.Typeable
data AnyFunctor a where
AnyFunctor :: (Functor f, Typeable1 f) => f a -> AnyFunctor a
instance Functor AnyFunctor where
fmap f (AnyFunctor v) = AnyFunctor (fmap f v)
(1参数)的实例;但它不是免费的类型变量,所以你需要添加类约束。所以downcast :: (Typeable1 f, Typeable a) => AnyFunctor a -> Maybe (f a)
downcast (AnyFunctor f) = cast f
解决方案变为:
{{1}}
允许向下转发:
{{1}}
这个解决方案实际上比我预期的要清洁,可能值得追求。
答案 1 :(得分:7)
一种方法是使用existentials:
{-# LANGUAGE GADTs #-}
data AnyFunctor v where
AnyFunctor :: Functor f => f v -> AnyFunctor v
instance Functor AnyFunctor where
fmap f (AnyFunctor fv) = AnyFunctor (fmap f fv)
您在问题中要求的输入列表是不可能的,因为它没有正确输入,所以当您接近它时,可能需要包括AnyFunctor
这样的包装。
您可以通过在AnyFunctor
数据构造函数中包装每个值来创建输入列表:
[AnyFunctor (Just 1), AnyFunctor [1,2,3],
AnyFunctor Nothing, AnyFunctor (Right 4)]
请注意,当您使用fmap (+1)
时,最好为1
使用显式类型签名,以避免数字重载出现任何问题,例如fmap (+(1::Integer))
。
AnyFunctor v
目前的难点在于你实际上无法用它做多少 - 你甚至无法查看结果,因为它不是Show
的实例,让单独提取值以供将来使用。
将它变成Show
的实例有点棘手。如果我们向Show (f v)
数据构造函数添加AnyFunctor
约束,则Functor
实例将停止工作,因为无法保证它将自己生成Show
的实例。相反,我们需要使用一种“高阶”类型类Show1
,如this answer中所述:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
data AnyFunctor v where
AnyFunctor :: (Show1 f, Functor f) => f v -> AnyFunctor v
instance Functor AnyFunctor where
fmap f (AnyFunctor fv) = AnyFunctor (fmap f fv)
data ShowDict a where
ShowDict :: Show a => ShowDict a
class Show1 a where
show1Dict :: ShowDict b -> ShowDict (a b)
instance Show v => Show (AnyFunctor v) where
show (AnyFunctor (v :: f v)) =
case show1Dict ShowDict :: ShowDict (f v) of
ShowDict -> "AnyFunctor (" ++ show v ++ ")"
instance Show1 [] where
show1Dict ShowDict = ShowDict
instance Show1 Maybe where
show1Dict ShowDict = ShowDict
instance Show a => Show1 (Either a) where
show1Dict ShowDict = ShowDict
在ghci中,这给出了以下内容(为了便于阅读,我打破了这些行):
*Main> map (fmap (+1)) [AnyFunctor (Just 1), AnyFunctor [1,2,3],
AnyFunctor Nothing, AnyFunctor (Right 4)]
[AnyFunctor (Just 2),AnyFunctor ([2,3,4]),
AnyFunctor (Nothing),AnyFunctor (Right 5)]
基本思想是使用{{1}来表达类似[{1}},Nothing
或[]
等类型构造函数“保留”Either a
约束的想法。 } class,表示只要Show
可用,就可以使用Show1
。
同样的技巧适用于其他类型类。例如,@ luqui的答案显示了如何使用Show (f v)
类提取值,该类已经内置Show v
变体。您添加的每个类型类限制了您可以放入Typeable
的内容,但也意味着您可以使用它执行更多操作。
答案 2 :(得分:4)
一种选择是为您的用例创建特定的数据类型,还有一个额外的好处就是拥有适当的名称。
另一种方法是创建一个专门的* -> *
元组:
newtype FTuple4 fa fb fc fd r = FTuple4 (fa r, fb r, fc r, fd r)
deriving (Eq, Ord, Show)
因此元组在值上是同质的,但在函子中是异构的。 然后你可以定义
instance (Functor fa, Functor fb, Functor fc, Functor fd) =>
Functor (FTuple4 fa fb fc fd) where
fmap f (FTuple4 (a, b, c, d)) =
FTuple4 (fmap f a, fmap f b, fmap f c, fmap f d)
和
main = let ft = FTuple4 (Just 1,
[1,2,3],
Nothing,
Right 4 :: Either String Int)
in print $ fmap (+ 1) ft
使用这种方法,您可以轻松地对结果进行模式匹配,而不会丢失有关各个元素类型,顺序等的信息。并且,您可以为Foldable
,Traversable
设置类似的实例,Applicative
等。
此外,您不需要自己实现Functor
实例,您可以使用GHC's deriving extensions,因此只需要编写所有实例就可以了
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
import Data.Foldable
import Data.Traversable
newtype FTuple4 fa fb fc fd r = FTuple4 (fa r, fb r, fc r, fd r)
deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
甚至可以使用Template Haskell进一步自动化任意长度。
这种方法的优点主要在于它只包含普通元组,因此如果需要,您可以在(,,,)
和FTuple4
之间无缝切换。
另一种没有自己的数据类型的替代方法是使用嵌套的functor products,因为你所描述的只是4个仿函数的产物。
import Data.Functor.Product
main = let ft = Pair (Just 1)
(Pair [1,2,3]
(Pair Nothing
(Right 4 :: Either String Int)
))
(Pair a (Pair b (Pair c d))) = fmap (+ 1) ft
in print (a, b, c, d)
这有点冗长,但您可以通过使用类型运算符创建自己的仿函数产品来做得更好:
{-# LANGUAGE TypeOperators, DeriveFunctor #-}
data (f :*: g) a = f a :*: g a
deriving (Eq, Ord, Show, Functor)
infixl 1 :*:
main = let a :*: b :*: c :*: d = fmap (+ 1) $ Just 1 :*:
[1,2,3] :*:
Nothing :*:
(Right 4 :: Either String Int)
in print (a, b, c, d)
这可能尽可能简洁和普遍。