<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Conal Elliott &#187; isomorphism</title>
	<atom:link href="http://conal.net/blog/tag/isomorphism/feed" rel="self" type="application/rss+xml" />
	<link>http://conal.net/blog</link>
	<description>Inspirations &#38; experiments, mainly about denotative/functional programming in Haskell</description>
	<lastBuildDate>Thu, 25 Jul 2019 18:15:11 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.1.17</generator>
	<atom:link rel="payment" title="Flattr this!" href="https://flattr.com/submit/auto?user_id=conal&amp;popout=1&amp;url=http%3A%2F%2Fconal.net%2Fblog%2F&amp;language=en_US&amp;category=text&amp;title=Conal+Elliott&amp;description=Inspirations+%26amp%3B+experiments%2C+mainly+about+denotative%2Ffunctional+programming+in+Haskell&amp;tags=blog" type="text/html" />
	<item>
		<title>Memoizing higher-order functions</title>
		<link>http://conal.net/blog/posts/memoizing-higher-order-functions</link>
		<comments>http://conal.net/blog/posts/memoizing-higher-order-functions#comments</comments>
		<pubDate>Wed, 21 Jul 2010 15:41:17 +0000</pubDate>
		<dc:creator><![CDATA[Conal]]></dc:creator>
				<category><![CDATA[Functional programming]]></category>
		<category><![CDATA[isomorphism]]></category>
		<category><![CDATA[memoization]]></category>
		<category><![CDATA[trie]]></category>

		<guid isPermaLink="false">http://conal.net/blog/?p=119</guid>
		<description><![CDATA[Memoization incrementally converts functions into data structures. It pays off when a function is repeatedly applied to the same arguments and applying the function is more expensive than accessing the corresponding data structure. In lazy functional memoization, the conversion from function to data structure happens all at once from a denotational perspective, and incrementally from [&#8230;]]]></description>
				<content:encoded><![CDATA[<!-- teaser -->

<p
>Memoization incrementally converts functions into data structures. It pays off when a function is repeatedly applied to the same arguments and applying the function is more expensive than accessing the corresponding data structure.</p
>

<p
>In <em
  >lazy functional</em
  > memoization, the conversion from function to data structure happens all at once from a denotational perspective, and incrementally from an operational perspective. See <em
  ><a href="http://conal.net/blog/posts/elegant-memoization-with-functional-memo-tries/" title="blog post"
    >Elegant memoization with functional memo tries</a
    ></em
  > and <em
  ><a href="http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/" title="blog post"
    >Elegant memoization with higher-order types</a
    ></em
  >.</p
>

<p
>As Ralf Hinze presented in <em
  ><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.8.4069" title="Paper: &quot;Generalizing Generalized Tries&quot; by Ralf Hinze"
    >Generalizing Generalized Tries</a
    ></em
  >, trie-based memoization follows from three simple isomorphisms involving functions types:</p
>

<div class=math-inset>
<p
><span class="math"
  >1 → <em
    >a</em
    > ≅ <em
    >a</em
    ></span
  ></p
><p
><span class="math"
  >(<em
    >a</em
    > + <em
    >b</em
    >) → <em
    >c</em
    > ≅ (<em
    >a</em
    > → <em
    >c</em
    >) × (<em
    >b</em
    > → <em
    >c</em
    >)</span
  ></p
><p
><span class="math"
  >(<em
    >a</em
    > × <em
    >b</em
    >) → <em
    >c</em
    > ≅ <em
    >a</em
    > → (<em
    >b</em
    > → <em
    >c</em
    >)</span
  ></p
></div>

<p
>which correspond to the familiar laws of exponents</p
>

<div class=math-inset>
<p
><span class="math"
  >a ^ 1 = a</span
  ></p
><p
><span class="math"
  ><em
    >c</em
    ><sup
    ><em
      >a</em
      > + <em
      >b</em
      ></sup
    > = <em
    >c</em
    ><sup
    ><em
      >a</em
      ></sup
    > × <em
    >c</em
    ><sup
    ><em
      >b</em
      ></sup
    ></span
  ></p
><p
><span class="math"
  ><em
    >c</em
    ><sup
    ><em
      >a</em
      > × <em
      >b</em
      ></sup
    > = (<em
    >c</em
    ><sup
    ><em
      >b</em
      ></sup
    >)<sup
    ><em
      >a</em
      ></sup
    ></span
  ></p
></div>

<p
>When applied as a transformation from left to right, each law simplifies the domain part of a function type. Repeated application of the rules then eliminate all function types or reduce them to functions of atomic types. These atomic domains are eliminated as well by additional mappings, such as between a natural number and a list of bits (as in <a href="http://hackage.haskell.org/packages/archive/containers/0.2.0.1/doc/html/Data-IntMap.html" title="hackage package"
  >patricia trees</a
  >). Algebraic data types corresponding to sums of products and so are eliminated by the sum and product rules. <em
  >Recursive</em
  > algebraic data types (lists, trees, etc) give rise to correspondingly recursive trie types.</p
>

<p
>So, with a few simple and familiar rules, we can memoize functions over an infinite variety of common types. Have we missed any?</p
>

<p
>Yes. <em
  >What about functions over functions?</em
  ></p
>

<p
><strong
  >Edits</strong
  >:</p
>

<ul
><li
  >2010-07-22: Made the memoization example polymorphic and switched from pairs to lists. The old example accidentally coincided with a specialized version of <code
    >trie</code
    > itself.</li
  ><li
  >2011-02-27: updated some notation</li
  ></ul
>

<p><span id="more-119"></span></p>

<div id="tries"
><h3
  >Tries</h3
  ><p
  >In <em
    ><a href="http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/" title="blog post"
      >Elegant memoization with higher-order types</a
      ></em
    >, I showed a formulation of functional memoization using functor combinators.</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >type</span
      > k &#8603; v <span class="fu"
      >=</span
      > <span class="dt"
      >Trie</span
      > k v<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >class</span
      > <span class="dt"
      >HasTrie</span
      > k <span class="kw"
      >where</span
      ><br
       />  <span class="kw"
      >type</span
      > <span class="dt"
      >Trie</span
      > k <span class="dv"
      >&#8759;</span
      > <span class="fu"
      >*</span
      > &#8594; <span class="fu"
      >*</span
      ><br
       />  trie   <span class="dv"
      >&#8759;</span
      > (k &#8594; v) &#8594; (k &#8603; v)<br
       />  untrie <span class="dv"
      >&#8759;</span
      > (k &#8603; v) &#8594; (k &#8594; v)<br
       /></code
    ></pre
  ><p
  >I will describe higher-order memoization in terms of this formulation. I imagine it would also work out, though less elegantly, in the associated data types formulation described in <em
    ><a href="http://conal.net/blog/posts/elegant-memoization-with-functional-memo-tries/" title="blog post"
      >Elegant memoization with functional memo tries</a
      ></em
    >.</p
  ></div
>

<div id="domain-isomorphisms"
><h3
  >Domain isomorphisms</h3
  ><p
  ><em
    ><a href="http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/" title="blog post"
      >Elegant memoization with higher-order types</a
      ></em
    > showed how to define a <code
    >HasTrie</code
    > instance in terms of the instance of an isomorphic type, e.g., reducing tuples to nested pairs or booleans to a sum of unit types. A C macro, <code
    >HasTrieIsomorph</code
    > encapsulates the domain isomorphism technique. For instance, to reduce triples to pairs:</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( (<span class="dt"
      >HasTrie</span
      > a, <span class="dt"
      >HasTrie</span
      > b, <span class="dt"
      >HasTrie</span
      > c), (a,b,c), ((a,b),c)<br
       />               , &#955; (a,b,c) &#8594; ((a,b),c), &#955; ((a,b),c) &#8594; (a,b,c))<br
       /></code
    ></pre
  ><p
  >This isomorphism technique applies as well to the standard functor combinators used for constructing tries (any many other purposes). Those combinators again:</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >data</span
      > <span class="dt"
      >Const</span
      > x a <span class="fu"
      >=</span
      > <span class="dt"
      >Const</span
      > x<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >data</span
      > <span class="dt"
      >Id</span
      > a <span class="fu"
      >=</span
      > <span class="dt"
      >Id</span
      > a<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >data</span
      > (f &#215; g) a <span class="fu"
      >=</span
      > f a &#215; g a<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >data</span
      > (f <span class="fu"
      >+</span
      > g) a <span class="fu"
      >=</span
      > <span class="dt"
      >InL</span
      > (f a) <span class="fu"
      >|</span
      > <span class="dt"
      >InR</span
      > (g a)<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="kw"
      >newtype</span
      > (g &#8728; f) a <span class="fu"
      >=</span
      > <span class="dt"
      >O</span
      > (g (f a))<br
       /></code
    ></pre
  ><p
  >and their trie definitions:</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( <span class="dt"
      >HasTrie</span
      > a, <span class="dt"
      >Const</span
      > a x, a, getConst, <span class="dt"
      >Const</span
      > )<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( <span class="dt"
      >HasTrie</span
      > a, <span class="dt"
      >Id</span
      > a, a, unId, <span class="dt"
      >Id</span
      > )<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( (<span class="dt"
      >HasTrie</span
      > (f a), <span class="dt"
      >HasTrie</span
      > (g a))<br
       />               , (f &#215; g) a, (f a,g a)<br
       />               , &#955; (fa &#215; ga) &#8594; (fa,ga), &#955; (fa,ga) &#8594; (fa &#215; ga) )<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( (<span class="dt"
      >HasTrie</span
      > (f a), <span class="dt"
      >HasTrie</span
      > (g a))<br
       />               , (f <span class="fu"
      >+</span
      > g) a, <span class="dt"
      >Either</span
      > (f a) (g a)<br
       />               , eitherF <span class="kw"
      >Left</span
      > <span class="kw"
      >Right</span
      >, <span class="fu"
      >either</span
      > <span class="dt"
      >InL</span
      > <span class="dt"
      >InR</span
      > )<br
       /></code
    ></pre
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( <span class="dt"
      >HasTrie</span
      > (g (f a))<br
       />               , (g &#8728; f) a, g (f a) , unO, <span class="dt"
      >O</span
      > )<br
       /></code
    ></pre
  ><p
  >The <code
    >eitherF</code
    > function is a variation on <a href="http://haskell.org/ghc/docs/6.12.1/html/libraries/base-4.2.0.0/Prelude.html#v:either"
    ><code
      >either</code
      ></a
    >:</p
  ><pre class="sourceCode haskell"
  ><code
    >eitherF <span class="dv"
      >&#8759;</span
      > (f a &#8594; b) &#8594; (g a &#8594; b) &#8594; (f <span class="fu"
      >+</span
      > g) a &#8594; b<br
       />eitherF p _ (<span class="dt"
      >InL</span
      > fa) <span class="fu"
      >=</span
      > p fa<br
       />eitherF _ q (<span class="dt"
      >InR</span
      > ga) <span class="fu"
      >=</span
      > q ga<br
       /></code
    ></pre
  ></div
>

<div id="higher-order-memoization"
><h3
  >Higher-order memoization</h3
  ><p
  >Now higher-order memoization is easy. Apply yet another isomorphism, this time between functions and tries: The <code
    >trie</code
    > and <code
    >untrie</code
    > methods are exactly the mappings we need.</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="dt"
      >HasTrieIsomorph</span
      >( (<span class="dt"
      >HasTrie</span
      > a, <span class="dt"
      >HasTrie</span
      > (a &#8603; b))<br
       />               , a &#8594; b, a &#8603; b, trie, untrie)<br
       /></code
    ></pre
  ><p
  >So, to memoize a higher-order function <code
    >f &#8759; (a &#8594; b) &#8594; v</code
    >, we only a trie type for <code
    >a</code
    > and one for <code
    >a &#8603; b</code
    >. The latter (tries for trie-valued domains) are provided by the isomorphisms above, and additional ones.</p
  ></div
>

<div id="demo"
><h3
  >Demo</h3
  ><p
  >Our sample higher-order function will take a function of booleans and yield its value at <code
    >False</code
    > and at <code
    >True</code
    >:</p
  ><pre class="sourceCode haskell"
  ><code
    >ft1 <span class="dv"
      >&#8759;</span
      > (<span class="dt"
      >Bool</span
      > &#8594; a) &#8594; [a]<br
       />ft1 f <span class="fu"
      >=</span
      > [f <span class="kw"
      >False</span
      >, f <span class="kw"
      >True</span
      >]<br
       /></code
    ></pre
  ><p
  >A sample input converts <code
    >False</code
    > to <code
    >0</code
    > and <code
    >True</code
    > to <code
    >1</code
    >:</p
  ><pre class="sourceCode haskell"
  ><code
    >f1 <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >Bool</span
      > &#8594; <span class="dt"
      >Int</span
      ><br
       />f1 <span class="kw"
      >False</span
      > <span class="fu"
      >=</span
      > <span class="dv"
      >0</span
      ><br
       />f1 <span class="kw"
      >True</span
      >  <span class="fu"
      >=</span
      > <span class="dv"
      >1</span
      ><br
       /></code
    ></pre
  ><p
  >A sample run without memoization:</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="fu"
      >*</span
      ><span class="dt"
      >FunctorCombo.MemoTrie</span
      ><span class="fu"
      >&gt;</span
      > ft1 f1<br
       />[<span class="dv"
      >0</span
      >,<span class="dv"
      >1</span
      >]<br
       /></code
    ></pre
  ><p
  >and one with memoization:</p
  ><pre class="sourceCode haskell"
  ><code
    ><span class="fu"
      >*</span
      ><span class="dt"
      >FunctorCombo.MemoTrie</span
      ><span class="fu"
      >&gt;</span
      > memo ft1 f1<br
       />[<span class="dv"
      >0</span
      >,<span class="dv"
      >1</span
      >]<br
       /></code
    ></pre
  ><p
  >To illustrate what's going on behind the scenes, the following definitions (all of which type-check) progressively reveal the representation of the underlying memo trie. Most steps result from inlining a single <code
    >Trie</code
    > definition (as well as switching between <code
    >Trie k v</code
    > and the synonymous form <code
    >k &#8603; v</code
    >).</p
  ><pre class="sourceCode haskell"
  ><code
    >trie1a <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (<span class="dt"
      >Bool</span
      > &#8594; a) &#8603; (a, a)<br
       />trie1a <span class="fu"
      >=</span
      > trie ft1<br
       /><br
       />trie1b <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (<span class="dt"
      >Bool</span
      > &#8603; a) &#8603; (a, a)<br
       />trie1b <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1c <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (<span class="dt"
      >Either</span
      > () () &#8603; a) &#8603; (a, a)<br
       />trie1c <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1d <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; ((<span class="dt"
      >Trie</span
      > () &#215; <span class="dt"
      >Trie</span
      > ()) a) &#8603; (a, a)<br
       />trie1d <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1e <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (<span class="dt"
      >Trie</span
      > () a, <span class="dt"
      >Trie</span
      > () a) &#8603; (a, a)<br
       />trie1e <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1f <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (() &#8603; a, () &#8603; a) &#8603; (a, a)<br
       />trie1f <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1g <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (a, a) &#8603; (a, a)<br
       />trie1g <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1h <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; (<span class="dt"
      >Trie</span
      > a &#8728; <span class="dt"
      >Trie</span
      > a) (a, a)<br
       />trie1h <span class="fu"
      >=</span
      > trie1a<br
       /><br
       />trie1i <span class="dv"
      >&#8759;</span
      > <span class="dt"
      >HasTrie</span
      > a &#8658; a &#8603; a &#8603; (a, a)<br
       />trie1i <span class="fu"
      >=</span
      > unO trie1a<br
       /></code
    ></pre
  ></div
>

<div id="pragmatics"
><h3
  >Pragmatics</h3
  ><p
  >I'm happy with the correctness and elegance of the method in this post. It gives me the feeling of inevitable simplicity that I strive for -- obvious in hindsight. What about performance? After all, memoization is motivated by a desire to efficiency -- specifically, to reduce the cost of repeatedly applying the same function to the same argument, while keeping almost all of the modularity &amp; simplicity of a naïve algorithm.</p
  ><p
  >Memoization pays off when (a) a function is repeatedly applied to some arguments, and (b) when the cost of recomputing an application exceeds the cost of finding the previously computed result. (I'm over-simplifying here. Space efficiency matters also and can affect time efficiency.) The isomorphism technique used in this post and <a href="http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/" title="blog post"
    >a previous one</a
    > requires transforming an argument to the isomorphic type for each look-up and from the isomorphic type for each application. (I'm using &quot;isomorphic type&quot; to mean the type for which a <code
    >HasTrie</code
    > instance is already defined.) When these transformations are between function and trie form, I wonder how high the break-even threshold becomes.</p
  ><p
  >How might we avoid these transformations, thus reducing the overhead of memoizing?</p
  ><p
  >For conversion to isomorphic type during trie lookup, perhaps the cost could be reduced substantially through deforestation--inlining chains of <code
    >untrie</code
    > methods and applying optimizations to eliminate the many intermediate representation layers. GHC has gotten awfully good at this sort of thing. Maybe someone with more Haskell performance analysis &amp; optimization experience than I have would be interested in collaborating.</p
  ><p
  >For trie construction, I suspect the conversion <em
    >back</em
    > from the isomorphic type could be avoided by somehow holding onto the original form of the argument, before it was converted to the isomorphic type. I haven't attempted this idea yet.</p
  ><p
  >Another angle on reducing the cost of the isomorphism technique is to use memoization! After all, if memoizing is worthwhile at all, there will be repeated applications of the memoized function to the same arguments. Exactly in such a case, the conversion of arguments to isomorphic form will also be done repeatedly for these same arguments. When a conversion is both expensive and repeated, we'd like to memoize. I don't know how to get off the ground with this idea, however. If I'm trying to memoize a function of type <code
    >a &#8594; b</code
    >, then the required conversion has type <code
    >a &#8594; a'</code
    > for some type <code
    >a'</code
    > with a <code
    >HasTrie</code
    > instance. Memoizing that conversion is just as hard as memoizing the function we started with.</p
  ></div
>

<div id="conclusion"
><h3
  >Conclusion</h3
  ><p
  >Existing accounts of functional memoization I know of cover functions of the unit type, sums, and products, and they do so quite elegantly.</p
  ><p
  ><em
    >Type isomorphisms</em
    > form the consistent, central theme in this work. Functions from unit, sums and products have isomorphic forms with simpler domain types (and so on, recursively). Additional isomorphisms extend these fundamental building blocks to many other types, including integer types and algebraic data types. However, functions over function-valued domains are conspicuously missing (though I hadn't noticed until recently). This post fills that gap neatly, using yet another isomorphism, and moreover an isomorphism that has been staring us in the face all along: the one between functions and tries.</p
  ><p
  >I wonder:</p
  ><ul
  ><li
    >Given how this trick shouts to be noticed, has it been discovered and written up?</li
    ><li
    >How useful will higher-order memoization turn out to be?</li
    ><li
    >How efficient is the straightforward implementation given above?</li
    ><li
    >Can the conversions between isomorphic domain types be done inexpensively, perhaps eliminating many altogether?</li
    ><li
    >How does [non-strict memoization][] fit in with higher-order memoization?</li
    ></ul
  ></div
>
<p><a href="http://conal.net/blog/?flattrss_redirect&amp;id=119&amp;md5=38c9c222d1cfdb6bbafc4cb90a393efa"><img src="http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png" srcset="http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png, http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white@2x.png 2xhttp://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png, http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white@3x.png 3x" alt="Flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://conal.net/blog/posts/memoizing-higher-order-functions/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<atom:link rel="payment" title="Flattr this!" href="https://flattr.com/submit/auto?user_id=conal&amp;popout=1&amp;url=http%3A%2F%2Fconal.net%2Fblog%2Fposts%2Fmemoizing-higher-order-functions&amp;language=en_GB&amp;category=text&amp;title=Memoizing+higher-order+functions&amp;description=Memoization+incrementally+converts+functions+into+data+structures.+It+pays+off+when+a+function+is+repeatedly+applied+to+the+same+arguments+and+applying+the+function+is+more+expensive+than+accessing+the...&amp;tags=isomorphism%2Cmemoization%2Ctrie%2Cblog" type="text/html" />
	</item>
		<item>
		<title>Elegant memoization with higher-order types</title>
		<link>http://conal.net/blog/posts/elegant-memoization-with-higher-order-types</link>
		<comments>http://conal.net/blog/posts/elegant-memoization-with-higher-order-types#comments</comments>
		<pubDate>Wed, 21 Jul 2010 04:48:22 +0000</pubDate>
		<dc:creator><![CDATA[Conal]]></dc:creator>
				<category><![CDATA[Functional programming]]></category>
		<category><![CDATA[functor]]></category>
		<category><![CDATA[isomorphism]]></category>
		<category><![CDATA[memoization]]></category>
		<category><![CDATA[trie]]></category>

		<guid isPermaLink="false">http://conal.net/blog/?p=117</guid>
		<description><![CDATA[A while back, I got interested in functional memoization, especially after seeing some code from Spencer Janssen using the essential idea of Ralf Hinze&#8217;s paper Generalizing Generalized Tries. The blog post Elegant memoization with functional memo tries describes a library, MemoTrie, based on both of these sources, and using associated data types. I would have [&#8230;]]]></description>
				<content:encoded><![CDATA[<!-- 

Title: Elegant memoization with higher-order types

Tags: functor, memoization, isomorphism, trie

URL: http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/

-->

<!-- references -->

<!-- teaser -->

<p>A while back, I got interested in functional memoization, especially after seeing some code from Spencer Janssen using the essential idea of Ralf Hinze&#8217;s paper <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.8.4069" title="Paper: &quot;Generalizing Generalized Tries&quot; by Ralf Hinze">Generalizing Generalized Tries</a></em>.
The blog post <em><a href="http://conal.net/blog/posts/elegant-memoization-with-functional-memo-tries/" title="blog post">Elegant memoization with functional memo tries</a></em> describes a library, <a href="http://haskell.org/haskellwiki/MemoTrie" title="Haskell wiki page for the MemoTrie library">MemoTrie</a>, based on both of these sources, and using <a href="http://www.cse.unsw.edu.au/~chak/papers/papers.html#assoc" title="Paper: &quot;Associated Types with Class&quot;">associated data types</a>.
I would have rather used associated type synonyms and standard types, but I couldn&#8217;t see how to get the details to work out.
Recently, while playing with functor combinators, I realized that they might work for memoization, which they do quite nicely.</p>

<p>This blog post shows how functor combinators lead to an even more elegant formulation of functional memoization.
The code is available as part of the <a href="http://hackage.haskell.org/package/functor-combo" title="Hackage entry: functor-combo">functor-combo</a> package.</p>

<p>The techniques in this post are not so much new as they are ones that have recently been sinking in for me.
See <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.8.4069" title="Paper: &quot;Generalizing Generalized Tries&quot; by Ralf Hinze">Generalizing Generalized Tries</a></em>, as well as <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.140.2412" title="Paper by Alexey Rodriguez, Stefan Holdermans, Andres Löh, and Johan Jeuring">Generic programming with fixed points for mutually recursive datatypes</a></em>.</p>

<p><strong>Edits</strong>:</p>

<ul>
<li>2011-01-28: Fixed small typo: &#8220;<em>b^^a^^</em>&#8221; ⟼ &#8220;<em>b<sup>a</sup></em>&#8220;</li>
<li>2010-09-10: Corrected <code>Const</code> definition to use <code>newtype</code> instead of <code>data</code>.</li>
<li>2010-09-10: Added missing <code>Unit</code> type definition (as <code>Const ()</code>).</li>
</ul>

<!-- without a comment or something here, the last item above becomes a paragraph -->

<p><span id="more-117"></span></p>

<h3>Tries as associated data type</h3>

<p>The <a href="http://haskell.org/haskellwiki/MemoTrie" title="Haskell wiki page for the MemoTrie library">MemoTrie</a> library is centered on a class <code>HasTrie</code> with an associated data type of tries (efficient indexing structures for memoized functions):</p>

<pre><code>class HasTrie k where
    data (:→:) k :: * → *
    trie   :: (k  →  v) → (k :→: v)
    untrie :: (k :→: v) → (k  →  v)
</code></pre>

<p>The type <code>a :→: b</code> represents a trie that maps values of type <code>a</code> to values of type <code>b</code>.
The trie representation depends only on <code>a</code>.</p>

<p>Memoization is a simple combination of these two methods:</p>

<pre><code>memo :: HasTrie a ⇒ (a → b) → (a → b)
memo = untrie . trie
</code></pre>

<p>The <code>HasTrie</code> instance definitions correspond to isomorphisms invoving function types.
The isomorphisms correspond to the familiar rules of exponents, if we translate <em>a → b</em> into <em>b<sup>a</sup></em>.
(See <em><a href="http://conal.net/blog/posts/elegant-memoization-with-functional-memo-tries/" title="blog post">Elegant memoization with functional memo tries</a></em> for more explanation.)</p>

<pre><code>instance HasTrie () where
    data () :→: x = UnitTrie x
    trie f = UnitTrie (f ())
    untrie (UnitTrie x) = const x

instance (HasTrie a, HasTrie b) ⇒ HasTrie (Either a b) where
    data (Either a b) :→: x = EitherTrie (a :→: x) (b :→: x)
    trie f = EitherTrie (trie (f . Left)) (trie (f . Right))
    untrie (EitherTrie s t) = either (untrie s) (untrie t)

instance (HasTrie a, HasTrie b) ⇒ HasTrie (a,b) where
    data (a,b) :→: x = PairTrie (a :→: (b :→: x))
    trie f = PairTrie (trie (trie . curry f))
    untrie (PairTrie t) = uncurry (untrie .  untrie t)
</code></pre>

<h3>Functors and functor combinators</h3>

<p>For notational convenience, let &#8220;<code>(:→:)</code>&#8221; be a synonym for &#8220;<code>Trie</code>&#8220;:</p>

<pre><code>type k :→: v = Trie k v
</code></pre>

<p>And replace the associated <code>data</code> with an associated <code>type</code>.</p>

<pre><code>class HasTrie k where
    type Trie k :: * → *
    trie   :: (k  →  v) → (k :→: v)
    untrie :: (k :→: v) → (k  →  v)
</code></pre>

<p>Then, imitating the three <code>HasTrie</code> instances above,</p>

<pre><code>type Trie () v = v

type Trie (Either a b) v = (Trie a v, Trie b v)

type Trie (a,b) v = Trie a (Trie b v)
</code></pre>

<p>Imagine that we have type lambdas for writing higher-kinded types.</p>

<pre><code>type Trie () = λ v → v

type Trie (Either a b) = λ v → (Trie a v, Trie b v)

type Trie (a,b) = λ v → Trie a (Trie b v)
</code></pre>

<p>Type lambdas are often written as &#8220;Λ&#8221; (capital &#8220;λ&#8221;) instead.
In the land of values, these three right-hand sides correspond to common building blocks for functions, namely identity, product, and composition:</p>

<pre><code>id      = λ v → v
f *** g = λ v → (f v, g v)
g  .  f = λ v → g (f v)
</code></pre>

<p>These building blocks arise in the land of types.</p>

<pre><code>newtype Id a = Id a

data (f :*: g) a = f a :*: g a

newtype (g :. f) a = O (g (f a))
</code></pre>

<p>where <code>Id</code>, <code>f</code> and <code>g</code> are functors.
Sum and a constant functor are also common building blocks:</p>

<pre><code>data (f :+: g) a = InL (f a) | InR (g a)

newtype Const x a = Const x

type Unit = Const () -- one non-⊥ inhabitant
</code></pre>

<h3>Tries as associated type synonym</h3>

<p>Given these standard definitions, we can eliminate the special-purpose data types used, replacing them with our standard functor combinators:</p>

<pre><code>instance HasTrie () where
  type Trie ()  = Id
  trie   f      = Id (f ())
  untrie (Id v) = const v

instance (HasTrie a, HasTrie b) =&gt; HasTrie (Either a b) where
  type Trie (Either a b) = Trie a :*: Trie b
  trie   f           = trie (f . Left) :*: trie (f . Right)
  untrie (ta :*: tb) = untrie ta `either` untrie tb

instance (HasTrie a, HasTrie b) ⇒ HasTrie (a , b) where
  type Trie (a , b) = Trie a :. Trie b
  trie   f      = O (trie (trie . curry f))
  untrie (O tt) = uncurry (untrie . untrie tt)
</code></pre>

<p>At first blush, it might appear that we&#8217;ve simply moved the data type definitions outside of the instances.
However, the extracted functor combinators have other uses, as explored in polytypic programming.
I&#8217;ll point out some of these uses in the next few blog posts.</p>

<h3>Isomorphisms</h3>

<p>Many types are isomorphic variations, and so their corresponding tries can share a common representation.
For instance, triples are isomorphic to nested pairs:</p>

<pre><code>detrip :: (a,b,c) → ((a,b),c)
detrip (a,b,c) = ((a,b),c)

trip :: ((a,b),c) → (a,b,c)
trip ((a,b),c) = (a,b,c)
</code></pre>

<p>A trie for triples can be a a trie for pairs (already defined).
The <code>trie</code> and <code>untrie</code> methods then just perform conversions around the corresponding methods on pairs:</p>

<pre><code>instance (HasTrie a, HasTrie b, HasTrie c) ⇒ HasTrie (a,b,c) where
    type Trie (a,b,c) = Trie ((a,b),c)
    trie f = trie (f . trip)
    untrie t = untrie t . detrip
</code></pre>

<p>All type isomorphisms can use this same pattern.
I don&#8217;t think Haskell is sufficiently expressive to capture this pattern within the language, so I&#8217;ll resort to a C macro.
There are five parameters:</p>

<ul>
<li><code>Context</code>: the instance context;</li>
<li><code>Type</code>: the type whose instance is being defined;</li>
<li><code>IsoType</code>: the isomorphic type;</li>
<li><code>toIso</code>: conversion function <em>to</em> <code>IsoType</code>; and</li>
<li><code>fromIso</code>: conversion function <em>from</em> <code>IsoType</code>.</li>
</ul>

<p>The macro:</p>

<pre><code>#define HasTrieIsomorph(Context,Type,IsoType,toIso,fromIso)  
instance Context ⇒ HasTrie (Type) where {  
  type Trie (Type) = Trie (IsoType);  
  trie f = trie (f . (fromIso));  
  untrie t = untrie t . (toIso);  
}
</code></pre>

<p>Now we can easily define <code>HasTrie</code> instances:</p>

<pre><code>HasTrieIsomorph( (), Bool, Either () ()
               ,  c -&gt; if c then Left () else Right ()
               , either ( () -&gt; True) ( () -&gt; False))

HasTrieIsomorph( (HasTrie a, HasTrie b, HasTrie c), (a,b,c), ((a,b),c)
               , λ (a,b,c) → ((a,b),c), λ ((a,b),c) → (a,b,c))

HasTrieIsomorph( (HasTrie a, HasTrie b, HasTrie c, HasTrie d)
               , (a,b,c,d), ((a,b,c),d)
               , λ (a,b,c,d) → ((a,b,c),d), λ ((a,b,c),d) → (a,b,c,d))
</code></pre>

<p>In most (but not all) cases, the first argument (<code>Context</code>) could simply be that the isomorphic type <code>HasTrie</code>, e.g.,</p>

<pre><code>HasTrieIsomorph( HasTrie ((a,b),c), (a,b,c), ((a,b),c)
               , λ (a,b,c) → ((a,b),c), λ ((a,b),c) → (a,b,c))
</code></pre>

<p>We could define another macro that captures this pattern and requires one fewer argument.
On the other hand, there is merit to keeping the contextual requirements explicit.</p>

<h3>Regular data types</h3>

<p>A regular data type is one in which the recursive uses are at the same type.
Functions over such types are often defined via <em>monomorphic</em> recursion.
Data types that do not satisfy this constraint are called &#8220;<a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.3551" title="Paper by Richard Bird and Lambert Meertens">nested</a>&#8220;.</p>

<p>As in several recent generic programming systems, regular data types can be encoded generically through a type class that unwraps one level of functor from a type.
The regular data type is the fixpoint of that functor.
See, e.g., <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.6390" title="Paper by Ulf Norell and Patrik Jansson">Polytypic programming in Haskell</a></em>.
Adopting the style of <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.142.4778" title="Paper by Thomas Noort, Alexey Rodriguez, Stefan Holdermans, Johan Jeuring, Bastiaan Heeren">A Lightweight Approach to Datatype-Generic Rewriting</a></em>,</p>

<pre><code>class Functor (PF t) ⇒ Regular t where
  type PF t :: * → *
  wrap   :: PF t t → t
  unwrap :: t → PF t t
</code></pre>

<p>Here &#8220;<code>PF</code>&#8221; stands for &#8220;pattern functor&#8221;.</p>

<p>The pattern functors can be constructed out of the functor combinators above.
For instance, a list at the top level is either empty or a value and a list.
Translating this description:</p>

<pre><code>instance Regular [a] where
  type PF [a] = Unit :+: Const a :*: Id

  unwrap []     = InL (Const ())
  unwrap (a:as) = InR (Const a :*: Id as)

  wrap (InL (Const ()))          = []
  wrap (InR (Const a :*: Id as)) = a:as
</code></pre>

<p>As another example, consider rose trees ([<code>Data.Tree</code>][]):</p>

<pre><code>data Tree  a = Node a [Tree a]

instance Regular (Tree a) where

  type PF (Tree a) = Const a :*: []

  unwrap (Node a ts) = Const a :*: ts

  wrap (Const a :*: ts) = Node a ts
</code></pre>

<p>Regular types allow for even more succinct <code>HasTrie</code> instance implementations.
Specialize <code>HasTrieIsomorph</code> further:</p>

<pre><code>#define HasTrieRegular(Context,Type)  
HasTrieIsomorph(Context, Type, PF (Type) (Type) , unwrap, wrap)
</code></pre>

<p>For instance, for lists and rose trees:</p>

<pre><code>HasTrieRegular(HasTrie a, [a])
HasTrieRegular(HasTrie a, Tree a)
</code></pre>

<p>The <code>HasTrieRegular</code> macro could be specialized even further for single-parameter polymorphic data types:</p>

<pre><code>#define HasTrieRegular1(TypeCon) HasTrieRegular(HasTrie a, TypeCon a)

HasTrieRegular1([])
HasTrieRegular1(Tree)
</code></pre>

<p>You might wonder if I&#8217;m cheating here, by claiming very simple trie specifications when I&#8217;m really just shuffling code around.
After all, the complexity removed from <code>HasTrie</code> instances shows up in <code>Regular</code> instances.
The win in making this shuffle is that <code>Regular</code> is handy for other purposes, as illustrated in <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.140.2412" title="Paper by Alexey Rodriguez, Stefan Holdermans, Andres Löh, and Johan Jeuring">Generic programming with fixed points for mutually recursive datatypes</a></em> (including <code>fold</code>, <code>unfold</code>, and <code>fmap</code>).
(More examples in <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.142.4778" title="Paper by Thomas Noort, Alexey Rodriguez, Stefan Holdermans, Johan Jeuring, Bastiaan Heeren">A Lightweight Approach to Datatype-Generic Rewriting</a></em>.)</p>

<h3>Trouble</h3>

<p>Sadly, these elegant trie definitions have a problem.
Trying to compile them leads to a error message from GHC.
For instance,</p>

<pre><code>Nested type family application
  in the type family application: Trie (PF [a] [a])
(Use -XUndecidableInstances to permit this)
</code></pre>

<p>Adding <code>UndecidableInstances</code> silences this error message, but leads to nontermination in the compiler.</p>

<p>Expanding definitions, I can see the likely cause of nontermination.
The definition in terms of a type family allows an infinite type to sneak through, and I guess GHC&#8217;s type checker is unfolding infinitely.</p>

<p>As a simpler example:</p>

<pre><code>{-# LANGUAGE TypeFamilies, UndecidableInstances #-}

type family List a :: *

type instance List a = Either () (a, List a)

-- Hangs ghc 6.12.1:
nil :: List a
nil = Left ()
</code></pre>

<h3>A solution</h3>

<p>Since GHC&#8217;s type-checker cannot handle directly recursive types, perhaps we can use a standard avoidance strategy, namely introducing a <code>newtype</code> or <code>data</code> definition to break the cycle.
For instance, as a trie for <code>[a]</code>, we got into trouble by using the trie of the unwrapped form of <code>[a]</code>, i.e., <code>Trie (PF [a] [a])</code>.
So instead,</p>

<pre><code>newtype ListTrie a v = ListTrie (Trie (PF [a] [a]) v)
</code></pre>

<p>which is to say</p>

<pre><code>newtype ListTrie a v = ListTrie (PF [a] [a] :→: v)
</code></pre>

<p>Now <code>wrap</code> and <code>unwrap</code> as before, and add &amp; remove <code>ListTrie</code> as needed:</p>

<pre><code>instance HasTrie a ⇒ HasTrie [a] where
  type Trie [a] = ListTrie a
  trie f = ListTrie (trie (f . wrap))
  untrie (ListTrie t) = untrie t . unwrap
</code></pre>

<p>Again, abstract the boilerplate code into a C macro:</p>

<pre><code>#define HasTrieRegular(Context,Type,TrieType,TrieCon) 
newtype TrieType v = TrieCon (PF (Type) (Type) :→: v); 
instance Context ⇒ HasTrie (Type) where { 
  type Trie (Type) = TrieType; 
  trie f = TrieCon (trie (f . wrap)); 
  untrie (TrieCon t) = untrie t . unwrap; 
}
</code></pre>

<p>For instance,</p>

<pre><code>HasTrieRegular(HasTrie a, [a] , ListTrie a, ListTrie)
HasTrieRegular(HasTrie a, Tree, TreeTrie a, TreeTrie)
</code></pre>

<p>Again, simplify a bit with a specialization to unary regular types:</p>

<pre><code>#define HasTrieRegular1(TypeCon,TrieCon) 
HasTrieRegular(HasTrie a, TypeCon a, TrieCon a, TrieCon)
</code></pre>

<p>And then use the following declarations instead:</p>

<pre><code>HasTrieRegular1([]  , ListTrie)
HasTrieRegular1(Tree, TreeTrie)
</code></pre>

<p>Similarly for binary etc as needed.</p>

<p>The second macro parameter (<code>TrieCon</code>) is just a name, which I don&#8217;t to be used other than in the macro-generated code.
It could be eliminated, if there were a way to gensym the name.
Perhaps with Template Haskell?</p>

<h3>Conclusion</h3>

<p>I like the elegance of constructing memo tries in terms of common functor combinators.
Standard pattern functors allow for extremely succinct trie specifications for regular data types.
However, these specifications lead to nontermination of the type checker, which can then be avoided by the standard trick of introducing a newtype to break type recursion.
As often, this trick brings introduces some clumsiness.
Perhaps the problem can also be avoided by using a formulation using <em>bifunctors</em>, as in <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.60.5251" title="Paper by Jeremy Gibbons">Design Patterns as Higher-Order Datatype-Generic Programs</a></em> and <em><a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.6390" title="Paper by Ulf Norell and Patrik Jansson">Polytypic programming in Haskell</a></em>, which allows the fixed-point nature of regular data types to be exposed.</p>
<p><a href="http://conal.net/blog/?flattrss_redirect&amp;id=117&amp;md5=32e9a954390ac5cfba0f5fb929af467d"><img src="http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png" srcset="http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png, http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white@2x.png 2xhttp://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white.png, http://conal.net/blog/wp-content/plugins/flattr/img/flattr-badge-white@3x.png 3x" alt="Flattr this!"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://conal.net/blog/posts/elegant-memoization-with-higher-order-types/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		<atom:link rel="payment" title="Flattr this!" href="https://flattr.com/submit/auto?user_id=conal&amp;popout=1&amp;url=http%3A%2F%2Fconal.net%2Fblog%2Fposts%2Felegant-memoization-with-higher-order-types&amp;language=en_GB&amp;category=text&amp;title=Elegant+memoization+with+higher-order+types&amp;description=A+while+back%2C+I+got+interested+in+functional+memoization%2C+especially+after+seeing+some+code+from+Spencer+Janssen+using+the+essential+idea+of+Ralf+Hinze%26%238217%3Bs+paper+Generalizing+Generalized+Tries.+The+blog...&amp;tags=functor%2Cisomorphism%2Cmemoization%2Ctrie%2Cblog" type="text/html" />
	</item>
	</channel>
</rss>
