函子列表

时间:2014-07-05 19:45:45

标签: haskell

这可能适用于任何类型类,但我们可以更好地了解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)
}

假设MaybeListEither实施IFunctor。自然JustNothing延伸MaybeRightLeft延伸Either。不满意这个问题更容易解决这些语言!!!

Haskell应该采用更清洁的方式:(

3 个答案:

答案 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以使其更有用,稍后我们会看到一点。

Functor Coproducts

但首先,我将分享我最终可能在真实程序中执行此操作的方式:使用仿函数组合器。

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

使用这种方法,您可以轻松地对结果进行模式匹配,而不会丢失有关各个元素类型,顺序等的信息。并且,您可以为FoldableTraversable设置类似的实例,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)

这可能尽可能简洁和普遍。