【问题标题】:Can fmap be written as a function instead of being called from the library?fmap 可以写成函数而不是从库中调用吗?
【发布时间】:2023-03-05 19:30:02
【问题描述】:

我试图理解 fmap 与函数的关系,我想知道是否有一种“简单”的方法来编写完成相同 fmap 的函数,而不是从函数库中调用它?

我是 haskell 的新手,所以当我检查参考资料时,我看到了很多我的班级还没有完成的事情 - 我正在努力保持领先地位,因为它非常困难并且会移动很快。

fmap的库定义为:

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

我想知道,如果使用其他函数,fmap 是否可以写成实现 fmap 的函数的辅助函数。

然后输出将与使用 fmap 完全相同,但我们将其替换为完成相同任务的辅助函数。有没有可能?

【问题讨论】:

  • 想想abf是什么类型。
  • fmapFunctor 类型类的一个方法(实际上是唯一一个)。 (上面的类型签名缺少Functor f 约束。)因此,它没有“标准”定义,需要为每个Functor 实例单独定义。但是,fmap 的每个单独“版本”通常很容易定义。例如列表的fmap 就是map,如果由于某种原因你不想使用库函数,它有一个非常简单的递归定义。
  • 而且由于您只是在学习,我会说在您对语言尤其是类型类更加熟悉之前,不必担心 Functor 和 fmap。但一定要熟悉map。我可以彻底推荐 learnyouahaskell.com/chapters 作为从零开始学习 Haskell 的绝佳资源。
  • fmap :: (a -> b) -> f a -> f b 是一个类型,而不是一个定义。
  • @RobinZigmond,事实上并不是唯一一个。 <$ 是另一个。

标签: haskell


【解决方案1】:

fmapFunctor 类型类的成员,定义如下:

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

这意味着作为Functor 实例的每个类型都有自己的fmap 专用实现:

-- For making type signatures in instances explicit
{-# LANGUAGE InstanceSigs #-}

instance Functor Maybe where
  fmap :: (a -> b) -> Maybe a -> Maybe b
  fmap f (Just x) = Just (f x)
  fmap f Nothing = Nothing

instance Functor [] where
  fmap :: (a -> b) -> [a] -> [b]
  fmap f (x : xs) = f x : fmap f xs
  fmap f [] = []
  -- Or: fmap = map

当您以特定类型调用 fmap 时,编译器会自动选择要使用的适当实例:

-- For making type arguments explicit
{-# LANGUAGE TypeApplications #-}

fmap succ (Just 1)
==
fmap @Maybe succ (Just 1)
==
Just 2

fmap succ [1, 2, 3]
==
fmap @[] succ [1, 2, 3]
==
[2, 3, 4]

您当然可以直接使用专门的函数来代替 fmap——我们只需要单独的函数 fmapMaybefmapList (map)、fmapEitherfmapIO 等等。但是,使用fmap 或一般类型类的​​优势在于,您可以编写多态 函数,这些函数适用于该类型类的任何 实例:

fmapBoth :: (Functor f) => (a -> b) -> f (a, a) -> f (b, b)
fmapBoth f m = fmap (\ (x, y) -> (f x, f y)) m

fmapBoth succ (Just (1, 2))
==
fmapBoth @Maybe succ (Just (1, 2))
==
Just (2, 3)

fmapBoth succ [(1, 2), (2, 3)]
==
fmapBoth @[] succ [(1, 2), (2, 3)]
==
[(2, 3), (3, 4)]

在内部,GHC 通过将 fmap 的特定实现作为额外参数传递给您定义的函数来实现这一点:

fmapBoth'
  :: ((a -> b) -> f a -> f b)
  -> (a -> b)
  -> f (a, a)
  -> f (b, b)
fmapBoth' fmapF f m = fmapF (\ (x, y) -> (f x, f y)) m

fmapBoth @[] succ [(1, 2), (2, 3)]
==
fmapBoth' map succ [(1, 2), (2, 3)]
==
map (\ (x, y) -> (succ x, succ y)) [(1, 2), (2, 3)]
==
[(2, 3), (3, 4)]

因此,您可以将Num a => …Functor f => … 之类的类型类约束视为函数的附加参数,该函数恰好由编译器隐式传递,包含特定类型类中所有方法的记录。 (事实上​​,ImplicitParams 扩展允许您将此机制用于 any 类型的隐式参数,尽管该扩展并未广泛使用,因为通常有更好的替代方案,例如 Reader。)

使用fmap 而不是专用函数的部分优势在于,它可以让您“对接口进行编码,而不是对实现进行编码”——例如,如果您在程序中使用列表,然后想更改为Vector 出于性能原因,如果你在任何地方都使用了像 fmap 这样的多态函数,那么你可以简单地换掉类型,你的代码将继续工作而无需修改——但如果你使用了 map 特别是列表,那么您将需要更新每个呼叫站点。

类型类的最初目的是避免有专门的函数来处理比较(EqOrd)和算术(Num)——如果我们没有这种多态性的话,那么我们需要单独的函数,如eqInteqFloateqChar 等。到处。类型类让我们对此进行抽象并编写 == 而不管特定类型如何,并让编译器插入对适当函数的调用。

【讨论】:

    【解决方案2】:

    您当然可以编写特定的函数,例如 mapList :: (a -> b) -> [a] -> [b]mapMaybe :: (a -> b) -> Maybe a -> Maybe b。听起来你好像还没有学习过类型类;您将需要他们将mapListmapMaybe 推广到fmap

    您也可以阅读the docs for fmap中的来源链接

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-21
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 2016-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多