建筑跟外观一样散发着新颖的气息,而格局跟上一间也相当类似。而这次大厅中央放着两座雕塑,两个之间看起来几乎是一样的,只是其中一个比较长一点。而跟之前建筑里的相比,这个两个上面有着精巧的装饰。
把四周的的短箭头丢进去後,从出口跑出来的箭头,被裹在一球透明的凝胶状物质里。我还发现,较长的那个雕塑只能接受较长的箭头,而短的箭头只能放进短的雕塑里。
依照二元组的独特惯例,要证明二元组是 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)。如果用过 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,就能对这个语法是怎麽设计的心领神会。
於是我们可以讨论串列的 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]
不是。例如 Const 这个 functor 就无法证出 applicative 的性质。不过,这又会是另外一个故事了……
如果你有注意到的话,我们大多数在 Applicative 的示范,都长成这种格式:
f <$> a <*> b
-- 或是
f <$> a <*> b <*> c
也就是第一步是一个 fmap
,接下来才是 <*>
。那是因为我们要先把多参数的函式先放进容器里。而这种第一步先 <$>
的手法,我们称之为自然升格。因为升格的过程在一般的函式去fmap
容器里的第一个变数时就被自然的完成了。
与自然升格对应的,就是显式升格了。我们可以手动先用 pure
把函式装到容器里,再来进行剩下的 <*>
:
pure f <*> a <*> b
-- 或是
pure f <*> a <*> b <*> c
不过有时候我们想要直接拿着普通的多参数函式,用多个放在容器里的参数进行函式应用。而 Haskell 提供了给双参数函式与三参数函式用的前缀版升格函式:liftA2
与 liftA3
:
-- 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
呢?有的:
-- Haskell
liftA :: (a -> b) -> f a -> f b
liftA (+1) [1, 2, 3] -- => [2, 3, 4]
「可是,这不就是 fmap
吗?」
没错,这个行为跟 fmap
完全相同。applicative,做为一个比 functor 强的性质,是可以直接由它身上推导出 functor 的实体的。当我们进行升格的函式,只需要一个参数就饱和的情况,就跟 functor 的 fmap
是相同的事情。
你知道这个总是要来的....
先说结论:是的。
我们之前提过,若把函式本身当成 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
里面有机会对传进来的参数偷偷动手脚,而最後再来应用应用了 x
的 h
函式。
来看一下实际范例吧:
-- Haskell
f x, y = (x + 2, y * 3) -- 我们用 tuple 来示范,比较能看得出来这是*两个函式并存*的东西
g = (+ 10)
h = f <*> g -- ap!
h 1 -- => (3,33)
延着这个概念再往下,就会触碰到 Reader
这个东西。不过我们就先止步於此吧。
而随着一间间房间走过,那些箭头不只会改变尾端爪子的样子。外面原本像是透明凝胶的物质,像是有保护色功能一样,不,比那个更好。每到一个房间,那个物质就会变得跟该房间里的东西完全一样…
在每一间房间里,只要找到开关的位置,都可以读到一些文件,而我愈来愈能看懂上面的字了。
走着走着,当然,又是那个放着平台的房间......
[to be contineue…]
<<: Day28 资安小结 - 红队与蓝队 ( 内附名字由来 )
今天要来讲介面型别的使用范例。 通常我们会使用介面来定义函式型别,程序码如下, interface ...
rdt 可靠资料传输协定 由於运输层(transport)的下面那一层~网路层(network)的传...
本篇介绍透过bootstrap4直接使用tab切换功能,并且实作tab切换自动循环播放 我们在昨篇...
从手机到智慧音箱,在不同装置上要考量到的情形皆有差异。 这篇文章中将先介绍Google助理可回应的...
本篇介绍可实现翻页效果的turn.js基本参数及基本用法 :哈罗! :(叫谁?) :叫你啦! :喔...