函子是一个类型类:
class Functor f where
map :: forall a b. (a -> b) -> f a -> f b要理解它, 必须先理解函子想描述什么, 即, 它的模型.
其实它有很复杂的数学模型, 但我们只说简单的.
考虑一个形如F a的类型, 比如Array Int.
把它想象成一个盒子, 盒子里面放着一个值.
对Array Int来说, 就是一个叫Array的盒子, 里面放着一个Int类型的值.
那么函子能提供什么功能呢? 它允许你输入一个a→b的函数, 来修改盒子里的值.
对于Array Int而言, 就是输入一个Int→X类型的函数, 然后你就会得到Array X.
从map函数的签名也可以看出, 输出a→b的函数, 然后输入f a, 得到f b.
数组实现了函子类型类, 所以可以使用map处理数组:
f1 :: Int -> String
f1 a = show a
x5 :: Array Int
x5 = [ 1, 2, 3 ]
x6 :: Array String
x6 = map f1 x5虽然, 实际上x5里面有三个Int值, 但这不重要, 我们就把他理解为, 一个Int值.
那么map做了什么呢? 它把这个Int值转成了一个字符串.
所以我们得到了Array String, 意为一个叫Array的盒子里装了一个String值.
但为什么要这么看, 它明明是三个值啊?
粗略的说, 对于形如F a类型的值, 在函子的模型中, 说是一个a类型的值放入了一个叫F的上下文中.
上下文可以给值提供更多的能力.
在这里, Array上下文提供的能力是不确定性.
日常生活中也有这样的例子, 比如说, x是1或者2, 那么这里就可以写成x=[1,2].
那, x+1是多少? 答案就是2或者3了.
可以使用map表达这个过程: map (\a → a + 1) x.
这个的意思是, 对这个Array盒子里的, 那个不确定的数加一. 得到的结果就是[2,3].
所以理解模型很重要, 如果不理解模型, 就不能解释为什么会得到这个结果. 虽然可以通过看代码找到他的逻辑, 可以扣逻辑得到结果, 但却不知道为什么要这样实现. 而如果理解了模型, 一切都是自然的, 就不需要再在意它具体是怎么写的, 就可以自然的使用了.
一个单纯的Int类型的值是不具有表示不确定性的能力的, 但只要套在Array里就可以.
但数组还可以实现其他类型类, 在其他类型类中, 它不一定会表示不确定性, 有时候我们也会把数组看作很多值组成的序列, 我们也有处理序列的函数.
另一个常见的模型是Maybe.
data Maybe a
= Just a
| Nothing
instance Functor Maybe where
map :: forall a b. (a -> b) -> Maybe a -> Maybe b
map _ Nothing = Nothing
map f (Just x) = Just (f x)它的上下文是可空.
例如, 一个Maybe Int值, 表示的是一个叫Maybe的盒子里, 可能装着一个Int值, 也可能是空的.
无论是不是空的, 你都可以通过map操作盒子内部的值(如果值存在的话).
逻辑显而易见, 如果是空盒子, 怎么操作都是空盒子, 如果是有值的盒子, 那么值就会变.
这非常适合处理一些可能出错的情况. 比如, 一个除法函数:
div :: Number -> Number -> Maybe Number
div a b
| b == 0.0 = Nothing
| otherwise = Just (a / b)我们知道, 除法的除数不能为0, 这里就可以说, 除法返回的结果可能是一个空, 或者一个Number.
接下来就可以使用了:
x7 :: Maybe Number
x7 = div 2.0 1.0
x11 :: Maybe Number
x11 = div 2.0 0.0对于任何一个Maybe Number类型的值, 我们也可以像普通的, 操作Number类型的值一样操作它.
比如我们有一个操作Number类型的函数.
add1 :: Number -> Number
add1 a = a + 1.0现在只要在函数前面加map就可以用在Maybe Number类型的值上了:
x8 :: Maybe Number
x8 = map add1 x7最后, 在真正要使用这个Maybe值的时候, 只需要模式匹配一下, 把盒子打开就可以了:
maybeNumberToStr :: Maybe Number -> String
maybeNumberToStr (Nothing) = "Nothing"
maybeNumberToStr (Just a) = show a
x9 :: String
x9 = maybeNumberToStr x8
x10 :: String
x10 = maybeNumberToStr $ map add1 (div 2.0 0.0)module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
data Maybe a
= Just a
| Nothing
instance Functor Maybe where
map :: forall a b. (a -> b) -> Maybe a -> Maybe b
map _ Nothing = Nothing
map f (Just x) = Just (f x)
div :: Number -> Number -> Maybe Number
div a b
| b == 0.0 = Nothing
| otherwise = Just (a / b)
x7 :: Maybe Number
x7 = div 2.0 1.0
x11 :: Maybe Number
x11 = div 2.0 0.0
add1 :: Number -> Number
add1 a = a + 1.0
maybeNumberToStr :: Maybe Number -> String
maybeNumberToStr (Nothing) = "Nothing"
maybeNumberToStr (Just a) = show a
x8 :: Maybe Number
x8 = map add1 x7
x9 :: String
x9 = maybeNumberToStr x8
x10 :: String
x10 = maybeNumberToStr $ map add1 (div 2.0 0.0)
main :: Effect Unit
main = log x10