Skip to content

Instantly share code, notes, and snippets.

@metamorph
Last active May 23, 2017 10:03
Show Gist options
  • Save metamorph/36169ea4076ac9286bf0 to your computer and use it in GitHub Desktop.
Save metamorph/36169ea4076ac9286bf0 to your computer and use it in GitHub Desktop.
Laziness in Java and Clojure (with, and without, Transducers)

I thought that function pipelines worked in the same 'lazy' way as in Java.

However - it seems that the "laziness" in Clojure is about not realizing the full collection until someone asks for the first element.

In Java it's about pulling elements through the pipeline of functions.

In order to have the same behaviour in Clojure it's required to use Transducers.

I was slightly surprised to discover this.

(defn my-odd [n] (do
(println "PREDICATE " n)
(odd? n)))
(defn my-inc [n] (do
(println "INC " n)
(inc n)))
;; Standard pipeline
(->> [1 2 3 4 5]
(map my-inc)
(filter my-odd)
(take 1))
;; Output:
;; INC 1
;; INC 2
;; INC 3
;; INC 4
;; INC 5
;; PREDICATE 2
;; PREDICATE 3
;; PREDICATE 4
;; PREDICATE 5
;; PREDICATE 6
;; Same pipeline with tranducers
(into [] (comp (map my-inc) (filter my-odd) (take 1)) [1 2 3 4 5])
;; Output:
;; INC 1
;; PREDICATE 2
;; INC 2
;; PREDICATE 3
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isOdd = i -> {
System.out.println("PREDICATE: " + i);
return i % 2 != 0;
};
Function<Integer, Integer> inc = i -> {
System.out.println("INC: " + i);
return i + 1;
};
System.out.println(
Stream.of(1,2,3,4,5)
.map(inc)
.filter(isOdd)
.limit(1)
.collect(toList()));
}
}
/* Output: */
/*
INC: 1
PREDICATE: 2
INC: 2
PREDICATE: 3
[3]
*/
@neilclarke
Copy link

neilclarke commented May 23, 2017

I'd like to leave this here in case anyone else stumbles across this post, like I did:
Clojure Don’ts: Lazy Effects

This is the old “chunked sequence” conundrum. Like many other lazy sequence functions, map has an optimization which allows it to evaluate batches of 32 elements at a time.

So, if you changed the standard pipeline to process, say, 50 entries, you'd get the desired laziness (although it would process 32 entries before returning the 1st item with "take":

(->> [1 2 3 4 5 ... 49 50]
     (map my-inc)
     (filter my-odd)
     (take 1))
;;; ...
;;; INC  32
;;; ...
;;; PREDICATE  33
;;; --> (3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment