【问题标题】:apply function on Maybe types?在 Maybe 类型上应用函数?
【发布时间】:2017-11-26 17:20:57
【问题描述】:

Haskell 新手,我不知道如何将函数 (a -> b) 应用到列表 [Maybe a] 并获取 [Maybe b]

maybeX:: (a -> b) -> [Maybe a] -> [Maybe b]

该函数应该与 map 执行完全相同的操作,将函数 f 应用于 Maybe 语句列表,如果它 Just 它返回我一个 f Just 并且如果它是一个 Nothing 就一个 Nothing。 像以下示例一样,我想在以下列表的每个元素上添加 +5:

[Just 1,Just 2,Nothing,Just 3]

得到

[Just 6,Just 7,Nothing,Just 8]

真的想弄清楚这一点,我尝试了很多,但似乎总是我不知道这种 Maybe 数据类型的工作方式。 谢谢你的帮助!

【问题讨论】:

    标签: function haskell maybe


    【解决方案1】:

    让我们首先定义如何对单个 Maybe 执行操作,然后将其扩展到整个列表。

    mapMaybe :: (a -> b) -> Maybe a -> Maybe b
    mapMaybe f Nothing = Nothing
    mapMaybe f (Just x) = Just (f x)
    

    如果Maybe 包含一个值,mapMaybe 将对其应用f,如果它不包含一个值,则我们只返回一个空的Maybe。

    但我们有一个Maybes 的列表,因此我们需要将mapMaybe 应用于每个人。

    mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
    mapMaybes f ms = [mapMaybe f m | m <- ms]
    

    在这里,我使用列表推导来评估 mapMaybe f m 为 ms 中的每个 m。


    现在介绍更高级的技术。 Functor 类型类捕获了将函数应用于容器中的每个值的模式。

    class Functor f where
        fmap :: (a -> b) -> f a -> f b
    

    f 类型是 Functor,如果您可以编写一个函数,该函数将函数从 a 转换为 b,并将该函数应用于充满 as 的 f 以获得f 充满了bs。比如[]和Maybe都是Functors:

    instance Functor Maybe where
        fmap f Nothing = Nothing
        fmap f (Just x) = Just (f x)
    
    instance Functor [] where
        fmap f xs = [f x | x <- xs]
    

    Maybe 的fmap 版本与我上面写的mapMaybe 相同,[] 的实现使用列表推导将f 应用于列表中的每个元素。

    现在,要写mapMaybes :: (a -&gt; b) -&gt; [Maybe a] -&gt; [Maybe b],你需要使用[]的fmap的版本对列表中的每个项目进行操作,然后使用Maybe的版本对单个Maybes进行操作fmap.

    mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
    mapMaybes f ms = fmap (fmap f) ms
    -- or:
    mapMaybes = fmap . fmap
    

    请注意,我们实际上在这里调用了两个不同的fmap 实现。外层是fmap :: (Maybe a -&gt; Maybe b) -&gt; [Maybe a] -&gt; [Maybe b],它调度到[] 的Functor 实例。内层是(a -&gt; b) -&gt; Maybe a -&gt; Maybe b。


    还有一个附录 - 虽然这很深奥,所以如果你不明白这里的所有内容,请不要担心。我只是想让你尝尝我认为很酷的东西。

    这种“fmaps 链”样式 (fmap . fmap . fmap ...) 是一种非常常见的用于向下钻取结构的多层的技巧。每个fmap 都有一个(a -&gt; b) -&gt; (f a -&gt; f b) 类型,所以当你用(.) 组合它们时,你正在构建一个高阶函数。

    fmap        :: Functor g              =>             (f a -> f b) -> (g (f a) -> g (f b))
    fmap        :: Functor f              => (a -> b) -> (f a -> f b)
    -- so...
    fmap . fmap :: (Functor f, Functor g) => (a -> b)          ->         g (f a) -> g (f b)
    

    因此,如果您有一个仿函数(仿函数...),那么 n fmaps 将允许您将元素映射到结构体。 Conal Elliot 称这种风格为"semantic editor combinators"。

    这个技巧也适用于traverse :: (Traversable t, Applicative f) =&gt; (a -&gt; f b) -&gt; (t a -&gt; f (t b)),这是一种“有效的fmap”。

    traverse            :: (...) =>               (t a -> f (t b)) -> (s (t a) -> f (s (t b)))
    traverse            :: (...) => (a -> f b) -> (t a -> f (t b))
    -- so...
    traverse . traverse :: (...) => (a -> f b)            ->           s (t a) -> f (s (t b))
    

    (我省略了=&gt; 之前的位,因为我用完了水平空间。)因此,如果您有可遍历的可遍历(可遍历...),则可以对级别的元素执行有效的计算n 只需写traverse n 次。像这样组合遍历是 lens 库背后的基本思想。

    【讨论】:

    • 或许值得一提的是mapMaybe已经存在并且被称为fmap,而mapMaybes可以通过将list-targeted fmap与maybe-targeted fmap组合得到,即mapMaybes = fmap . fmap。头脑=炸毁。 :-)
    【解决方案2】:

    你可能已经知道了

    map :: (a -> b) -> [a] -> [b]
    

    ...这实际上只是

    的一个特例
    fmap :: Functor f => (a -> b) -> f a -> f b
    

    后者既适用于列表(其行为与map 完全相同)也适用于Maybe,因为两者都是函子。即,以下两个签名都是有效的专业化:

    fmap :: (a -> b) -> [a] -> [b]
    fmap :: (a -> b) -> Maybe a -> Maybe b
    

    现在,您的用例看起来很相似,但不幸的是[Maybe a] 本身并不是f a 的特化,而是具有f (g a) 的形式。但请注意,我们可以将变量α 替换为g a,即Maybe a,然后我们可以使用

    fmap :: (α -> β) -> [α] -> [β]
    

    即

    fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]
    

    这看起来更像你想要的签名!但是我们仍然需要一个带有签名Maybe a -&gt; Maybe b 的函数。好吧,看上面......我重复一遍:

    fmap :: (a -> b) -> Maybe a -> Maybe b
    

    这可以部分应用,即当你有一个函数φ :: a -&gt; b时,你可以很容易地得到函数fmap φ :: Maybe a -&gt; Maybe b。这解决了你的问题:

    maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
    maybeX φ = fmap (fmap φ)
    

    ...或者,如果你想获得额外的花哨,

    maybeX = fmap . fmap
    

    【讨论】:

      【解决方案3】:

      [注意:这假设您熟悉函子。]

      另一种方法是使用Data.Functor.Compose 中定义的类型级组合。

      >>> getCompose (fmap (+5) (Compose [Just 1, Just 2, Nothing, Just 3]))
      [Just 6,Just 7,Nothing,Just 8]
      

      您可以将其抽象为您的maybeX 的定义:

      -- Wrap, map, and unwrap
      maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
      maybeX f = getCompose . fmap f . Compose
      

      (事实上,定义中没有假设Maybe或[],只是限制类型注释。如果启用FlexibleContexts扩展,则可以推断类型(Functor g, Functor f) =&gt; (a1 -&gt; a) -&gt; f (g a1) -&gt; f (g a)并将其用于任意嵌套仿函数.

       >>> maybeX (+1) (Just [1,2])
       Just [2,3]
       >>> maybeX (+1) [[1,2]]
       [[2,3]]
       >>> maybeX (+1) Nothing -- type (Num a, Functor g) => Maybe (g a), since `Nothing :: Maybe a`
      

      )


      Compose 构造函数将[] 和Maybe 类型构造函数组合成一个新的类型构造函数:

      >>> :k Compose
      Compose :: (k1 -> *) -> (k -> k1) -> k -> *
      

      比较:

      Compose :: (k1 -> *) -> (k -> k1) -> k -> *
        (.)   :: (b  -> c) -> (a -> b)  -> a -> c
      

      (主要区别在于(.) 可以组合任何两个函数;似乎Compose 及其关联实例需要将最后一个更高种类的值应用于具体类型,一种*。 )

      将数据构造函数 Compose 应用于可能的列表会产生一个包装值

      >>> :t Compose [Nothing]
      Compose [Nothing] :: Compose [] Maybe a
      

      只要Compose 的参数本身是Functor 的实例(就像[] 和Maybe 一样),Compose f g 也是一个仿函数,所以你可以使用fmap:

      >>> fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
      Compose [Just 6,Nothing,Just 7,Just 8]
      

      Compose 值只是原始值的包装:

      >>> getCompose $ fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
      [Just 6,Nothing,Just 7,Just 8]
      

      最后,这与直接编写 fmap 并没有太大区别:

      instance (Functor f, Functor g) => Functor (Compose f g) where
          fmap f (Compose x) = Compose (fmap (fmap f) x)
      

      不同之处在于您可以使用函数定义 type 并免费获取其随附的 Functor 实例,而不必手动编写两者。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-31
        • 1970-01-01
        相关资源
        最近更新 更多