Functional Patterns
Prerequisites: Anonymous functions and do blocks
Julia code often combines small functions into pipelines.
Piping and composition
words = ["Julia", "is", "fast", "and", "fun"]
result = words |> x -> filter(w -> length(w) > 2, x) |> x -> map(uppercase, x) |> x -> join(x, "-")
println(result)
clean = strip ∘ lowercase
println("[", clean(" HeLLo "), "]")
println((sqrt ∘ abs)(-16))
println(map(string ∘ length, words))
JULIA-FAST-AND-FUN
[hello]
4.0
["5", "2", "4", "3", "3"]
x |> fmeansf(x).f ∘ g(type\circ+ Tab) is composition:(f ∘ g)(x) = f(g(x)).
Partial application
Many functions have a one-argument curried form that returns a function:
xs = [1, 5, 10, 15]
println(filter(>(4), xs))
println(count(==(10), xs))
println(findall(in([5, 15]), xs))
println(map(Base.Fix2(-, 1), xs))
names = ["ada", "alan", "grace"]
println(filter(startswith("a"), names))
[5, 10, 15]
1
[2, 4]
[0, 4, 9, 14]
["ada", "alan"]
>(4) is a function meaning x -> x > 4; the same works for ==, <,
in, startswith, endswith, contains, and more. Base.Fix2(f, b)
fixes the second argument of any function.
Closures that keep state
function make_counter()
count = 0
return () -> (count += 1)
end
c = make_counter()
c(); c()
println(c())
function memoize(f)
cache = Dict{Any,Any}()
return x -> get!(() -> f(x), cache, x)
end
slow_square(x) = (sleep(0.01); x^2)
fast_square = memoize(slow_square)
println(fast_square(12), " ", fast_square(12))
3
144 144
get!(f, dict, key) calls f() only when the key is missing — the
do-block-friendly form of get!.
mapreduce
orders = [(item = "pen", qty = 3, price = 1.5),
(item = "book", qty = 1, price = 12.0),
(item = "bag", qty = 2, price = 20.0)]
total = mapreduce(o -> o.qty * o.price, +, orders)
println(total)
println(sum(o -> o.qty * o.price, orders))
println(maximum(o -> o.price, orders))
println(argmax(o -> o.price, orders).item)
56.5
56.5
20.0
bag
mapreduce(f, op, xs) maps and reduces in one pass. Most reductions
(sum, maximum, any, all, count) accept a function first.