可以将fmap编写为函数而不是从库中调用吗?

时间:2019-04-11 17:31:50

标签: haskell

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

我对Haskell还是陌生的,所以当我检查引用时,我发现很多事情我的课还没有结束-并且我试图保持领先地位,因为这非常困难并且会移动很快。

fmap的库定义为:

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

我想知道是否可以将fmap像辅助函数一样写入实现fmap的函数中。

然后,输出将完全就像使用fmap一样,但是我们将其替换为完成相同任务的辅助函数。甚至有可能吗?

2 个答案:

答案 0 :(得分:5)

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-我们只有单独的功能fmapMaybefmapListmap),fmapEitherfmapIO,依此类推。但是,通常使用fmap或类型类的优点是,您可以编写 polymorphic 函数,这些函数可用于该类型类的 any 实例:

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 类型的隐式参数,尽管该扩展名并未得到广泛使用,因为通常有更好的替代方法,例如{{1} }。

使用Reader代替专用功能的部分优势在于,它可以让您“编码到接口,而不是实现”(例如,如果您在程序中使用列表,以后又想出于性能原因更改为fmap,如果您到处都使用了Vector之类的多态函数,则只需换掉类型,代码就可以继续工作而无需修改-但是如果您使用过fmap(特定于列表),则您需要更新每个呼叫站点。

类型类的最初目的是避免具有专门的功能,例如比较(mapEq)和算术(Ord)-如果我们没有这种多态性,那么我们将需要单独的函数,例如NumeqInteqFloat,&c。到处。类型类使我们可以对此进行抽象并写入eqChar,而不管特定的类型如何,并让编译器插入对适当函数的调用。

答案 1 :(得分:2)

您当然可以编写特定的功能,例如mapList :: (a -> b) -> [a] -> [b]mapMaybe :: (a -> b) -> Maybe a -> Maybe b。听起来好像您还没有学习过类型类。您将需要它们将mapListmapMaybe概括为fmap

您还可以阅读the docs for fmap

中的 source 链接。