{"componentChunkName":"component---src-templates-post-tsx","path":"/what-are-applicative-functors","result":{"data":{"markdownRemark":{"html":"<p>上一篇文章我们讲了<ruby>函子<rp>(</rp><rt>functor</rt><rp>)</rp></ruby>是什么，这一篇文章讲一个基于函子的概念——<ruby>可应用函子<rp>(</rp><rt>applicative functor</rt><rp>)</rp></ruby>。</p>\n<h2>动机</h2>\n<p>我们知道，函子可以描述从元素类型的映射到容器类型之间的映射之间的转换。最常见的应用场景就是 <code class=\"language-text\">Maybe</code>，下面是 <code class=\"language-text\">Maybe</code> 的 <code class=\"language-text\">Functor</code> 实现。请读者熟悉一下，因为后面会用到。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">instance</span> <span class=\"token constant\">Functor</span> <span class=\"token constant\">Maybe</span> <span class=\"token keyword\">where</span>\n  <span class=\"token builtin\">fmap</span> <span class=\"token hvariable\">_</span> <span class=\"token constant\">Nothing</span>  <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n  <span class=\"token builtin\">fmap</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">a</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Just</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>假设我们有二元运算，叫做 <code class=\"language-text\">op</code>。我们不管它做了什么，只需要知道它是一个在整数上的二元运算就可以了。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">op</span> <span class=\"token operator\">::</span> <span class=\"token constant\">Float</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span></code></pre></div>\n<p>因为 Haskell 中的函数都是高阶函数，所以我们 Haskell 程序员常用的一个技巧是用函数只用一半，后半部分留着，需要用的时候再用。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">newop</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">op</span> <span class=\"token number\">0.0</span></code></pre></div>\n<p>但很多时候我们拿到的值的类型并非是 <code class=\"language-text\">Float</code>，很有可能是被某个<ruby>容器类型<rp>(</rp><rt>container type</rt><rp>)</rp></ruby>包装起来的 <code class=\"language-text\">Float</code>，比如 <code class=\"language-text\">Maybe Float</code>。此时我们想把 <code class=\"language-text\">op</code> 变得在 <code class=\"language-text\">Maybe Float</code> 上也是可用的，该怎么做呢？如果直接重写一个 <code class=\"language-text\">opForMaybe</code>，代码大概是这样子的。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">opForMaybe</span> <span class=\"token operator\">::</span> <span class=\"token constant\">Maybe</span> <span class=\"token constant\">Float</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Maybe</span> <span class=\"token constant\">Float</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Maybe</span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">opForMaybe</span> <span class=\"token constant\">Nothing</span>  <span class=\"token hvariable\">_</span>        <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n<span class=\"token hvariable\">opForMaybe</span> <span class=\"token hvariable\">_</span>        <span class=\"token constant\">Nothing</span>  <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n<span class=\"token hvariable\">opForMaybe</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">y</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Just</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">op</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>但别忘了我们是 Haskell 程序员，我们要从类型的角度思考问题！很自然地，我们会想到上面的 <code class=\"language-text\">Functor</code>，毕竟它的作用就是「描述从元素类型的映射到容器类型之间的映射之间的转换」。我们打开 GHCi 来试试。</p>\n<div class=\"gatsby-highlight\" data-language=\"plaintext\"><pre class=\"language-plaintext\"><code class=\"language-plaintext\">Prelude> :t fmap op\nfmap op :: Functor f => f Float -> f (Float -> Float)</code></pre></div>\n<p>诶？这和我们想的不太一样啊，我们想要的是一个 <code class=\"language-text\">Maybe Float -> Maybe Float -> Maybe Float</code> 的函数，用 <code class=\"language-text\">fmap</code> 操作后得到的反而是一个 <code class=\"language-text\">Maybe Float -> Maybe (Float -> Float)</code> 函数。这就说明 <code class=\"language-text\">Functor</code> 在此刻肯定不能满足我们的需求——把一个二元的操作映射到对容器类型的二元操作，我们要构造一个<em>至少</em>可以用于二元函数的 <code class=\"language-text\">Functor</code>。</p>\n<h2>构造</h2>\n<p>上面的小节展示了一种 <code class=\"language-text\">Functor</code> 不能满足需求的情况，下面我们就来想一下如何解决这个问题。如果我们仿照 <code class=\"language-text\">Functor</code> 的定义，去构建一个可以用于二元函数的 <code class=\"language-text\">Functor</code>，写出来的东西会是下面这个样子。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">class</span> <span class=\"token constant\">BinaryFunctor</span> <span class=\"token hvariable\">f</span> <span class=\"token keyword\">where</span>\n  <span class=\"token hvariable\">bfmap</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">c</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">c</span></code></pre></div>\n<p>二元还好，如果是三元、四元、甚至十元函数，那我们岂不是要一个一个写？作为喜欢抽象的 Haskell 程序员，肯定不能就此屈服，我们要实现更加通用的东西。为了看出哪里可以改进，我们随便把一个三元函数套用到上面的 <code class=\"language-text\">BinaryFunctor</code> 中。</p>\n<div class=\"gatsby-highlight\" data-language=\"plaintext\"><pre class=\"language-plaintext\"><code class=\"language-plaintext\">Prelude> :t op3\nop3 :: Float -> Float -> Float -> Float\nPrelude> :t bfmap op3\nbfmap op3 :: BinaryFunctor f => f Float -> f Float -> f (Float -> Float)</code></pre></div>\n<p>观察类型我们可以发现，和上一节 GHCi 的结果一样，得到的函数签名的最后一个类型都是 <code class=\"language-text\">f (Float -> Float)</code>，所以与其给 <span class=\"katex\"><span class=\"katex-mathml\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\"><semantics><mrow><mi>n</mi></mrow><annotation encoding=\"application/x-tex\">n</annotation></semantics></math></span><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.43056em;vertical-align:0em;\"></span><span class=\"mord mathnormal\">n</span></span></span></span> 元函数设计不同的 <code class=\"language-text\">Functor</code>，我们不如直接设计一个把 <code class=\"language-text\">f (a -> b)</code> 转换为 <code class=\"language-text\">f a -> f b</code> 的抽象。写出来大概就是这个样子的。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">class</span> <span class=\"token constant\">Functor'</span> <span class=\"token hvariable\">f</span> <span class=\"token keyword\">where</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token operator\">::</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">b</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span></code></pre></div>\n<p>我们手动给 <code class=\"language-text\">Maybe</code> 实现一下。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">instance</span> <span class=\"token constant\">Functor'</span> <span class=\"token constant\">Maybe</span> <span class=\"token keyword\">where</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token constant\">Nothing</span>  <span class=\"token hvariable\">_</span>        <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token hvariable\">_</span>        <span class=\"token constant\">Nothing</span>  <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">f</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">x</span></code></pre></div>\n<p>这样就完美了！不过还有个问题：我们对第一个参数 <code class=\"language-text\">Maybe a</code> 做了过多的<ruby>解构<rp>(</rp><rt>destruct</rt><rp>)</rp></ruby>，讨论了每一种情况，这样有点重复造轮子，通过重用 <code class=\"language-text\">Functor</code> 中的 <code class=\"language-text\">fmap</code>，我们可以实现得更加简洁。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">class</span> <span class=\"token constant\">Functor</span> <span class=\"token hvariable\">f</span> <span class=\"token operator\">=></span> <span class=\"token constant\">Functor'</span> <span class=\"token hvariable\">f</span> <span class=\"token keyword\">where</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token operator\">::</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">b</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span>\n\n<span class=\"token keyword\">instance</span> <span class=\"token constant\">Functor'</span> <span class=\"token constant\">Maybe</span> <span class=\"token keyword\">where</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token constant\">Nothing</span>  <span class=\"token hvariable\">_</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Nothing</span>\n  <span class=\"token builtin\">fmap</span>' <span class=\"token punctuation\">(</span><span class=\"token constant\">Just</span> <span class=\"token hvariable\">f</span><span class=\"token punctuation\">)</span> <span class=\"token hvariable\">x</span> <span class=\"token operator\">=</span> <span class=\"token builtin\">fmap</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">x</span></code></pre></div>\n<h2>登场</h2>\n<p>我们定义的 <code class=\"language-text\">Functor'</code> 其实就是 <code class=\"language-text\">Applicative</code>，来看看它的定义。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">class</span> <span class=\"token constant\">Functor</span> <span class=\"token hvariable\">f</span> <span class=\"token operator\">=></span> <span class=\"token constant\">Applicative</span> <span class=\"token hvariable\">f</span> <span class=\"token keyword\">where</span>\n    <span class=\"token comment\">{-# MINIMAL pure, ((&lt;*>) | liftA2) #-}</span>\n\n    <span class=\"token hvariable\">pure</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span>\n\n    <span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;*></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">b</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span>\n    <span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;*></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">liftA2</span> <span class=\"token builtin\">id</span>\n\n    <span class=\"token hvariable\">liftA2</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">c</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">c</span>\n    <span class=\"token hvariable\">liftA2</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">x</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;*></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">fmap</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token punctuation\">(</span><span class=\"token operator\">*></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span>\n    <span class=\"token hvariable\">a1</span> <span class=\"token operator\">*></span> <span class=\"token hvariable\">a2</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">id</span> <span class=\"token operator\">&lt;$</span> <span class=\"token hvariable\">a1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;*></span> <span class=\"token hvariable\">a2</span>\n\n    <span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;*</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">a</span>\n    <span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;*</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">liftA2</span> <span class=\"token builtin\">const</span></code></pre></div>\n<p><code class=\"language-text\">Applicative</code> 中居然有五个成员，我们分别解读一下。</p>\n<ul>\n<li><code class=\"language-text\">&lt;*></code> 其实就是我们上面定义的 <code class=\"language-text\">fmap'</code> 函数，它们的类型签名一模一样。</li>\n<li>我们发现了一个熟悉的成员 <code class=\"language-text\">liftA2</code>，它的函数签名和我们第一版的 <code class=\"language-text\">Functor'</code> 的一模一样！其实这二者之间是可以相互转换的，只不过我们之前没有发现。</li>\n<li>除此之外，还有一个 <code class=\"language-text\">pure</code> 函数，它的作用我们现在暂且不提，因为要在引入新的概念后才能明白。</li>\n<li>而 <code class=\"language-text\">*></code> 和 <code class=\"language-text\">&lt;*</code>，可以理解为是 <code class=\"language-text\">Functor</code> 中的 <code class=\"language-text\">&lt;$</code> 操作符，忽略其中的一个参数，直接给出定值。</li>\n</ul>","frontmatter":{"title":"What are Applicative Functors?","date":"November 14, 2018","tags":["Haskell","functional-programming","functor","applicative"],"language":"zh-Hans"}},"site":{"siteMetadata":{"title":"Luyu Cheng"}}},"pageContext":{"id":"90f994b2-ddbf-55fb-9500-e1b9ed3b583a"}},"staticQueryHashes":["3649515864","63159454","684267432"]}