Parametricity
or why types are useful
#cljsyd - May, 2015
Leonardo Borges
@leonardo_borges
www.atlassian.com
www.leonardoborges.com
Here’s the plan
‣ Parametricity and Fast and loose reasoning
‣ Breaking parametricity
‣ Property-based tests
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
xs)
Theorem
Every element A in the result sequence appears in the input.
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
xs)
(ann irrelevant (All [a b] [a -> b]))
(defn irrelevant [a]
(irrelevant a))
Theorem
This function never returns because if it did, it would never have compiled
(ann irrelevant (All [a b] [a -> b]))
(defn irrelevant [a]
(irrelevant a))
(ann even? [Number -> Boolean])
(defn even? [p]
(= (mod p 2) 0))
Theorem
The even function returns either true or false
(ann even? [Number -> Boolean])
(defn even? [p]
(= (mod p 2) 0))
Escape hatches
‣ null (actually not an issue in core.typed)
‣ exceptions
‣ Type-casing (isInstanceOf)
‣ Type-casting (asInstanceOf)
‣ Side-effects
‣ equals/toString/hashCode
‣ notify/wait
‣ classOf/.getClass
‣ General recursion
Escape hatches
‣ null (actually not an issue in core.typed)
‣ exceptions
‣ Type-casing (isInstanceOf)
‣ Type-casting (asInstanceOf)
‣ Side-effects
‣ equals/toString/hashCode
‣ notify/wait
‣ classOf/.getClass
‣ General recursion
Theorem
Every element A in the result sequence appears in the input.
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
nil)
Theorem
Every element A in the result sequence appears in the input.
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
nil)
Doesn’t compile with core.typed!
Theorem
This function ignores its argument and consistently returns either true or false
(ann irrelevant (All [a] [a -> Boolean]))
(defn irrelevant [a]
(or (instance? Long 1)
(and (instance? String a)
(< (count a) 10))))
Theorem
This function ignores its argument and consistently returns either true or false
(ann irrelevant (All [a] [a -> Boolean]))
(defn irrelevant [a]
(or (instance? Long 1)
(and (instance? String a)
(< (count a) 10))))
Theorem
Every A element in the result list appears in the input list
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
(cons (cast "abc" 'x) xs))
Theorem
Every A element in the result list appears in the input list
(ann irrelevant (All [x] [(Seq x) -> (Seq x)]))
(defn irrelevant [xs]
(cons (cast "abc" 'x) xs))
Doesn’t compile with core.typed!
Theorem
This function only ever does one thing —return its argument
(ann irrelevant (All [a] [a -> a]))
(defn irrelevant [x]
(prn "hi")
x)
Theorem
This function only ever does one thing —return its argument
(ann irrelevant (All [a] [a -> a]))
(defn irrelevant [x]
(prn "hi")
x)
Theorem
This function ignores its argument to return one of 2^32 values.
(ann irrelevant (All [a] [a -> Int]))
(defn irrelevant [x]
(-> x
str
(.length)))
Theorem
This function ignores its argument to return one of 2^32 values.
(ann irrelevant (All [a] [a -> Int]))
(defn irrelevant [x]
(-> x
str
(.length)))
Theorem
This function always returns Nil and so cannot possibly reverse the list
(ann reverse (All [a b] [(Seq a) -> (Seq b)]))
(defn reverse [as]
(reduce (fn [acc a]
(conj acc (cast b a))) nil as))
Theorem
This function always returns Nil and so cannot possibly reverse the list
(ann reverse (All [a b] [(Seq a) -> (Seq b)]))
(defn reverse [as]
(reduce (fn [acc a]
(conj acc (cast b a))) nil as))
Doesn’t compile with core.typed!
Escape hatches
‣ null (actually not an issue in core.typed)
‣ exceptions
‣ Type-casing (isInstanceOf)
‣ Type-casting (asInstanceOf)
‣ Side-effects
‣ equals/toString/hashCode
‣ notify/wait
‣ classOf/.getClass
‣ General recursion
[2] http://www.cse.chalmers.se/~nad/publications/danielsson-et-al-popl2006.pdf
[1] http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf
Where does this thinking come from?
‣ Theorems for Free, Philip Wadler [1]
‣ Fast and Loose reasoning is Morally correct, John Hughes et al. [2]
Where does this thinking come from?
What about docstrings?
Example: repeat
(defn repeat
"Returns a lazy (infinite!, or length n if supplied) sequence of xs."
([x] (clojure.lang.Repeat/create x))
([n x] (clojure.lang.Repeat/create n x)))
Example: repeat
(defn repeat
"Returns a lazy (infinite!, or length n if supplied) sequence of xs."
([x] (clojure.lang.Repeat/create x))
([n x] (clojure.lang.Repeat/create n x)))
Example: repeat
(ann repeat (All [x]
(IFn [x -> (ASeq x)]
[AnyInteger x -> (ASeq x)])))
(defn repeat
"Returns a lazy (infinite!, or length n if supplied) sequence of xs."
([x] (clojure.lang.Repeat/create x))
([n x] (clojure.lang.Repeat/create n x)))
Example: iterate
(ann iterate (All [x]
[[x -> x] x -> (ASeq x)]))
(defn iterate
"Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of
side-effects"
[f x] (clojure.lang.Iterate/create f x) )
Example: iterate
(ann iterate (All [x]
[[x -> x] x -> (ASeq x)]))
(defn iterate
"Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of
side-effects"
[f x] (clojure.lang.Iterate/create f x) )
Example: iterate
(ann iterate (All [x]
[[x -> x] x -> (ASeq x)]))
(defn iterate
"Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of
side-effects"
[f x] (clojure.lang.Iterate/create f x) )
Some other core.typed niceties
(ann underage? [(HMap :mandatory {:name String
:age Num}) -> Boolean])
(defn underage? [p]
(< (:age p) 18))
Some other core.typed niceties
(t/defalias Person (HMap :mandatory {:name String
:age Num}))
(ann underage? [Person -> Boolean])
(defn underage? [p]
(< (:age p) 18))
Some other core.typed niceties
(underage? {:age 32})
Type error:
Domains:
Person
Arguments:
(t/HMap :mandatory {:age (t/Val 17)} :complete? true)
Ranges:
java.lang.Boolean
Some other core.typed niceties
(underage? {:name "Leo" :age 32})
So types can check everything, right?
So types can check everything, right?
nope
Property based testing
(ann does-not-reverse (All [a] [(Seq a) -> (Seq a)]))
(defn does-not-reverse [xs]
...)
Property based testing
(def does-not-reverse-prop-1
(prop/for-all [x (gen/vector gen/simple-type)]
(= (does-not-reverse (does-not-reverse x))
x)))
(def does-not-reverse-prop-2
(prop/for-all [x (gen/vector gen/simple-type)
y (gen/vector gen/simple-type)]
(= (does-not-reverse (concat x y))
(concat (does-not-reverse x)
(does-not-reverse y)))))
Takeaway points
‣ Don’t dismiss types too quickly. They can improve code comprehension
and quality
‣ Even comments expressed in terms of types can be a big help when
reading foreign code
‣ Write property-based tests first, unit tests second
Thanks
Questions?
Leonardo Borges
@leonardo_borges
www.atlassian.com
www.leonardoborges.com

Parametricity - #cljsyd - May, 2015

  • 1.
    Parametricity or why typesare useful #cljsyd - May, 2015 Leonardo Borges @leonardo_borges www.atlassian.com www.leonardoborges.com
  • 2.
    Here’s the plan ‣Parametricity and Fast and loose reasoning ‣ Breaking parametricity ‣ Property-based tests
  • 3.
    (ann irrelevant (All[x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] xs)
  • 4.
    Theorem Every element Ain the result sequence appears in the input. (ann irrelevant (All [x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] xs)
  • 5.
    (ann irrelevant (All[a b] [a -> b])) (defn irrelevant [a] (irrelevant a))
  • 6.
    Theorem This function neverreturns because if it did, it would never have compiled (ann irrelevant (All [a b] [a -> b])) (defn irrelevant [a] (irrelevant a))
  • 7.
    (ann even? [Number-> Boolean]) (defn even? [p] (= (mod p 2) 0))
  • 8.
    Theorem The even functionreturns either true or false (ann even? [Number -> Boolean]) (defn even? [p] (= (mod p 2) 0))
  • 9.
    Escape hatches ‣ null(actually not an issue in core.typed) ‣ exceptions ‣ Type-casing (isInstanceOf) ‣ Type-casting (asInstanceOf) ‣ Side-effects ‣ equals/toString/hashCode ‣ notify/wait ‣ classOf/.getClass ‣ General recursion
  • 10.
    Escape hatches ‣ null(actually not an issue in core.typed) ‣ exceptions ‣ Type-casing (isInstanceOf) ‣ Type-casting (asInstanceOf) ‣ Side-effects ‣ equals/toString/hashCode ‣ notify/wait ‣ classOf/.getClass ‣ General recursion
  • 11.
    Theorem Every element Ain the result sequence appears in the input. (ann irrelevant (All [x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] nil)
  • 12.
    Theorem Every element Ain the result sequence appears in the input. (ann irrelevant (All [x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] nil) Doesn’t compile with core.typed!
  • 13.
    Theorem This function ignoresits argument and consistently returns either true or false (ann irrelevant (All [a] [a -> Boolean])) (defn irrelevant [a] (or (instance? Long 1) (and (instance? String a) (< (count a) 10))))
  • 14.
    Theorem This function ignoresits argument and consistently returns either true or false (ann irrelevant (All [a] [a -> Boolean])) (defn irrelevant [a] (or (instance? Long 1) (and (instance? String a) (< (count a) 10))))
  • 15.
    Theorem Every A elementin the result list appears in the input list (ann irrelevant (All [x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] (cons (cast "abc" 'x) xs))
  • 16.
    Theorem Every A elementin the result list appears in the input list (ann irrelevant (All [x] [(Seq x) -> (Seq x)])) (defn irrelevant [xs] (cons (cast "abc" 'x) xs)) Doesn’t compile with core.typed!
  • 17.
    Theorem This function onlyever does one thing —return its argument (ann irrelevant (All [a] [a -> a])) (defn irrelevant [x] (prn "hi") x)
  • 18.
    Theorem This function onlyever does one thing —return its argument (ann irrelevant (All [a] [a -> a])) (defn irrelevant [x] (prn "hi") x)
  • 19.
    Theorem This function ignoresits argument to return one of 2^32 values. (ann irrelevant (All [a] [a -> Int])) (defn irrelevant [x] (-> x str (.length)))
  • 20.
    Theorem This function ignoresits argument to return one of 2^32 values. (ann irrelevant (All [a] [a -> Int])) (defn irrelevant [x] (-> x str (.length)))
  • 21.
    Theorem This function alwaysreturns Nil and so cannot possibly reverse the list (ann reverse (All [a b] [(Seq a) -> (Seq b)])) (defn reverse [as] (reduce (fn [acc a] (conj acc (cast b a))) nil as))
  • 22.
    Theorem This function alwaysreturns Nil and so cannot possibly reverse the list (ann reverse (All [a b] [(Seq a) -> (Seq b)])) (defn reverse [as] (reduce (fn [acc a] (conj acc (cast b a))) nil as)) Doesn’t compile with core.typed!
  • 23.
    Escape hatches ‣ null(actually not an issue in core.typed) ‣ exceptions ‣ Type-casing (isInstanceOf) ‣ Type-casting (asInstanceOf) ‣ Side-effects ‣ equals/toString/hashCode ‣ notify/wait ‣ classOf/.getClass ‣ General recursion
  • 24.
    [2] http://www.cse.chalmers.se/~nad/publications/danielsson-et-al-popl2006.pdf [1] http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf Wheredoes this thinking come from? ‣ Theorems for Free, Philip Wadler [1] ‣ Fast and Loose reasoning is Morally correct, John Hughes et al. [2]
  • 25.
    Where does thisthinking come from?
  • 26.
  • 27.
    Example: repeat (defn repeat "Returnsa lazy (infinite!, or length n if supplied) sequence of xs." ([x] (clojure.lang.Repeat/create x)) ([n x] (clojure.lang.Repeat/create n x)))
  • 28.
    Example: repeat (defn repeat "Returnsa lazy (infinite!, or length n if supplied) sequence of xs." ([x] (clojure.lang.Repeat/create x)) ([n x] (clojure.lang.Repeat/create n x)))
  • 29.
    Example: repeat (ann repeat(All [x] (IFn [x -> (ASeq x)] [AnyInteger x -> (ASeq x)]))) (defn repeat "Returns a lazy (infinite!, or length n if supplied) sequence of xs." ([x] (clojure.lang.Repeat/create x)) ([n x] (clojure.lang.Repeat/create n x)))
  • 30.
    Example: iterate (ann iterate(All [x] [[x -> x] x -> (ASeq x)])) (defn iterate "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects" [f x] (clojure.lang.Iterate/create f x) )
  • 31.
    Example: iterate (ann iterate(All [x] [[x -> x] x -> (ASeq x)])) (defn iterate "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects" [f x] (clojure.lang.Iterate/create f x) )
  • 32.
    Example: iterate (ann iterate(All [x] [[x -> x] x -> (ASeq x)])) (defn iterate "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects" [f x] (clojure.lang.Iterate/create f x) )
  • 33.
    Some other core.typedniceties (ann underage? [(HMap :mandatory {:name String :age Num}) -> Boolean]) (defn underage? [p] (< (:age p) 18))
  • 34.
    Some other core.typedniceties (t/defalias Person (HMap :mandatory {:name String :age Num})) (ann underage? [Person -> Boolean]) (defn underage? [p] (< (:age p) 18))
  • 35.
    Some other core.typedniceties (underage? {:age 32}) Type error: Domains: Person Arguments: (t/HMap :mandatory {:age (t/Val 17)} :complete? true) Ranges: java.lang.Boolean
  • 36.
    Some other core.typedniceties (underage? {:name "Leo" :age 32})
  • 37.
    So types cancheck everything, right?
  • 38.
    So types cancheck everything, right? nope
  • 39.
    Property based testing (anndoes-not-reverse (All [a] [(Seq a) -> (Seq a)])) (defn does-not-reverse [xs] ...)
  • 40.
    Property based testing (defdoes-not-reverse-prop-1 (prop/for-all [x (gen/vector gen/simple-type)] (= (does-not-reverse (does-not-reverse x)) x))) (def does-not-reverse-prop-2 (prop/for-all [x (gen/vector gen/simple-type) y (gen/vector gen/simple-type)] (= (does-not-reverse (concat x y)) (concat (does-not-reverse x) (does-not-reverse y)))))
  • 41.
    Takeaway points ‣ Don’tdismiss types too quickly. They can improve code comprehension and quality ‣ Even comments expressed in terms of types can be a big help when reading foreign code ‣ Write property-based tests first, unit tests second
  • 42.