Skip to content

Search content

From F# to Clojure > 8: From Pipelines to Transducers

From F# to Clojure Part 8
Several inputs pass through a reusable transformation and fan into multiple outputs.

An F# pipeline usually names both the transformation and its collection context:

Table of contents

On this page

Separate the Transformation

An F# function can make the Seq pipeline reusable. Clojure’s one-argument forms of filter and map instead return a transformation independent of any collection API:

let paidTotals orders =
    orders
    |> Seq.filter (fun order -> order.Paid)
    |> Seq.map (fun order -> order.Total)F#
(def paid-totals
  (comp
    (filter :paid?)
    (map :total)))Clojure

Use transduce to transform and reduce without creating intermediate sequences:

orders |> paidTotals |> Seq.sum
// 55F#
(transduce paid-totals + 0 orders)
;; => 55Clojure

Use the same transducer with into when the result should be a collection:

orders |> paidTotals |> Seq.toList
// [24; 31]F#
(into [] paid-totals orders)
;; => [24 31]Clojure

Transducers composed with comp read in data-flow order here: filter paid orders, then extract each total.

Produce a Sequence When Needed

sequence applies a transducer and exposes a lazy sequence:

paidTotals orders
// seq [24; 31]F#
(sequence paid-totals orders)
;; => (24 31)Clojure

This is useful when downstream code expects sequence operations but the transformation itself should remain reusable.

Stop an Unbounded Input

Some transducers, including take, can signal that reduction is complete:

Seq.initInfinite id
|> Seq.filter (fun value -> value % 2 <> 0)
|> Seq.map (fun value -> value * value)
|> Seq.take 3
|> Seq.toList
// [1; 9; 25]F#
(def first-three-odd-squares
  (comp
    (filter odd?)
    (map #(* % %))
    (take 3)))

(into [] first-three-odd-squares (range))
;; => [1 9 25]Clojure

Although (range) is unbounded, take stops the reduction after three accepted values.

When They Earn Their Keep

Use a transducer when a transformation is reused with different sources or destinations, when intermediate allocations matter, or when an API already accepts one. For a single short pipeline over a normal collection, ->> with sequence functions is usually easier to read.


Share this post

Previous in series
From F# to Clojure > 7: State and Concurrency
Next in series
From F# to Clojure > 9: From Quotations to Macros