Fork me on GitHub
#clojuredesign-podcast
<
2020-09-02
>
Stefan07:09:48

Hi all! I’m listening to #76 at the moment, and you just said something like: if you’re missing some function in core, it’s probably because you don’t know about an idiom that you need to learn. I have a question 🙂 What about `unfold` ? I’ve used it a few times through `flatland.useful`, but is there an idiom that I’m missing that allows me to easily do it with core functions? Please note that `unfold` has termination built into it, which is an important feature. If you want an example, you could imagine a function that takes a euro amount of change that needs to be handed out by a shop employee, and returns the list of coins that makes up the change. So it’s the opposite of `reduce`, in the way that it takes a single value and creates a list of values from it.

nate15:09:57

Very interesting. I've never used unfold before. Can you give a few other examples of how it would be used?

Stefan19:09:05

Well, until recently my FP was mostly “playing around”, but I used it on Exercism for converting a number (single value) into roman numeral representation (list of characters):

``````(ns roman-numerals
(:require [flatland.useful.seq :refer [unfold]]))

(def values {1000 "M"
900 "CM"
500 "D"
400 "CD"
100 "C"
90 "XC"
50 "L"
40 "XL"
10 "X"
9 "IX"
5 "V"
4 "IV"
1 "I"})

(defn lookupLE [v m]
(let [ks (reverse (sort (keys m)))
highest (first (filter #(<= % v) ks))]
[highest (values highest)]))

(defn select-greatest [n]
(when (> n 0)
(let [[val numeral] (lookupLE n values)]
[numeral (- n val)])))

(defn numerals [n]
(apply str (unfold select-greatest n)))``````
And for the ISBN verifier:
``````(ns isbn-verifier
(:require [flatland.useful.seq :refer [unfold]]))

(defn isbn-digit? [c]
(or (= \X c)
(java.lang.Character/isDigit c)))

(defn isbn->digits [isbn]
(letfn [(characters-to-numbers [s]
(when-let [char (first s)]
(if (= \X char)
[10 (next s)]
[(java.lang.Character/digit char 10) (next s)])))]
(->> isbn
(filter isbn-digit?)
(unfold characters-to-numbers))))

(defn mod-11 [n] (mod n 11))

(defn isbn? [isbn]
(let [digits (isbn->digits isbn)]
(and (= 10 (count digits))
(not-any? #(= 10 %) (butlast digits))
(= 0 (->> digits
(map * (range 10 0 -1))
(reduce +)
(mod-11))))))``````
No real-world examples yet, but I’m pretty sure they will arise…

genekim18:07:36

Interesting! I had a bunch of interactions with some folks on exactly this on Twitter: https://twitter.com/RealGeneKim/status/1412277787679891459 In short, calling an API with “next-token” semantics is amenable to unfold. Which apparently may be added to clojure.core — how long this is taking and the thought being put into this is a fascinating glimpse into what it takes to get into core. 🙂

lodin14:09:27

@stefan.van.den.oord I have used iterate, take-while, and map, using a tuple holding the result and state. But yeah, that is too long to not be a function.

👍 3