mostly:functional 第二十八章:Applicative 的实体

建筑跟外观一样散发着新颖的气息,而格局跟上一间也相当类似。而这次大厅中央放着两座雕塑,两个之间看起来几乎是一样的,只是其中一个比较长一点。而跟之前建筑里的相比,这个两个上面有着精巧的装饰。

把四周的的短箭头丢进去後,从出口跑出来的箭头,被裹在一球透明的凝胶状物质里。我还发现,较长的那个雕塑只能接受较长的箭头,而短的箭头只能放进短的雕塑里。




二元组是一种 Applicative 吗?

依照二元组的独特惯例,要证明二元组是 applicative 也需要一个前题。这个前题可以让我们更能体会上一节所说,「Applicative 有隐含的 monoid 特质」这件事。

二元组是 Applicative 的前题,就是前面的元素,必须要是 Monoid 才行。当你看到 (a, b) 这个二元组的时候,你可以把它当成装在 (a,) 容器里的 b

那麽在进行 ap 的时後,里面是函式应用,那外面的壳,包含那个 a,就要进行 mappend 了。
而另一个函式 pure,把输入的值放在後面没问题。那前面呢?既然那个位置是个 monoid,我们只要放个 mempty 在那边,编译时再来进行推导就可以了。

instance Applicative ((,) a) where
  pure b = (mempty, b)
  (a, f) <*> (a', b) = (mappend a b, f b)


-- 试试看
(Sum 3, (*10)) <*> (Sum 4, 20) -- => (Sum 7, 200)

pure (+8) <*> ("hello", 10)    -- => ("hello", 18)

串列是一种 Applicative 吗?

是的。但是为了写出它的实作,我们还得多介绍一样东西:

串列推导 (list comprehension)

要能够实做出串列的 applicative,得要先说明一下串列本身具有的一个功能:串列推导 (list comprehension)。如果用过 python 或是 elixir 的人,应该蛮习惯这个的了。而对於不熟悉这个语法的人,只要知道这个有点类似能产出装在串列里的元素的 for 回圈,而且可以做出巢状(多层)回圈做的事就可以了。

-- Hakell 语法

xs = [1, 2, 3]

[x * 2 | x <- xs] -- => [2, 4, 6]

---

ys = ['a', 'b']

[(x, y) | x <- xs, y <- ys] 
-- => [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]

对比数学上的 set notation,就能对这个语法是怎麽设计的心领神会。

https://chart.googleapis.com/chart?cht=tx&chl=%5C%7Bx%20%2B%201%20%5Cmid%20x%5Cin%20%5Cmathbb%7BN%7D%5C%7D

串列的 Applicative

於是我们可以讨论串列的 applicative 了,先来看一下它会怎麽运作:

-- Haskell 语法
(+) <$> [1, 2] <*> [3, 4, 5] -- => [4,5,6,5,6,7]

如果有点看不懂的话,可以想成第一次的 fmap 先产生装在串列里的 [(+1), (+2)]两个函式。
接着用 (+1) <$> [3, 4, 5], 再用 (+2) <$> [3, 4, 5] ,接着把两个结果串列结合在一起。

这个是怎麽实作的呢?

-- Haskell
instance Applicative [] where
  pure = []
  fs <*> xs = [f(x) | f <- fs, x <- xs]

所有的 Functor,都是 Applicative 吗?

不是。例如 Const 这个 functor 就无法证出 applicative 的性质。不过,这又会是另外一个故事了……


自然升格

如果你有注意到的话,我们大多数在 Applicative 的示范,都长成这种格式:

f <$> a <*> b
-- 或是
f <$> a <*> b <*> c

也就是第一步是一个 fmap,接下来才是 <*>。那是因为我们要先把多参数的函式先放进容器里。而这种第一步先 <$> 的手法,我们称之为自然升格。因为升格的过程在一般的函式去fmap 容器里的第一个变数时就被自然的完成了。

显式升格

自然升格对应的,就是显式升格了。我们可以手动先用 pure 把函式装到容器里,再来进行剩下的 <*>

pure f <*> a <*> b
-- 或是
pure f <*> a <*> b <*> c

LiftA2、LiftA3

不过有时候我们想要直接拿着普通的多参数函式,用多个放在容器里的参数进行函式应用。而 Haskell 提供了给双参数函式与三参数函式用的前缀版升格函式:liftA2liftA3

-- Haskell 语法

liftA2 (+) [1, 2] [3, 4, 5] -- => [4,5,6,5,6,7]

liftA3 (,,) "ab" "xy" [1, 2] 
-- => [('a','x',1),('a','x',2),('a','y',1),('a','y',2),('b','x',1),('b','x',2),('b','y',1),('b','y',2)]

LiftA

那有没有个单数数函式用的 liftA 呢?有的:

-- Haskell
liftA :: (a -> b) -> f a -> f b

liftA (+1) [1, 2, 3] -- => [2, 3, 4]

「可是,这不就是 fmap 吗?」

没错,这个行为跟 fmap 完全相同。applicative,做为一个比 functor 强的性质,是可以直接由它身上推导出 functor 的实体的。当我们进行升格的函式,只需要一个参数就饱和的情况,就跟 functor 的 fmap 是相同的事情。


你知道这个总是要来的....

函式本身也是一种 Applicative 吗?

先说结论:是的。

我们之前提过,若把函式本身当成 functor,用另一个函式对它 fmap,则会用传入的函式变动其输出值。而若我们把函式当成 Applicative,那麽可以做出把同一个参数同时传给多个函式的东西。

我们先来比对一下泛用的 applicative 的两个函式型别定义,以及当把容器代换成函式的型别定义:

-- Haskell 语法

-- 把任意的 functor, f,用装在函式容器里的东西来思考,也就是 (r -> ) 

pure :: a ->     f a   -- 任意 functor 的 pure
pure :: a -> (r -> a)  -- 函式上的 pure

(<*>) ::   f (a -> b)    -- 任意 functor 的 ap
        -> f a
        -> f b
        
(<*>) ::   (r -> a -> b) -- 函式上的 ap
        -> (r -> a)
        -> (r -> b)

实作

instance Applicative ((->) a) where
  pure = const
  g <*> h = \x -> g x (h x)

pure 一开始可能比较不好想像。那就先回到定义:给它一个值,回传包在容器里的这个值。如果用函式来代替上一句的容器的话,那我们就是要给它一个值,拿到一个直接回传这个值的函式。嗯?有没有觉得很熟悉?我们之前有做过这种没路用的东西:const,当时我们还拿它来做为连续升格的范例。

<*> 的部份,第一个参数是接收两个参数的函式,第二个参数也是个函式,而回传值当然也是个函式。这里有趣的地方在於 g 是一个双参数函式,而 h 是个单参数函式,先用 g 部份应用了 x,代表我们在 g 里面有机会对传进来的参数偷偷动手脚,而最後再来应用应用了 xh 函式

来看一下实际范例吧:

-- Haskell 
f x, y = (x + 2, y * 3)  -- 我们用 tuple 来示范,比较能看得出来这是*两个函式并存*的东西
g = (+ 10)

h = f <*> g  -- ap!

h 1 -- => (3,33)

延着这个概念再往下,就会触碰到 Reader 这个东西。不过我们就先止步於此吧。




而随着一间间房间走过,那些箭头不只会改变尾端爪子的样子。外面原本像是透明凝胶的物质,像是有保护色功能一样,不,比那个更好。每到一个房间,那个物质就会变得跟该房间里的东西完全一样…

在每一间房间里,只要找到开关的位置,都可以读到一些文件,而我愈来愈能看懂上面的字了。

走着走着,当然,又是那个放着平台的房间......

[to be contineue…]


<<:  Day28 资安小结 - 红队与蓝队 ( 内附名字由来 )

>>:  Day29-台湾菜鸟工程师除错之卷四

Day9-TypeScript(TS)的介面型别(Interface)Part 2

今天要来讲介面型别的使用范例。 通常我们会使用介面来定义函式型别,程序码如下, interface ...

企业资料通讯Week7 (1) | rdt(reliable data transfer)[上]

rdt 可靠资料传输协定 由於运输层(transport)的下面那一层~网路层(network)的传...

第18车厢-动ㄘ动ㄘ!tab页签切换+轮播应用篇

本篇介绍透过bootstrap4直接使用tab切换功能,并且实作tab切换自动循环播放 我们在昨篇...

[Day21] 扩展你的设计:根据与对话发生的装置修改对白

从手机到智慧音箱,在不同装置上要考量到的情形皆有差异。 这篇文章中将先介绍Google助理可回应的...

第24车厢-翻起来惹!页面翻页效果turn.js应用篇

本篇介绍可实现翻页效果的turn.js基本参数及基本用法 :哈罗! :(叫谁?) :叫你啦! :喔...