{"componentChunkName":"component---src-templates-post-tsx","path":"/what-are-functors","result":{"data":{"markdownRemark":{"html":"<p>初学 Haskell 时，总是不太理解<ruby>函子<rp>(</rp><rt>functor</rt><rp>)</rp></ruby>的概念。虽然看了很多文章，但还是感觉隔着一层迷雾。最近写了很多 TypeScript，和类型系统做了很多搏斗（笑），再回来看函子的概念，突然感觉比之前更清晰了。</p>\n<p>很多数据结构，用<ruby>范型<rp>(</rp><rt>generic</rt><rp>)</rp></ruby>描述起来大都是 <code class=\"language-text\">Structure&lt;T></code> 的形式，例如 <code class=\"language-text\">LinkedList&lt;T></code>、<code class=\"language-text\">BinarySearchTree&lt;T></code> 和 <code class=\"language-text\">Optional&lt;T></code> 等等。这类数据结构虽然对数据的组织各有差异，但都建构于某个类型之上，或者说「持有」某个类型的数据。在下文中，我们称之为<ruby>容器类型<rp>(</rp><rt>container types</rt><rp>)</rp></ruby>。</p>\n<p>对容器类型来说，一个常见的操作就是对容器中的元素进行遍历，以生成新的元素。在没有这个抽象的情况下，你可能会写很多种不同的遍历。有了函子后，这种重复工作可以被抽象成一个简单的 <code class=\"language-text\">fmap</code> 函数，你可以把某个在原始类型上的变换，转换为在容器类型上的变换。当然，这是最直观的理解，如果你去翻 Haskell 中 <code class=\"language-text\">Functor</code> 的定义，你会发现其中还有一个操作符 <code class=\"language-text\">&lt;$</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 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 punctuation\">(</span><span class=\"token operator\">&lt;$</span><span class=\"token punctuation\">)</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\">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 builtin\">fmap</span> <span class=\"token operator\">.</span> <span class=\"token builtin\">const</span></code></pre></div>\n<p>它是做什么用的？回忆一下 <code class=\"language-text\">const</code> 的定义。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token builtin\">const</span> <span class=\"token operator\">::</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\">a</span>\n<span class=\"token builtin\">const</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">_</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">x</span></code></pre></div>\n<p>带入到上面的函数，可以看出，它代表了一个最常见的操作——丢掉原来容器类型里的内容，把某个有价值的变量（用 <code class=\"language-text\">$</code> 表示）输送（用 <code class=\"language-text\">&lt;</code> 表示）到一个容器类型中，这么看 <code class=\"language-text\">&lt;$</code> 这个符号还是挺传神的。</p>\n<h2>什么是<ruby>自函子<rp>(</rp><rt>endofunctor</rt><rp>)</rp></ruby>？</h2>\n<p>我相信大家都听过这句话了：「<ruby>单子<rp>(</rp><rt>monad</rt><rp>)</rp></ruby>不就是<em><ruby>自函子<rp>(</rp><rt>endofunctor</rt><rp>)</rp></ruby></em>范畴上的一个<ruby>幺半群<rp>(</rp><rt>monoid</rt><rp>)</rp></ruby>吗？有什么难理解的？」<ruby>自函子<rp>(</rp><rt>endofunctor</rt><rp>)</rp></ruby>的定义用一句话就能概括：它是一种特殊的函子，其变换的输入类型（定义域）和输出类型（值域）是同一个类型。</p>\n<p>Haskell 中自带 <code class=\"language-text\">Endo</code> 类型，它的定义是参数和返回值都是相同类型的一元函数。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">newtype</span> <span class=\"token constant\">Endo</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Endo</span> <span class=\"token punctuation\">{</span> <span class=\"token hvariable\">appEndo</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span> <span class=\"token punctuation\">}</span></code></pre></div>\n<p>值得一提的是，endo 这个前缀来源于希腊语的 <ruby>ἔνδον<rp>(</rp><rt>endon</rt><rp>)</rp></ruby>，含义是「<ruby>在…里面，内部的，被…吸收或包含的<rp>(</rp><rt>within, inner, absorbing, or containing</rt><rp>)</rp></ruby>」。</p>\n<h2>有关 <code class=\"language-text\">fmap</code> 和 <code class=\"language-text\">map</code> 命名</h2>\n<p>最开始读《Real World Haskell》的时候，看着示例代码里成片的 <code class=\"language-text\">fmap</code>，我心里浮现出一个疑问：<code class=\"language-text\">fmap</code> 在数组类型上的实现明明和 <code class=\"language-text\">map</code> 相同，为什么不直接把 <code class=\"language-text\">Functor</code> 中的 <code class=\"language-text\">fmap</code> 改名为 <code class=\"language-text\">map</code> 呢？后来我在 Haskell 的 wiki 上看到了这个问题的解答。</p>\n<blockquote>\n<p>Why not just do away with the current list-only <code class=\"language-text\">map</code> function, and rename <code class=\"language-text\">fmap</code> to <code class=\"language-text\">map</code> instead? Well, that’s a good question. The usual argument is that someone just learning Haskell, when using <code class=\"language-text\">map</code> incorrectly, would much rather see an error about lists than about <code class=\"language-text\">Functor</code>s.</p>\n</blockquote>","frontmatter":{"title":"What are Functors?","date":"October 12, 2018","tags":["haskell","functional-programming","functor"],"language":"zh-Hans"}},"site":{"siteMetadata":{"title":"Luyu Cheng"}}},"pageContext":{"id":"08b85ebb-82a6-5c4a-a2e7-94565f33399b"}},"staticQueryHashes":["3649515864","63159454","684267432"]}