函子列表

Cri*_*cia 15 haskell

这可能适用于任何类型类,但我们可以更好地了解Functors.我不想构建这个列表.

l = [Just 1, [1,2,3], Nothing, Right 4]
Run Code Online (Sandbox Code Playgroud)

然后

map (fmap (+1)) l
Run Code Online (Sandbox Code Playgroud)

要得到

[Just 2, [2,3,4], Nothing, Right 5]
Run Code Online (Sandbox Code Playgroud)

我知道它们都是含有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)
}
Run Code Online (Sandbox Code Playgroud)

假设Maybe,ListEither实施IFunctor.自然JustNothing延伸Maybe,并RightLeft延伸Either.不满意这个问题更容易解决这些语言!

在Haskell应该更干净的方式:(

luq*_*qui 14

在Haskell中,不允许向下转换.你可以使用AnyFunctor,但问题是没有任何方法可以回到你知道的仿函数.如果你有一个AnyFunctor a,你所知道的只是你有f a一些f,所以你所能做的就是fmap(让你另一个AnyFunctor).因此,AnyFunctor a实际上相当于().

您可以添加结构以AnyFunctor使其更有用,稍后我们会看到一点.

Functor Coproducts

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

{-# 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
Run Code Online (Sandbox Code Playgroud)

当数据类型读取时,f :+: g是一个函数,其值可以是f ag a.

然后你可以使用,例如:

l :: [ (Maybe :+: []) Int ]
l = [ FLeft (Just 1), FRight [2,3,4], FLeft Nothing ]
Run Code Online (Sandbox Code Playgroud)

你可以通过模式匹配来观察:

getMaybe :: (Maybe :+: g) a -> Maybe a
getMaybe (FLeft v) = v
getMaybe (FRight _) = Nothing
Run Code Online (Sandbox Code Playgroud)

添加更多仿函数时会变得很难看:

l :: [ (Maybe :+: [] :+: Either Int) Int ]
l = [ FLeft (FLeft Nothing), FRight (Right 42) ]
-- Remember that we declared :+: left-associative.
Run Code Online (Sandbox Code Playgroud)

但我推荐它,只要你能处理丑陋,因为它跟踪类型中可能的仿函数列表,这是一个优点.(也许你最终需要的结构超出了Functor可以提供的范围;只要你可以提供它(:+:),你就处于良好的领域.)

您可以通过创建显式联合使术语更清晰,Ganesh建议:

data MyFunctors a
    = FMaybe (Maybe a)
    | FList [a]
    | FEitherInt (Either Int a)
    | ...
Run Code Online (Sandbox Code Playgroud)

但你必须Functor为它重新实施付出代价({-# LANGUAGE DeriveFunctor #-}可以提供帮助).我更喜欢忍受丑陋,并且在一个足够高的抽象层次上工作,它不会太丑陋(即一旦你开始编写FLeft (FLeft ...)它的时间来重构和概括).

如果你不想自己实现它,可以在comonad-transformers包中找到Coproduct(尽管这是很好的练习).其他常见的仿函数组合器位于变换器包中的Data.Functor.命名空间中.

向下倾斜的存在感

AnyFunctor也可以扩展到允许向下转换.必须通过将Typeable类添加到您想要转发的任何内容来明确启用向下转换.每个具体类型都是Typeable; type constructors是Typeable1(1参数)的实例; 但它不是免费的类型变量,所以你需要添加类约束.所以AnyFunctor解决方案变成:

{-# 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)
Run Code Online (Sandbox Code Playgroud)

这允许向下转换:

downcast :: (Typeable1 f, Typeable a) => AnyFunctor a -> Maybe (f a)
downcast (AnyFunctor f) = cast f
Run Code Online (Sandbox Code Playgroud)

这种解决方案实际上比我预期的更清洁,可能值得追求.

  • 你可以在约束中省略`Typeable a`.公开了`a`变量(与`f`不同),因此可以在需要的地方添加约束.那应该解决`fmap`问题.`downcast`然后需要一个`Typeable a`约束,这是没有问题的. (2认同)
  • 使用新的polykinded`Typeable`,你不再需要`Typeable1`. (2认同)

GS *_*ica 7

一种方法是使用存在:

{-# 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)
Run Code Online (Sandbox Code Playgroud)

您在问题中要求的输入列表是不可能的,因为它没有正确输入,所以有些包装AnyFunctor可能是必要的,但是你接近它.

您可以通过在AnyFunctor数据构造函数中包装每个值来创建输入列表:

[AnyFunctor (Just 1), AnyFunctor [1,2,3],
 AnyFunctor Nothing, AnyFunctor (Right 4)]
Run Code Online (Sandbox Code Playgroud)

请注意,当您使用fmap (+1)它时,最好使用显式类型签名1来避免数字重载的任何问题,例如fmap (+(1::Integer)).

目前的困难AnyFunctor v在于你实际上无法用它做多少 - 你甚至无法查看结果,因为它不是一个实例Show,更不用说提取一个值以供将来使用.

把它变成一个实例是有点棘手的Show.如果我们Show (f v)AnyFunctor数据构造函数添加一个约束,那么Functor实例就会停止工作,因为不能保证它会生成一个Show自己的实例.相反,我们需要使用一种"高阶"类型类Show1,如本答案所述:

{-# 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
Run Code Online (Sandbox Code Playgroud)

在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)]
Run Code Online (Sandbox Code Playgroud)

基本思想是表达类型构造函数的想法Nothing,[]或者Either a"保留" Show约束,使用Show1类来说明Show (f v)只要Show v可用就可用.

同样的技巧适用于其他类型类.例如,@ luqui的答案显示了如何使用Typeable已经具有内置Typeable1变体的类提取值.您添加的每个类型类都限制了您可以添加的内容AnyFunctor,但也意味着您可以使用它执行更多操作.