Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Folding Cheat Sheet # 9 - List Unfolding ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘...

Folding Cheat Sheet # 9 - List Unfolding ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ as the Computational Dual of ๐‘“๐‘œ๐‘™๐‘‘ and how ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ relates toย ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’

In this deck we look at the following:
* How unfolding lists is the computational dual of folding lists
* Different variants of the function for unfolding lists
* How they relate to the iterate function

Keywords: "folding","unfolding","fold","unfold","fold'","unfold'","iterate","anamorphism","catamorphism","infinite list","lazy list","functional programming","scala","haskell"

Avatar for Philip Schwarz

Philip Schwarz

June 15, 2025
Tweet

More Decks by Philip Schwarz

Other Decks in Programming

Transcript

  1. CHEAT-SHEET Folding #9 โˆถ / \ ๐’‚๐ŸŽ โˆถ / \

    ๐’‚๐Ÿ โˆถ / \ ๐’‚๐Ÿ โˆถ / \ ๐’‚๐Ÿ‘ ๐’‡ / \ ๐’‚๐ŸŽ ๐’‡ / \ ๐’‚๐Ÿ ๐’‡ / \ ๐’‚๐Ÿ ๐’‡ / \ ๐’‚๐Ÿ‘ ๐’† List Unfolding ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ as the Computational Dual of ๐‘“๐‘œ๐‘™๐‘‘ and how ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ relates to ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ @philip_schwarz slides by https://fpilluminated.org/
  2. This cheat sheet is based on the following deck, which

    it aims to condense, partly by focusing on a single running example, and partly by choosing, rather than to provide excerpts from referenced sources, to simply present salient points made by the sources. @philip_schwarz
  3. Here is a function called digits which takes an integer

    number, and returns a list of integers corresponding to the numberโ€™s digits (yes, we are not handling cases like n=0 or negative n). The digits function makes use of a function called iterate. See the next two slides for the Haskell and Scala definitions of iterate. The Haskell version of digits is defined in point-free style, uses the function composition function, and uses backticks to specify the infix version of functions mod and div. If you find it at all cryptic, see the slide after the next two for alternative definitions that are less succinct. digits :: Int -> [Int] digits = reverse . map (`mod` 10) . takeWhile (/= 0) . iterate (`div` 10) def digits(n: Int): List[Int] = LazyList.iterate(n)(_ / 10 ) .takeWhile(_ != 0) .map( _ % 10 ) .toList .reverse ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ ๐‘ฅ = ๐‘ฅ โˆถ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ (๐‘“ ๐‘ฅ) > digits 2718 [2,7,1,8] scala> digits(2718) val res0: List[Int] = List(2, 7, 1, 8)
  4. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’

    ๐‘“ ๐‘ฅ = ๐‘ฅ โˆถ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ (๐‘“ ๐‘ฅ)
  5. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’

    ๐‘“ ๐‘ฅ = ๐‘ฅ โˆถ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ (๐‘“ ๐‘ฅ)
  6. digits :: Int -> [Int] digits n = reverse (

    map (\x -> mod x 10) ( takeWhile (\x -> x /= 0) ( iterate (\x -> div x 10) n ) ) ) digits :: Int -> [Int] digits n = reverse $ map (\x -> mod x 10) $ takeWhile (\x -> x > 0) $ iterate (\x -> div x 10) $ n digits :: Int -> [Int] digits n = n & iterate (\x -> div x 10) & takeWhile (\x -> x > 0) & map (\x -> mod x 10) & reverse digits :: Int -> [Int] digits = reverse . map (\x -> mod x 10) . takeWhile (\x -> x > 0) . iterate (\x -> div x 10) digits :: Int -> [Int] digits n = n & iterate (`div` 10) & takeWhile (/= 0) & map (`mod` 10) & reverse digits :: Int -> [Int] digits n = reverse $ map (`mod` 10) $ takeWhile (/= 0) $ iterate (`div` 10) $ n digits :: Int -> [Int] digits n = reverse ( map (`mod` 10) ( takeWhile (/= 0) ( iterate (`div` 10) n ) ) ) digits :: Int -> [Int] digits = reverse . map (`mod` 10) . takeWhile (/= 0) . iterate (`div` 10)
  7. If you would like to know more about how the

    digits function works then see either the following deck, or the one mentioned at the beginning of this deck.
  8. digits :: Int -> [Int] digits = reverse . map

    (`mod` 10) . takeWhile (/= 0) . iterate (`div` 10) def digits(n: Int): List[Int] = LazyList iterate(n)(_ / 10) .takeWhile (_ != 0) .map (_ % 10) .toList .reverse The digits function composes map, takeWhile and iterate in sequence. One often finds such a pattern of computation, which may be captured as a generic function called unfold. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท (๐›ฝ โ†’ ๐›ผ) โ†’ ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โ„Ž ๐‘ ๐‘ก = ๐‘š๐‘Ž๐‘ โ„Ž โˆ˜ ๐‘ก๐‘Ž๐‘˜๐‘’๐‘คโ„Ž๐‘–๐‘™๐‘’ ๐‘ โˆ˜ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘ก unfold :: (b -> a) -> (b -> Bool) -> (b -> b) -> b -> [a] unfold h p t = map h . takeWhile p . iterate t def unfold[A,B](h: B => A, p: B => Boolean, t: B => B, b: B): LazyList[A] = LazyList.iterate(b)(t).takeWhile(p).map(h)
  9. digits :: Int -> [Int] digits = reverse . unfold

    (`mod` 10) (/= 0) (`div` 10) def digits(n: Int): List[Int] = unfold[Int,Int](_ % 10, _ != 0, _ / 10, n).toList.reverse digits :: Int -> [Int] digits = reverse . map (`mod` 10) . takeWhile (/= 0) . iterate (`div` 10) def digits(n: Int): List[Int] = LazyList iterate(n)(_ / 10) .takeWhile (_ != 0) .map (_ % 10) .toList .reverse Letโ€™s simplify the implementation of digits using the unfold function. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท (๐›ฝ โ†’ ๐›ผ) โ†’ ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โ„Ž ๐‘ ๐‘ก = ๐‘š๐‘Ž๐‘ โ„Ž โˆ˜ ๐‘ก๐‘Ž๐‘˜๐‘’๐‘คโ„Ž๐‘–๐‘™๐‘’ ๐‘ โˆ˜ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘ก
  10. Here is a more efficient variant of unfold that fuses

    together the map, takewhile and iterate. The signature is slightly different in that there is some renaming and reordering of parameters, and the condition tested by predicate function ๐‘ is inverted. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ผ) โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” ๐‘ = ๐ข๐Ÿ ๐‘ ๐‘ ๐ญ๐ก๐ž๐ง ๐‘ต๐’Š๐’ ๐ž๐ฅ๐ฌ๐ž ๐‘ช๐’๐’๐’” (๐‘“ ๐‘) (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” (๐‘” ๐‘)) ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท (๐›ฝ โ†’ ๐›ผ) โ†’ ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โ„Ž ๐‘ ๐‘ก = ๐‘š๐‘Ž๐‘ โ„Ž โˆ˜ ๐‘ก๐‘Ž๐‘˜๐‘’๐‘คโ„Ž๐‘–๐‘™๐‘’ ๐‘ โˆ˜ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘ก digits :: Int -> [Int] digits = reverse . unfold (== 0) (`mod` 10) (`div` 10) digits :: Int -> [Int] digits = reverse . unfold (`mod` 10) (/= 0) (`div` 10) def digits(n: Int): List[Int] = unfold[Int,Int](_ == 0, _ % 10, _ / 10, n).toList.reverse def digits(n: Int): List[Int] = unfold[Int,Int](_ % 10, _ != 0, _ / 10, n).toList.reverse
  11. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’

    ๐‘“ = ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘๐‘œ๐‘›๐‘ ๐‘ก ๐น๐‘Ž๐‘™๐‘ ๐‘’ ๐‘–๐‘‘ ๐‘“ Just for completeness, here is how iterate can be defined in terms of unfold. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ผ) โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” ๐‘ = ๐ข๐Ÿ ๐‘ ๐‘ ๐ญ๐ก๐ž๐ง ๐‘ต๐’Š๐’ ๐ž๐ฅ๐ฌ๐ž ๐‘ช๐’๐’๐’” (๐‘“ ๐‘) (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” (๐‘” ๐‘)) ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ ๐‘ฅ = ๐‘ฅ โˆถ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ (๐‘“ ๐‘ฅ)
  12. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ผ) โ†’

    (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” ๐‘ = ๐ข๐Ÿ ๐‘ ๐‘ ๐ญ๐ก๐ž๐ง ๐‘ต๐’Š๐’ ๐ž๐ฅ๐ฌ๐ž ๐‘ช๐’๐’๐’” (๐‘“ ๐‘) (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” (๐‘” ๐‘)) The unfold function is not available in Haskell and Scala. What is available, is an alternative variant which (for reasons that will become apparent later) we shall refer to as unfoldโ€™. In Haskell, unfoldโ€™ is called unfoldr (a right unfold). In Scala it is called unfold. Letโ€™s reimplement digits using the unfoldโ€™ function. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) unfoldr unfold digits :: Int -> [Int] digits = reverse . unfoldr gen where gen 0 = Nothing gen d = Just (d `mod` 10, d `div` 10) def digits(n: Int): List[Int] = LazyList.unfold(n): case 0 => None case x => Some(x % 10, x / 10) .toList.reverse digits :: Int -> [Int] digits = reverse . unfold (== 0) (`mod` 10) (`div` 10) def digits(n: Int): List[Int] = unfold[Int,Int](_ == 0, _ % 10, _ / 10, n).toList.reverse
  13. The next two slides show Haskell and Scala documentation for

    their equivalent of ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ.
  14. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’

    ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) The function passed to unfoldr takes a seed value that it uses either to generate Nothing or to generate both a new result item and a new seed value. unfoldr unfold
  15. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’

    ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) The function passed to unfold takes a state value that it uses either to generate None or to generate both a new result item and a new state value. unfoldr unfold
  16. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’

    ๐‘“ = ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐œ†๐‘ฅ. ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘“ ๐‘ฅ) Just for completeness, here is how iterate can be defined in terms of unfoldโ€™. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ โˆท ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ โ†’ ๐›ผ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ ๐‘ฅ = ๐‘ฅ โˆถ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘“ (๐‘“ ๐‘ฅ) ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) unfoldr unfold
  17. The dual of the digits function is the decimal function,

    which given a list of digits, returns the integer number with such digits. As seen below, the decimal function is neatly and efficiently implemented using a left fold (if you want to know more then see folding cheat-sheet #4). decimal :: [Int] -> Int decimal = foldl (โŠ•) 0 (โŠ•) :: Int -> Int -> Int n โŠ• d = 10 * n + d def decimal(ds: List[Int]): Int = ds.foldLeft(0)(_โŠ•_) extension (n: Int) def โŠ•(d Int): Int = 10 * n + d > decimal(List(2,7,1,8)) val res0: Int = 2718 > decimal [2,7,1,8] 2718
  18. Given the followingโ€ฆ โ€ข decimal is the computational dual of

    digits โ€ข decimal can be implemented using unfoldโ€™ (a right unfold) โ€ข the computational dual of unfoldโ€™ is foldโ€™ (a right fold) โ€ฆletโ€™s see if decimal can be implemented using foldโ€™. Because foldโ€™ is not available in Haskell and Scala, we implement it ourselves. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท (๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ) โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ต๐’Š๐’ = ๐‘“ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ช๐’๐’๐’” ๐‘ฅ ๐‘ฅ๐‘  = ๐‘“ (๐‘ฑ๐’–๐’”๐’• ๐‘ฅ, ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฅ๐‘ ) unfoldr unfold dual dual digits :: Int -> [Int] digits = reverse . unfoldr gen where gen 0 = Nothing gen d = Just (d `mod` 10, d `div` 10) def digits(n: Int): List[Int] = LazyList.unfold(n): case 0 => None case x => Some(x % 10, x / 10) .toList.reverse dual fold' :: (Maybe (a, b) -> b) -> [a] -> b fold' f [] = f Nothing fold' f (x:xs) = f (Just (x, fold' f xs)) decimal :: [Int] -> Int decimal = fst . fold' f where f Nothing = (0, 0) f (Just (d, (n,e))) = (d * (10 ^ e) + n, e + 1) def decimal(ds: List[Int]): Int = foldp[Int,(Int,Int)](ds){ case None => (0,0) case Some(d, (n, e)) => (d * (pow(10, e)).toInt + n, e + 1) }(0) def foldp[A,B](as: List[A])(f: Option[(A,B)] => B): B = as match case Nil => f(None) case x :: xs => f(Option(x, foldp(xs)(f))) decimal digits duals ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ duals uses uses
  19. Letโ€™s now switch from foldโ€™ to the more customary right

    fold, which is provided by Haskell and Scala. ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท (๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ) โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ต๐’Š๐’ = ๐‘“ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ช๐’๐’๐’” ๐‘ฅ ๐‘ฅ๐‘  = ๐‘“ (๐‘ฑ๐’–๐’”๐’• ๐‘ฅ, ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฅ๐‘ ) ๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ผ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ต๐’Š๐’ = ๐‘’ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ช๐’๐’๐’” ๐‘ฅ ๐‘ฅ๐‘  = ๐‘“ ๐‘ฅ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ฅ๐‘  foldr foldRight decimal :: [Int] -> Int decimal = fst . foldr f (0, 0) where f d (n, e) = (d * (10 ^ e) + n, e + 1) decimal :: [Int] -> Int decimal = fst . fold' f where f Nothing = (0, 0) f (Just (d, (n,e))) = (d * (10 ^ e) + n, e + 1) def decimal(ds: List[Int]): Int = foldp[Int,(Int,Int)](ds){ case None => (0,0) case Some(d, (n, e)) => (d * (pow(10, e)).toInt + n, e + 1) }(0) def decimal(ds: List[Int]): Int = ds.foldRight(0,0){ case (d, (n, e)) => (d * (pow(10, e)).toInt + n, e + 1) }(0)
  20. digits = reverse . map (`mod` 10) . takeWhile (/=

    0) . iterate (`div` 10) digits = reverse . unfold (`mod` 10) (/= 0) (`div` 10) digits = reverse . unfold (== 0) (`mod` 10) (`div` 10) digits = reverse . unfoldr gen where gen 0 = Nothing gen d = Just (d `mod` 10, d `div` 10) def digits(n: Int): List[Int] = LazyList iterate(n)(_ / 10 ).takeWhile(_ != 0).map( _ % 10 ).toList.reverse def digits(n: Int): List[Int] = unfold[Int,Int](_ % 10, _ != 0, _ / 10, n).toList.reverse def digits(n: Int): List[Int] = unfold[Int,Int](_ == 0, _ % 10, _ / 10, n).toList.reverse def digits(n: Int): List[Int] = def digits(n: Int): List[Int] = LazyList.unfold(n): LazyList.unfold(n): x => case 0 => None Option.unless(x==0)(x % 10, x / 10) case x => Some(x % 10, x / 10) .toList.reverse .toList.reverse Here are the four different implementations of digits that we have considered. ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ฃ1 ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ฃ2 ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘–๐‘ก๐‘’๐‘Ÿ๐‘Ž๐‘ก๐‘’ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ฃ1 ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ฃ2 ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ
  21. decimal = foldl (โŠ•) 0 n โŠ• d = 10

    * n + d decimal = fst . fold' f where f Nothing = (0, 0) f (Just (d, (n,e))) = (d * (10 ^ e) + n, e + 1) decimal = fst . foldr f (0, 0) where f d (n, e) = (d * (10 ^ e) + n, e + 1) def decimal(ds: List[Int]): Int = ds.foldLeft(0)(_โŠ•_) extension (n: Int) def โŠ•(d Int): Int = 10 * n + d def decimal(ds: List[Int]): Int = foldp[Int,(Int,Int)](ds){ case None => (0,0) case Some(d, (n, e)) => (d * (pow(10, e)).toInt + n, e + 1) }(0) def decimal(ds: List[Int]): Int = ds.foldRight(0,0){ case (d, (n, e)) => (d * (pow(10, e)).toInt + n, e + 1) }(0) And here are the three different implementations of decimal that we have considered. ๐‘™๐‘’๐‘“๐‘ก ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘™๐‘’๐‘“๐‘ก ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“๐‘œ๐‘™๐‘‘
  22. The next slide is the penultimate one and suggests that

    โ€ข the introduction of foldโ€™ and unfoldโ€™ makes the duality between folding and unfolding very clear โ€ข the names of foldโ€™ and unfoldโ€™ are primed because the two functions are less convenient for programming than fold and unfold. The slide after that is the last one and introduces the terms catamorphism and anamorphism.
  23. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’

    ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ข = ๐œ๐š๐ฌ๐ž ๐‘“ ๐‘ข ๐จ๐Ÿ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ โ†’ ๐‘ต๐’Š๐’ ๐‘ฑ๐’–๐’”๐’• (๐‘ฅ, ๐‘ฃ) โ†’ ๐‘ช๐’๐’๐’” ๐‘ฅ (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฃ) ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ผ) โ†’ (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” ๐‘ = ๐ข๐Ÿ ๐‘ ๐‘ ๐ญ๐ก๐ž๐ง ๐‘ต๐’Š๐’ ๐ž๐ฅ๐ฌ๐ž ๐‘ช๐’๐’๐’” (๐‘“ ๐‘) (๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ ๐‘ ๐‘“ ๐‘” (๐‘” ๐‘)) ๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ผ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ต๐’Š๐’ = ๐‘’ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ช๐’๐’๐’” ๐‘ฅ ๐‘ฅ๐‘  = ๐‘“ ๐‘ฅ ๐‘“๐‘œ๐‘™๐‘‘ ๐‘“ ๐‘’ ๐‘ฅ๐‘  ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท (๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ) โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ต๐’Š๐’ = ๐‘“ ๐‘ต๐’๐’•๐’‰๐’Š๐’๐’ˆ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ช๐’๐’๐’” ๐‘ฅ ๐‘ฅ๐‘  = ๐‘“ (๐‘ฑ๐’–๐’”๐’• ๐‘ฅ, ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ฅ๐‘ ) ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) ๐›ฝ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘“ ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ ๐‘“ ๐›ฝ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) ๐‘“ While they may sometimes be less convenient for programming with, ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ and ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ, the primed versions of ๐‘“๐‘œ๐‘™๐‘‘ and ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘, make the duality between the fold and the unfold very clear The two diagrams are based on ones in Conal Elliottโ€™s deck Folds and Unfolds all around us: http://conal.net/talks/folds-and-unfolds.pdf foldr foldRight unfoldr unfold
  24. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ฝ โ†’ ๐‘ฉ๐’๐’๐’ โ†’ (๐›ฝ โ†’ ๐›ผ) โ†’

    (๐›ฝ โ†’ ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘“๐‘œ๐‘™๐‘‘ โˆท ๐›ผ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ Functions that โ€˜destructโ€™ a list โ€“ they have been called catamorphisms, from the Greek proposition ฮบฮฑฯ„ฮฌ, meaning โ€˜downwardsโ€™ as in โ€˜catastropheโ€™. Functions that โ€˜generateโ€™ a list of items of type ๐›ผ from a seed of type ๐›ฝ โ€“ they have been called anamorphisms, from the Greek proposition แผ€ฮฝฮฌ, meaning โ€˜upwardsโ€™ as in โ€˜anabolismโ€™. ๐‘ข๐‘›๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท ๐›ฝ โ†’ ๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ ๐‘“๐‘œ๐‘™๐‘‘โ€ฒ โˆท (๐‘ด๐’‚๐’š๐’ƒ๐’† (๐›ผ, ๐›ฝ) โ†’ ๐›ฝ) โ†’ ๐‘ณ๐’Š๐’”๐’• ๐›ผ โ†’ ๐›ฝ unfoldr unfold Based on slightly modified versions of two sentences from the paper Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire Available here https://maartenfokkinga.github.io/utwente/mmf91m.pdf foldr foldRight