{"componentChunkName":"component---src-templates-post-tsx","path":"/rewrite-foldl-with-foldr-explained","result":{"data":{"markdownRemark":{"html":"<blockquote>\n<p>📣 本文修订于 2020 年 11 月 27 日。</p>\n</blockquote>\n<h2>问题</h2>\n<p>在<a href=\"http://book.realworldhaskell.org/\" target=\"_blank\" rel=\"noopener noreferrer\">《Real World Haskell》</a>一书中，作者在 95 页中给出了一个神奇的实现：用 <code class=\"language-text\">foldr</code> 实现 <code class=\"language-text\">foldl</code>，并在下面附上了一段话。</p>\n<blockquote>\n<p><strong>Understanding foldl in terms of foldr</strong></p>\n<p>If you want to set yourself a solid challenge, try to follow our definition of foldl using foldr. Be warned: this is not trivial! You might want to have the following tools at hand: some headache pills and a glass of water, ghci (so that you can find out what the id function does), and a pencil and paper.</p>\n<p>You will want to follow the same manual evaluation process as we just outlined to see what foldl and foldr were really doing. If you get stuck, you may find the task easier after reading “Partial Function Application and Currying” on page 100.</p>\n</blockquote>\n<p>大意就是说：理解这个还是比较困难的，准备好笔和纸（以及头疼药）再来思考这个问题。作为爱思考的函数式编程爱好者我偏不信这个邪，本文就是在我苦苦思考后写出的一种容易理解的解释。</p>\n<h2>观察</h2>\n<blockquote>\n<p>如果你对 <code class=\"language-text\">foldr</code> 和 <code class=\"language-text\">foldl</code> 两个函数十分熟悉，知道它们是如何求值的，那就可以跳过这一节。</p>\n</blockquote>\n<p>先来看看它们的定义：</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token builtin\">foldr</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">b</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> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token punctuation\">[</span><span class=\"token hvariable\">b</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span>\n<span class=\"token builtin\">foldr</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span>     <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span>\n<span class=\"token builtin\">foldr</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">x</span><span class=\"token operator\">:</span><span class=\"token hvariable\">xs</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">x</span> <span class=\"token operator\">`f`</span> <span class=\"token builtin\">foldr</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">xs</span>\n\n<span class=\"token builtin\">foldl</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\">a</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 punctuation\">[</span><span class=\"token hvariable\">b</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span>\n<span class=\"token builtin\">foldl</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span>     <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span>\n<span class=\"token builtin\">foldl</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">x</span><span class=\"token operator\">:</span><span class=\"token hvariable\">xs</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token builtin\">foldl</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">z</span> <span class=\"token operator\">`f`</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span> <span class=\"token hvariable\">xs</span></code></pre></div>\n<p>为了搞清楚这两个函数究竟在做什么，我们分别在 <code class=\"language-text\">folr</code> 和 <code class=\"language-text\">foldl</code> 上应用函数 <code class=\"language-text\">(+)</code>、整数 <code class=\"language-text\">0</code> 和列表 <code class=\"language-text\">[1, 2, 3, 4]</code>。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\">  <span class=\"token builtin\">foldr</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token number\">0</span> <span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span> <span class=\"token number\">1</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">foldr</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token number\">0</span> <span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n<span class=\"token operator\">=</span> <span class=\"token number\">1</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">2</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">foldr</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token number\">0</span> <span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token operator\">=</span> <span class=\"token number\">1</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">2</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">3</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">foldr</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token number\">0</span> <span class=\"token punctuation\">[</span><span class=\"token number\">4</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token operator\">=</span> <span class=\"token number\">1</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">2</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">3</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">4</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">foldr</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token number\">0</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token operator\">=</span> <span class=\"token number\">1</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">2</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">3</span> <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span><span class=\"token number\">4</span> <span class=\"token operator\">+</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token builtin\">foldl</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span>     <span class=\"token number\">0</span>                     <span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span> <span class=\"token builtin\">foldl</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span>    <span class=\"token punctuation\">(</span><span class=\"token number\">0</span> <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span>                <span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span> <span class=\"token builtin\">foldl</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span>   <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span> <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">2</span><span class=\"token punctuation\">)</span>           <span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span> <span class=\"token builtin\">foldl</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span>  <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span> <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">2</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span>      <span class=\"token punctuation\">[</span><span class=\"token number\">4</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span> <span class=\"token builtin\">foldl</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">+</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span> <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">2</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">4</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span>\n<span class=\"token operator\">=</span>\t\t\t      <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span> <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">2</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">4</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>仔细观察求值结果可以发现：<code class=\"language-text\">foldr</code> 把初始运算元 <code class=\"language-text\">z</code> 放在列表的最右边，每次取最右边的两个元素，应用二元函数 <code class=\"language-text\">f</code> 求出结果，并把结果放回最右边；而 <code class=\"language-text\">foldl</code> 做的事情恰好相反，它把初始运算元放在列表最左边，每次取最左边的两个元素，应用二元函数 <code class=\"language-text\">f</code> 求出结果，并把结果放回最左边。</p>\n<blockquote>\n<p>你可能会问：两个表达式运算后的结果不是一样吗？这是因为我们用了 <code class=\"language-text\">(+)</code> 作为二元函数，而 <code class=\"language-text\">(+)</code> 满足交换律，如果选择一个没有交换律的二元函数，比如 <code class=\"language-text\">(-)</code> 那么结果就会大不相同了。</p>\n</blockquote>\n<p>通过观察 <code class=\"language-text\">foldl</code> 和 <code class=\"language-text\">foldr</code> 的求值过程，我们可以发现，<code class=\"language-text\">foldl</code> 的运算顺序是一棵<a href=\"https://en.wikipedia.org/wiki/Leftist_tree\" target=\"_blank\" rel=\"noopener noreferrer\"><ruby>左偏树<rp>(</rp><rt>leftist tree</rt><rp>)</rp></ruby></a>，而 <code class=\"language-text\">foldr</code> 的运算顺序可以用一棵<ruby>右偏树<rp>(</rp><rt>rightist tree</rt><rp>)</rp></ruby>表示，两个函数的差别主要在于<em>运算顺序</em>。所以用 <code class=\"language-text\">foldr</code> 重写 <code class=\"language-text\">foldl</code>，本质上就是要用 <code class=\"language-text\">foldr</code> 来<em>模拟</em> <code class=\"language-text\">foldl</code> 的运算顺序。</p>\n<h2>函数和数据</h2>\n<p>在 Haskell 中，如果让你实现一个表示二维实数空间上的一个点，你会怎么表示？我们的第一想法就是去构造新的类型。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">data</span> <span class=\"token constant\">Point</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Point</span> <span class=\"token constant\">Float</span> <span class=\"token constant\">Float</span>\n\n<span class=\"token hvariable\">point</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\">Point</span>\n<span class=\"token hvariable\">point</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Point</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span></code></pre></div>\n<p>我们可以通过<ruby>模式匹配<rp>(</rp><rt>pattern matching</rt><rp>)</rp></ruby>获得结构中的数据。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">getX</span> <span class=\"token operator\">::</span> <span class=\"token constant\">Point</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">getX</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Point</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">_</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">x</span>\n\n<span class=\"token hvariable\">getY</span> <span class=\"token operator\">::</span> <span class=\"token constant\">Point</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">getY</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Point</span> <span class=\"token hvariable\">_</span> <span class=\"token hvariable\">y</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">y</span></code></pre></div>\n<p>在<a href=\"https://mitpress.mit.edu/sites/default/files/sicp/index.html\" target=\"_blank\" rel=\"noopener noreferrer\">《计算机程序设计的构造和解释》</a>的第二章里，作者介绍了<ruby>数据抽象<rp>(</rp><rt>data abstraction</rt><rp>)</rp></ruby>的概念。作者演示了如何用函数去构造 <code class=\"language-text\">cons</code>、<code class=\"language-text\">car</code> 和 <code class=\"language-text\">cdr</code> 这三个基本函数。</p>\n<div class=\"gatsby-highlight\" data-language=\"scheme\"><pre class=\"language-scheme\"><code class=\"language-scheme\"><span class=\"token punctuation\">(</span><span class=\"token keyword\">define</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">cons</span> x y<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">(</span><span class=\"token keyword\">define</span> <span class=\"token punctuation\">(</span><span class=\"token function\">dispatch</span> m<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">(</span><span class=\"token keyword\">cond</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token operator\">=</span> m <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> x<span class=\"token punctuation\">)</span>\n          <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token operator\">=</span> m <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> y<span class=\"token punctuation\">)</span>\n          <span class=\"token punctuation\">(</span><span class=\"token keyword\">else</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">error</span> <span class=\"token string\">\"Argument not 0 or 1 -- CONS\"</span> m<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n  dispatch<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">(</span><span class=\"token keyword\">define</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">car</span> z<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token function\">z</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">(</span><span class=\"token keyword\">define</span> <span class=\"token punctuation\">(</span><span class=\"token builtin\">cdr</span> z<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span><span class=\"token function\">z</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>在这个例子里，<code class=\"language-text\">cons</code> 所返回的不是传统意义上的<ruby>数据<rp>(</rp><rt>data</rt><rp>)</rp></ruby>，而是一个接受更多参数的函数。<code class=\"language-text\">car</code> 和 <code class=\"language-text\">cdr</code> 就是通过对这个函数进行进一步求值而得到所需的值的。</p>\n<p>带着这个例子所给我们的启示，我们可以在不构造新类型的情况下定义一个点。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">point</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\">Bool</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">point</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span> <span class=\"token hvariable\">t</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">if</span> <span class=\"token hvariable\">t</span> <span class=\"token keyword\">then</span> <span class=\"token hvariable\">x</span> <span class=\"token keyword\">else</span> <span class=\"token hvariable\">y</span>\n\n<span class=\"token hvariable\">getX</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Bool</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">getX</span> <span class=\"token hvariable\">pt</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">pt</span> <span class=\"token constant\">True</span>\n\n<span class=\"token hvariable\">getY</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Bool</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token constant\">Float</span>\n<span class=\"token hvariable\">getY</span> <span class=\"token hvariable\">pt</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">pt</span> <span class=\"token constant\">False</span></code></pre></div>\n<p>你会发现，上面代码里的 <code class=\"language-text\">point</code>、<code class=\"language-text\">getX</code> 和 <code class=\"language-text\">getY</code> 的用法和最初的版本并没有什么差别。<code class=\"language-text\">Bool -> Float</code> 其实和 <code class=\"language-text\">Point</code> 无异，都是一个可以表示出两个坐标的类型。讲到这，你可能已经明白了这道题的做法。</p>\n<h2>构造</h2>\n<p>上一节的内容启发了我们，我们可以用 <code class=\"language-text\">foldr</code> 构造一个结构，再把这个结构按照 <code class=\"language-text\">foldl</code> 的方法展开并计算，就可以达到我们的目的了。</p>\n<p>我们先对 <code class=\"language-text\">foldr</code> 进行拆分，把它分为两部分：构造<ruby>右偏树<rp>(</rp><rt>rightist tree</rt><rp>)</rp></ruby>和对右偏树结构进行求值。我们定义出右偏树类型 <code class=\"language-text\">FoldRight</code>，它有两个<ruby>类型构造器<rp>(</rp><rt>type constructor</rt><rp>)</rp></ruby>：</p>\n<ul>\n<li>第一个是 <code class=\"language-text\">Compute x y</code>，其中 <code class=\"language-text\">x</code> 是列表中的元素，<code class=\"language-text\">y</code> 是嵌套的 <code class=\"language-text\">FoldRight a</code>，表示计算；</li>\n<li>第二个是 <code class=\"language-text\">Stop</code> 表示已经到了尽头，不需要计算了。</li>\n</ul>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token keyword\">data</span> <span class=\"token constant\">FoldRight</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Compute</span> <span class=\"token hvariable\">a</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">FoldRight</span> <span class=\"token hvariable\">a</span><span class=\"token punctuation\">)</span>\n                 <span class=\"token operator\">|</span> <span class=\"token constant\">Stop</span></code></pre></div>\n<p>我们可以轻松用 <code class=\"language-text\">foldr</code> 写出从数组中构建 <code class=\"language-text\">FoldRight</code> 的函数。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">makeFold</span> <span class=\"token operator\">::</span> <span class=\"token punctuation\">[</span><span class=\"token hvariable\">a</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">-></span> <span class=\"token constant\">FoldRight</span> <span class=\"token hvariable\">a</span>\n<span class=\"token hvariable\">makeFold</span> <span class=\"token operator\">=</span> <span class=\"token builtin\">foldr</span> <span class=\"token hvariable\">f</span> <span class=\"token constant\">Stop</span>\n    <span class=\"token keyword\">where</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span> <span class=\"token operator\">=</span> <span class=\"token constant\">Compute</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span></code></pre></div>\n<p>有人可能会问，之所以没有把类型 <code class=\"language-text\">b</code> 写进去，是因为我们接下来要按照 <code class=\"language-text\">foldl</code> 的顺序执行计算，也就是 <code class=\"language-text\">evalFold</code> 函数。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">evalFold</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\">a</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 constant\">FoldRight</span> <span class=\"token hvariable\">b</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span>\n<span class=\"token hvariable\">evalFold</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token constant\">Stop</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span>\n<span class=\"token hvariable\">evalFold</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Compute</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">evalFold</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span> <span class=\"token hvariable\">y</span></code></pre></div>\n<p><code class=\"language-text\">evalFold</code> 函数做的事其实很简单，就是把 <code class=\"language-text\">FoldRight</code> 结构按照 <code class=\"language-text\">foldl</code> 的顺序进行运算。有了这 <code class=\"language-text\">makeFold</code> 和 <code class=\"language-text\">evalFold</code> 我们就可以用 <code class=\"language-text\">foldr</code> 实现出 <code class=\"language-text\">foldl</code>。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token builtin\">foldl</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\">a</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 punctuation\">[</span><span class=\"token hvariable\">b</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span>\n<span class=\"token builtin\">foldl</span>' <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">xs</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">evalFold</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">makeFold</span> <span class=\"token hvariable\">xs</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>读者可能会问：虽然你实现出来了，但形式太复杂了，而且书中给出的答案并没有构造中间数据结构。别急，还记得上一节我们提到的可以用高阶函数来表示数据结构吗？我们可以重写 <code class=\"language-text\">FoldRight</code> 的定义，使之用起来一模一样，但本质上是个函数。</p>\n<p>在开始之前，我们要调整一下 <code class=\"language-text\">evalFold</code> 函数的参数的顺序，把 <code class=\"language-text\">FoldRight b</code> 变成第一个参数。为了表达清楚，我干脆就把调整过后的函数写出来好了。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">evalFold'</span> <span class=\"token operator\">::</span> <span class=\"token constant\">FoldRight</span> <span class=\"token hvariable\">b</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\">a</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\">a</span>\n<span class=\"token hvariable\">evalFold'</span> <span class=\"token constant\">Stop</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span>\n<span class=\"token hvariable\">evalFold'</span> <span class=\"token punctuation\">(</span><span class=\"token constant\">Compute</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span><span class=\"token punctuation\">)</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">evalFold'</span> <span class=\"token hvariable\">y</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>把 <code class=\"language-text\">FoldRight b</code> 调整到第一个后，<code class=\"language-text\">evalFold'</code> 函数其实是在对 <code class=\"language-text\">FoldRight</code> 的两种<em>构造</em>进行分情况讨论。如果我们用函数重写了 <code class=\"language-text\">FoldRight</code> 结构，那么其实可以把每种情况的<em>构造</em>和<em>计算</em>合并成一个函数。</p>\n<p>第一步，重写 <code class=\"language-text\">evalFold' Stop f z</code>。我们可以直接抹掉 <code class=\"language-text\">evalFold'</code> 这个名字，把 <code class=\"language-text\">Stop</code> 改成小写。这样就得到了 <code class=\"language-text\">stop</code> 函数的定义。你可能会开始考虑类型，但这里不妨再等一下，等重写完后，我们再来看类型能不能说得通。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">stop</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span></code></pre></div>\n<p>第二步，重写 <code class=\"language-text\">evalFold' (Compute x y) f z</code>。同样，我们可以直接抹掉 <code class=\"language-text\">evalFold</code> 这个名字，把 <code class=\"language-text\">Compute</code> 改成小写，并去掉构造的括号，我们就得到了 <code class=\"language-text\">compute</code> 的定义。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">compute</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">y</span> <span class=\"token operator\">$</span> <span class=\"token hvariable\">f</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>在检查类型前，我们发现 <code class=\"language-text\">f</code> 在整个过程中保持不变，不如把它当作类似全局变量的东西，所以我们可以直接从两个函数中删掉它。得到新版的 <code class=\"language-text\">stop</code> 和 <code class=\"language-text\">compute</code> 定义。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">compute</span> <span class=\"token hvariable\">x</span> <span class=\"token hvariable\">y</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">y</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">x</span><span class=\"token punctuation\">)</span>\n<span class=\"token hvariable\">stop</span> <span class=\"token hvariable\">z</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">z</span></code></pre></div>\n<p>这个定义就非常简洁了，<code class=\"language-text\">stop</code> 就是一个 <code class=\"language-text\">id</code> 函数，我们尝试写出它们的类型，发现完全可以跑得通。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token hvariable\">compute</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">b</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\">a</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\">a</span>\n<span class=\"token hvariable\">stop</span> <span class=\"token operator\">::</span> <span class=\"token hvariable\">a</span> <span class=\"token operator\">-></span> <span class=\"token hvariable\">a</span></code></pre></div>\n<p>观察一下结构，其实 <code class=\"language-text\">a -> a</code> 就是用于代替 <code class=\"language-text\">FoldRight</code> 的类型。接下来，把我们新的实现带回到上面的实现中，经过下面的三步替换，就可以得到书中的版本了。</p>\n<div class=\"gatsby-highlight\" data-language=\"haskell\"><pre class=\"language-haskell\"><code class=\"language-haskell\"><span class=\"token builtin\">foldl</span>'   <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">xs</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">evalFold</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">makeFold</span> <span class=\"token hvariable\">xs</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">-- Step 1</span>\n<span class=\"token builtin\">foldl</span>''  <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">xs</span> <span class=\"token operator\">=</span> <span class=\"token hvariable\">evalFold'</span> <span class=\"token punctuation\">(</span><span class=\"token hvariable\">makeFold</span> <span class=\"token hvariable\">xs</span><span class=\"token punctuation\">)</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token comment\">-- Step 2</span>\n<span class=\"token builtin\">foldl</span>''' <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token hvariable\">xs</span> <span class=\"token operator\">=</span> <span class=\"token builtin\">foldr</span> <span class=\"token hvariable\">compute</span> <span class=\"token hvariable\">stop</span> <span class=\"token hvariable\">f</span> <span class=\"token hvariable\">z</span> <span class=\"token comment\">-- Step 3</span></code></pre></div>\n<h2>作业</h2>\n<p>是不是很神奇？来尝试做一下下面的作业吧。</p>\n<ol>\n<li>请尝试用类似的方法，用 <code class=\"language-text\">foldl</code> 实现 <code class=\"language-text\">foldr</code>。</li>\n<li>完成 1 后，思考 <code class=\"language-text\">foldl</code> 和 <code class=\"language-text\">foldr</code> 之间的关联。</li>\n</ol>","frontmatter":{"title":"Rewrite foldl with foldr, explained","date":"August 26, 2018","tags":["haskell","functional-programming"],"language":"zh-Hans"}},"site":{"siteMetadata":{"title":"Luyu Cheng"}}},"pageContext":{"id":"be76e1b7-a720-5afc-aca4-cecc701a2e88"}},"staticQueryHashes":["3649515864","63159454","684267432"]}