talk.tex 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. % Copyright (C) 2017 Koz Ross <koz.ross@retro-freedom.nz>
  2. % This work is licensed under the Creative Commons Attribution-ShareAlike 4.0
  3. % International License. To view a copy of this license, visit
  4. % http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative
  5. % Commons, PO Box 1866, Mountain View, CA 94042, USA.
  6. \documentclass{beamer}
  7. \usepackage[utf8]{inputenc}
  8. \usecolortheme{seahorse}
  9. \usefonttheme{professionalfonts}
  10. \usepackage{fontspec}
  11. \setmainfont{DejaVu Sans}
  12. \setbeamertemplate{navigation symbols}{}
  13. \usepackage{caption}
  14. \usepackage[font=itshape, begintext=``, endtext='']{quoting}
  15. \usepackage{fancyvrb}
  16. \usepackage{color}
  17. \usepackage[noend]{algpseudocode}
  18. \title{Hylomorphisms}
  19. \subtitle{Or: intermediate structures in a non-awful way}
  20. \titlegraphic{\includegraphics[scale=0.8]{img/logo.pdf}}
  21. \author{Koz Ross}
  22. \date{14th September, 2017}
  23. \begin{document}
  24. \begin{frame}[c]
  25. \titlepage{}
  26. \end{frame}
  27. \begin{frame}[c]
  28. \frametitle{Outline}
  29. \tableofcontents
  30. \end{frame}
  31. \section{Introduction}
  32. \begin{frame}[c]
  33. \frametitle{Intermediate structures}
  34. \pause{}
  35. \begin{definition}
  36. We say that a data structure is {\em intermediate\/} if it is used in an
  37. algorithm, but not given as an input or returned as an output.
  38. \end{definition}
  39. \pause{}
  40. \bigskip{}
  41. Essentially, it's a data structure which we {\em use}, but never {\em see}.
  42. \end{frame}
  43. \begin{frame}[c, fragile]
  44. \frametitle{An example}
  45. \begin{definition}
  46. The {\em Fibonacci numbers\/} $(F_0, F_1, \ldots)$ are a sequence of natural
  47. numbers, such that:\pause{}
  48. \begin{itemize}
  49. \item $F_0 = F_1 = 1$\pause{}
  50. \item For any $n > 1$, $F_n = F_{n - 1} + F_{n - 2}$
  51. \end{itemize}
  52. \end{definition}\pause{}
  53. If we wanted to write an algorithm for computing $F_n$ given an input $n$, we
  54. can convert this definition directly:\pause{}
  55. \bigskip{}
  56. \begin{algorithmic}
  57. \Function{$\mathrm{fib}$}{$n$}
  58. \If{$n = 0$ or $n = 1$}
  59. \State{}\Return{}$1$
  60. \Else{}
  61. \State{}\Return{}\Call{$\mathrm{fib}$}{$n - 1$} $+$ \Call{$\mathrm{fib}$}{$n - 2$}
  62. \EndIf{}
  63. \EndFunction{}
  64. \end{algorithmic}
  65. \end{frame}
  66. \begin{frame}[c]
  67. \frametitle{Why this is awful}
  68. Let's see what happens if we call $\mathrm{fib}(4)$:\pause{}
  69. \begin{enumerate}
  70. \item $\mathrm{fib}(3) + \mathrm{fib}(2)$\pause{}
  71. \item $\mathrm{fib}(2) + \mathrm{fib}(1) + \mathrm{fib}(2)$\pause{}
  72. \item $\mathrm{fib}(1) + \mathrm{fib}(0) + \mathrm{fib}(1) +
  73. \mathrm{fib}(2)$\pause{}
  74. \item $1 + \mathrm{fib}(0) + \mathrm{fib}(1) + \mathrm{fib}(2)$\pause{}
  75. \item $1 + 1 + \mathrm{fib}(1) + \mathrm{fib}(2)$\pause{}
  76. \item $1 + 1 + 1 + \mathrm{fib}(2)$\pause{}
  77. \item $1 + 1 + 1 + \mathrm{fib}(1) + \mathrm{fib}(0)$\pause{}
  78. \item $1 + 1 + 1 + 1 + \mathrm{fib}(0)$\pause{}
  79. \item $1 + 1 + 1 + 1 + 1$\pause{}
  80. \item $5$\pause{}
  81. \end{enumerate}
  82. That's a {\em lot\/} of wasted effort --- for example, we have to compute
  83. $\mathrm{fib}(2)$ twice.\pause{} Only gets worse for higher $n$!
  84. \end{frame}
  85. \begin{frame}[c]
  86. \frametitle{How awful is it?}
  87. Every call to $\mathrm{fib}(n)$ produces two recursive calls: one to
  88. $\mathrm{fib}(n - 1)$ and another to $\mathrm{fib}(n - 2)$. We stop when
  89. $n = 0, 1$.\pause{} That means that $\mathrm{fib}(n)$ requires $2^{n - 2} +
  90. 2^{n - 3}$ recursive calls.\pause{} This means our algorithm is
  91. $O(2^n)$.\pause{} This is \alert{atrocious}.\pause{}
  92. \bigskip{}
  93. One reason why it's so bad is because we waste a lot of work --- in order to
  94. compute $\mathrm{fib}(n - 1)$, we have to compute $\mathrm{fib}(n - 2)$, only
  95. to throw it away and do it again!\pause{} If only we kept a hold of
  96. $\mathrm{fib}(n - 2)$, we could reduce the number of recursive calls required
  97. from $2$ to $1$.\pause{} Let's try using an intermediate data structure for
  98. that\ldots
  99. \end{frame}
  100. \begin{frame}[c, fragile]
  101. \frametitle{Take 2}
  102. Let's use an array to hold the intermediate computations:\pause{}
  103. \bigskip
  104. \begin{algorithmic}
  105. \Function{$\mathrm{fib}^{\prime}$}{$n$}
  106. \State{}$\mathrm{arr} \gets$ an empty array of length $n$
  107. \State{}$\mathrm{arr}[0] = 1$
  108. \State{}$\mathrm{arr}[1] = 1$
  109. \For{$i \in 2,3,\ldots,n - 1$}
  110. \State{}$\mathrm{arr}[i] \gets \mathrm{arr}[i - 1] + \mathrm{arr}[i -
  111. 2]$
  112. \EndFor{}
  113. \State{}\Return{}$\mathrm{arr}[n - 1]$
  114. \EndFunction{}
  115. \end{algorithmic}\pause{}
  116. \bigskip
  117. Since we have a (nearly) $n$-length loop in $\mathrm{fib}^{\prime}(n)$, this
  118. is clearly $O(n)$ --- much better! All thanks to the intermediate array.
  119. \end{frame}
  120. \begin{frame}[c]
  121. \frametitle{Intermediate structures are {\em everywhere\/}}
  122. \pause{}
  123. \begin{itemize}
  124. \item Graph processing algorithms (DFS, BFS, Dijkstra's, Prim's, Kruskal's,
  125. \ldots)\pause{}
  126. \item Graphics pipeline (rasterization, translations, shading,
  127. \ldots)\pause{}
  128. \item Compilers (of any sort)\pause{}
  129. \item Every time you do recursion (implicitly)\pause{}
  130. \item And so many more\ldots
  131. \end{itemize}
  132. \pause{}
  133. However, it's not all brilliant --- intermediate structures have two major
  134. issues.
  135. \end{frame}
  136. \begin{frame}[c]
  137. \frametitle{Issue One: ad-hoc-ism}
  138. Every time we employ an intermediate data structure, we have to devise the
  139. algorithm `from first principles'. This has several downsides:\pause{}
  140. \begin{itemize}
  141. \item Correctness is harder to determine\pause{}
  142. \item Structure and content are entangled (no general treatment)\pause{}
  143. \item Incidental complexity from the structure adds to the complexity of the
  144. algorithm\pause{}
  145. \end{itemize}
  146. This is mostly because we're being forced to consider a specific solution
  147. every time we want an intermediate structure to be built up, then torn down.
  148. \end{frame}
  149. \begin{frame}[c, fragile]
  150. \frametitle{Issue Two: efficiency}
  151. Let's look at $\mathrm{fib}^{\prime}$ again:\pause{}
  152. \bigskip
  153. \begin{algorithmic}
  154. \Function{$\mathrm{fib}^{\prime}$}{$n$}
  155. \State{}$\mathrm{arr} \gets$ an empty array of length $n$
  156. \State{}$\mathrm{arr}[0] = 1$
  157. \State{}$\mathrm{arr}[1] = 1$
  158. \For{$i \in 2,3,\ldots,n - 1$}
  159. \State{}$\mathrm{arr}[i] \gets \mathrm{arr}[i - 1] + \mathrm{arr}[i -
  160. 2]$
  161. \EndFor{}
  162. \State{}\Return{}$\mathrm{arr}[n - 1]$
  163. \EndFunction{}
  164. \end{algorithmic}\pause{}
  165. \bigskip
  166. At no point in the algorithm are we interested in the whole array --- just the
  167. last two elements.\pause{} However, we can't get rid of any part of the
  168. structure until the {\em very\/} end, even though it's useless to us!
  169. \end{frame}
  170. \begin{frame}[c]
  171. \frametitle{Can we do better?}
  172. What we really want is:\pause{}
  173. \begin{itemize}
  174. \item A general way of handling {\em any\/} algorithm which involves
  175. intermediate structures\pause{}
  176. \item A guarantee of {\em some\/} correctness properties (from a
  177. formal/mathematical point of view)\pause{}
  178. \item Some way of automatically only building up as much structure as we
  179. need\pause{}
  180. \end{itemize}
  181. Sounds impossible, right?\pause{} However: there's a morphism for that!
  182. \end{frame}
  183. \section{Hylomorphisms}
  184. \begin{frame}[fragile, c]
  185. \frametitle{Where we last left recursion schemes\ldots}\pause{}
  186. \begin{Verbatim}[fontsize=\small, commandchars=\\\{\}]
  187. \color{gray}-- our knotting hack
  188. data Knot f = Tie \{ untie :: f (Knot f) \}
  189. \color{gray}-- pronounced `then', for easier composition
  190. >>> :: (a -> b) -> (b -> c) -> (a -> c)
  191. f >>> g = g \circ f
  192. type Algebra f a = f a -> a
  193. \color{gray}-- catamorphism - tears structures down
  194. cata :: Functor f => Algebra f a -> Knot f -> a
  195. cata f = untie >>> map (cata f) >>> f
  196. type Coalgebra f a = a -> f a
  197. \color{gray}-- anamorphism - builds structures up
  198. ana :: Functor f => Coalgebra f a -> a -> Knot f
  199. ana f = f >>> map (ana f) >>> Tie
  200. \end{Verbatim}
  201. \end{frame}
  202. \begin{frame}[fragile, c]
  203. \frametitle{Trying to use recursion schemes}
  204. When we use an intermediate structure, we first build it up, then tear it
  205. down. What would happen if we tried to write this using recursion
  206. schemes?\pause{}
  207. \begin{Verbatim}[fontsize=\small, commandchars=\\\{\}]
  208. \color{gray}-- what's the type of this?
  209. huh coalg alg = ana coalg >>> cata alg \pause{}
  210. huh :: Functor f => (a -> f a) -> (f b -> b) -> a -> b
  211. \color{gray}-- remember, (a -> f a) is Coalgebra f a
  212. \color{gray}-- and (f b -> b) is Algebra f b
  213. \end{Verbatim}
  214. \pause{}
  215. Now, {\tt huh} is a very silly name. We should really fix our terminology
  216. again\ldots
  217. \end{frame}
  218. \begin{frame}[fragile, c]
  219. \frametitle{The humble hylomorphism}
  220. \begin{Verbatim}[fontsize=\small, commandchars=\\\{\}]
  221. hylo :: Functor f => Coalgebra f a -> Algebra f b -> a -> b
  222. hylo f g = ana f >>> cata g
  223. \end{Verbatim}
  224. \bigskip
  225. The term `hylomorphism' comes from the Greek root {\em hylo\/} (meaning
  226. `tree'), as its behaviour can be seen as tree-like (building up is like the
  227. tree growing, and tearing down is like the tree being turned into
  228. matches).\pause{}
  229. \bigskip{}
  230. Note: The intermediate structure being operated on by the
  231. algebra and coalgebra must be {\em the same}, but the data can be different.
  232. \end{frame}
  233. \section{Using hylomorphisms}
  234. \begin{frame}[fragile, c]
  235. \frametitle{Example: mergesort}
  236. \pause{}
  237. \begin{itemize}
  238. \item A well-known {\em comparison-sorting\/} algorithm for arrays\pause{}
  239. \item An example of a {\em divide-and-conquer\/} algorithm\pause{}
  240. \item Relies on the following two principles:\pause{}
  241. \begin{enumerate}
  242. \item An array of length $1$ is always sorted\pause{}
  243. \item We can merge two sorted arrays into one sorted array\pause{}
  244. \end{enumerate}
  245. \item Relies on a three-step {\em merge procedure\/}:\pause{}
  246. \begin{enumerate}
  247. \item Create an array big enough to hold the contents of both inputs\pause{}
  248. \item Walk over the new array, inserting the smallest element from either
  249. input at each stage\pause{}
  250. \item When we've finished, return the result\pause{}
  251. \end{enumerate}
  252. \item Implicitly builds up a binary tree of deferred sort operations
  253. \end{itemize}
  254. \end{frame}
  255. \begin{frame}[fragile,c]
  256. \frametitle{The merge procedure, in detail}
  257. \begin{algorithmic}
  258. \Function{$\mathrm{merge}$}{$a_1, a_2$}
  259. \State{}$n \gets$ \Call{$\mathrm{len}$}{$a_1$} $+$ \Call{$\mathrm{len}$}{$a_2$}
  260. \State{}$a \gets$ a new array of length $n$
  261. \State{}$f_1, f_2 \gets 0$
  262. \For{$i \in 0,1,\ldots,n - 1$}
  263. \If{$f_1 =$ \Call{$\mathrm{len}$}{$a_1$}}
  264. \State{}$a[i] = a_2[f_2]$
  265. \State{}$f_2 \gets f_2 + 1$
  266. \ElsIf{$f_2 =$ \Call{$\mathrm{len}$}{$a_2$}}
  267. \State{}$a[i] \gets a_1[f_1]$
  268. \State{}$f_1 \gets f_1 + 1$
  269. \Else{}
  270. \State{}$e \gets \min\{a_1[f_1], a_2[f_2]\}$
  271. \State{}$a[i] \gets e$
  272. \State{}Increment corresponding $f$
  273. \EndIf{}
  274. \EndFor{}
  275. \State{}\Return{}$a$
  276. \EndFunction{}
  277. \end{algorithmic}
  278. \end{frame}
  279. \begin{frame}[fragile,c]
  280. \frametitle{Viewing mergesort as a hylomorphism}
  281. \begin{itemize}
  282. \item When we do the `divide' step of mergesort, we build up a binary
  283. tree:\pause{}
  284. \begin{itemize}
  285. \item The leaves are singleton arrays\pause{}
  286. \item The internal nodes are deferred merges of the children\pause{}
  287. \end{itemize}
  288. \item Once we've built up this tree, we can tear it down bottom-up by
  289. realizing the deferred computations based on their children\pause{}
  290. \item At the end, we have a single, sorted array, and the tree is
  291. gone\pause{}
  292. \end{itemize}
  293. \bigskip{}
  294. Let's see how this works in practice: we will sort the array {\tt [4, 2, 6,
  295. 1]}.
  296. \end{frame}
  297. \begin{frame}[fragile,c]
  298. \frametitle{Worked example}
  299. \begin{overprint}
  300. \onslide<1>\centerline{\includegraphics[scale=1]{img/merge1.pdf}}%
  301. \onslide<2>\centerline{\includegraphics[scale=1]{img/merge2.pdf}}%
  302. \onslide<3>\centerline{\includegraphics[scale=1]{img/merge3.pdf}}%
  303. \onslide<4>\centerline{\includegraphics[scale=1]{img/merge4.pdf}}%
  304. \onslide<5>\centerline{\includegraphics[scale=1]{img/merge5.pdf}}%
  305. \onslide<6>\centerline{\includegraphics[scale=1]{img/merge6.pdf}}%
  306. \onslide<7>\centerline{\includegraphics[scale=1]{img/merge7.pdf}}%
  307. \onslide<8>\centerline{\includegraphics[scale=1]{img/merge8.pdf}}%
  308. \end{overprint}
  309. \end{frame}
  310. \begin{frame}[fragile,c]
  311. \frametitle{Putting it into a hylo}
  312. First, we will need an intermediate tree structure:\pause{}
  313. \begin{Verbatim}[commandchars=\\\{\}]
  314. data Tree a = Leaf a | Internal (Tree a) (Tree a)
  315. \end{Verbatim}
  316. \pause{}
  317. It then needs a slight change so we can use it with recursion schemes:\pause{}
  318. \begin{Verbatim}[commandchars=\\\{\}]
  319. data ITree a s = Leaf a | Internal s s
  320. \end{Verbatim}
  321. \pause{}
  322. Lastly, we need a {\tt Functor} instance for it. This can be derived
  323. automatically (both in theory {\em and\/} practice).\pause{}
  324. \begin{Verbatim}[commandchars=\\\{\}]
  325. instance Functor (ITree a) where \pause{}
  326. map :: (s -> t) -> ITree a s -> ITree a t\pause{}
  327. map _ (Leaf a) = Leaf a \color{gray}-- nothing to do\pause{}
  328. map f (Internal l r) = Internal (f l) (f r)
  329. \end{Verbatim}
  330. \end{frame}
  331. \begin{frame}[fragile,c]
  332. \frametitle{A suitable coalgebra}
  333. Our coalgebra will represent one `step' in the dividing process:\pause{}
  334. \begin{itemize}
  335. \item If we get given an array of length $1$, we just make a {\tt Leaf}
  336. storing it\pause{}
  337. \item Otherwise, we make an {\tt Internal}, and send half of the array into
  338. each child\pause{}
  339. \end{itemize}
  340. \begin{Verbatim}[commandchars=\\\{\}]
  341. \color{gray}-- Vector a is an array of a
  342. breakDown :: Coalgebra (ITree (Vector a)) (Vector a)\pause{}
  343. \color{gray}-- breakDown :: Vector a -> ITree (Vector a)\pause{}
  344. breakDown v = case (length v) of \pause{}
  345. 1 -> Leaf v\pause{}
  346. x -> let half = x `div` 2 in
  347. Internal (slice v 0 half) (slice v half (x - half))
  348. \end{Verbatim}
  349. \end{frame}
  350. \begin{frame}[fragile,c]
  351. \frametitle{A suitable algebra}
  352. Our algebra will represent one `step' in the conquering process:\pause{}
  353. \begin{itemize}
  354. \item If we have a {\tt Leaf}, we just return the array as-is\pause{}
  355. \item If we have an {\tt Internal}, merge the arrays in its left and right
  356. children\pause{}
  357. \end{itemize}
  358. We will assume we have {\tt merge} defined appropriately --- its details don't
  359. matter for this.\pause{}
  360. \begin{Verbatim}[commandchars=\\\{\}]
  361. mergeAlg :: Ord a => Algebra (ITree (Vector a)) (Vector a)\pause{}
  362. \color{gray}-- mergeAlg :: Ord a => ITree (Vector a) -> Vector a\pause{}
  363. mergeAlg (Leaf v) = v\pause{}
  364. mergeAlg (Internal v1 v2) = merge v1 v2
  365. \end{Verbatim}
  366. \end{frame}
  367. \begin{frame}[fragile,c]
  368. \frametitle{Putting it all together}
  369. \begin{Verbatim}[commandchars=\\\{\}]
  370. mergeSort :: Ord a => Vector a -> Vector a
  371. mergeSort v\pause{}
  372. | length v == 0 = v \color{gray}-- no point sorting an empty array\pause{}
  373. | otherwise = hylo breakDown mergeAlg v
  374. \end{Verbatim}
  375. \pause{}
  376. \bigskip{}
  377. \alert{Yay!}
  378. \end{frame}
  379. \begin{frame}[fragile, c]
  380. \frametitle{Reviewing our goals}
  381. \pause{}
  382. \begin{itemize}
  383. \item Generalization of all divide-and-conquer algorithms (and almost all
  384. intermediate structure work!)\pause{}
  385. \item Strong correctness guarantees:\pause{}
  386. \begin{itemize}
  387. \item Won't miss any items in the teardown\pause{}
  388. \item No edge-cases for `lop-sided' structures\pause{}
  389. \item No `funny stuff' --- no side effects, no structural wrecking we
  390. didn't ask for\pause{}
  391. \end{itemize}
  392. \item Efficiency still a problem --- until we finish {\tt ana f} we can't
  393. start on {\tt cata g}!\pause{}
  394. \end{itemize}
  395. Luckily for us, Meijer and Hutton solved that last one in their `Bananas in
  396. Space' paper in 1995:\pause{}
  397. \begin{Verbatim}[fontsize=\small,commandchars=\\\{\}]
  398. hylo' :: Functor f => Coalgebra f a -> Algebra f b -> a -> b
  399. hylo' f g = f >>> map (hylo' f g) >>> g
  400. \end{Verbatim}
  401. \pause{}
  402. \alert{Result:} We have everything we wanted, and recursion schemes still rule!
  403. \end{frame}
  404. \section{Questions}
  405. \begin{frame}[fragile, c]
  406. \frametitle{Questions?}
  407. \begin{center}
  408. \includegraphics[scale=0.27]{img/questions.pdf}
  409. \end{center}
  410. \end{frame}
  411. \end{document}