Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Programming with Clojure

Functional Programming with Clojure

A very basic introduction to Clojure given to a small group of C# developers for a brown-bag lunch.

Chad Taylor

June 12, 2013
Tweet

More Decks by Chad Taylor

Other Decks in Programming

Transcript

  1. Functional Programming • A programming paradigm based on the lambda

    calculus • Close to pure mathematics in terms of abstraction • No concept of application state • Treats code as data
  2. Clojure • A Lisp on the JVM and CLR •

    Interoperates with JARs and .NET libraries ◦ "There is an elephant in the room called all the code we have already written." - Rich Hickey • C# + Lisp + Java + "closure" = Clojure
  3. Values • 3 • "hello, world" • #{ "Tolkien" "Martin"

    "Butcher" } • { :lat 34.65 :lon 86.77 :city "Huntsville"}
  4. Immutability • Once a value is set, it cannot be

    changed • Can be simulated in C# with readonly • Example: Aster.Numerics.Vector3D • Always threadsafe • Everything in Clojure is immutable
  5. Phoenix.Settings and the Mutation Problem • Phoenix.Settings allows the storage

    of mutable data objects • Cannot enforce communication of settings changes to observers • Forces serialization of entire settings collection to ensure capture of all changes • Threadsafe?
  6. java.util.prefs and the Mutation Problem • Only allows the use

    of primitive types • Suffers from less expressiveness than Phoenix.Settings • Can properly notify observers of changes
  7. Clojure and the Mutation Problem • Not limited to primitive

    types • Does not suffer from lack of expressiveness • In fact, could build an entire prefs DSL • Can property notify observers • Threadsafe!
  8. Basic Syntax • Clojure is a Lisp (and so uses

    parens a lot) • Everything is a list • First element in list is in the operator position and indicates the function to call on the elements that follow ◦ Example: (+ 2 3 4)
  9. Basic Syntax (cont.) • Lists of data use the list

    or quote method, or the ' sugar: ◦ (list 1 2 3) => (1 2 3) ◦ (quote (1 2 3)) => (1 2 3) ◦ '(1 2 3) => (1 2 3) ◦ (1 2 3) => error; 1 is not a function
  10. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? 3 + 1 / 2 * 4 - 5 * 3
  11. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? 3 + (1 / 2 * 4) - (5 * 3)
  12. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? 3 + ((1 / 2) * 4) - (5 * 3)
  13. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (3 + ((1 / 2) * 4)) - (5 * 3)
  14. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? ((3 + ((1 / 2) * 4)) - (5 * 3))
  15. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (- (3 + ((1 / 2) * 4)) (5 * 3))
  16. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (- (+ 3 ((1 / 2) * 4)) (5 * 3))
  17. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (- (+ 3 (* (1 / 2) 4)) (* 5 3))
  18. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (- (+ 3 (* (/ 1 2) 4)) (* 5 3))
  19. Oh Those Parens! • The parens really are not that

    bad... • Quick - recall your order of operations! • What is the result of the following (C# rules)? (- (+ 3 (* (/ 1 2) 4)) (* 5 3)) => -10
  20. REPL - Read, Evaluate, Print, Loop • Read code from

    input into data structures • Evaluate the data structures • Print the result to the screen • Loop back to Read step
  21. Functions • A mapping from an input set to an

    output set that does not change the input • Note that these are the same rules as LINQ • Defined using the defn macro or the fn function (defn square [x] (* x x)) (square 2) => 4
  22. Local Variable Binding: let • Allows you to name a

    value within a context • It is like an immutable local variable (let [x (* 2 3)] (prn x)) => 6 nil
  23. Higher-order Functions • Does one of two things: ◦ Takes

    at least one function as a parameter ◦ Returns a function public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) Clojure equivalent: (filter pred coll) (filter even? (range 10)) => (2 4 6 8)
  24. Anonymous Functions • In C#, use lambda expressions: ◦ x

    => x > 3 ◦ (x,y) => x * y • In Clojure, uses # expressions: ◦ #(> % 3) ◦ #(* %1 %2) • Note that % matches the first parameter, and %1 and %2 are positional parameters
  25. Homoiconicity • The representation of a data structure is a

    data structure in Clojure (and other Lisps) • Makes it easy to write code that modifies other code • Makes the translation into an AST easier to visualize
  26. AST From C# Statement Statement: Assignment variable name: x type:

    int 4 Expression lhs operator: - rhs ((1 + (2 * 3))
  27. AST From C# Statement Statement: Assignment variable name: x type:

    int number: 4 Expression lhs operator: - rhs rhs operator: + Expression lhs number: 1 rhs operator: * Expression lhs number: 2 number: 3
  28. AST from a Clojure Expression - + 4 1 *

    2 3 The syntax is a serialized AST!
  29. Macros • Macros allow you to change the structure of

    a statement before it gets to the compiler • It happens during the evaluate step of REPL • Adds expressiveness, and can be used to build DSLs • In traditional Lisps, naming conflicts can occur across libraries, but...
  30. Macros (cont.) • Clojure solves this by placing macros in

    namespaces to avoid naming collisions • Macros should be used infrequently compared to functions (defmacro when "Evaluates test. If logical true, evaluates body in an implicit do." {:added "1.0"} [test & body] (list 'if test (cons 'do body)))
  31. But...What about Performance? • All Clojure code is compiled into

    bytecode, even during the REPL ◦ Maintains awesome performance of the platform • But copying all that data is expensive right? ◦ Since everything is part of a list in Clojure, everything can be represented in trees. ◦ You can copy the change from a node into a new tree that shares most of the nodes with the original data tree. ◦ This is cheap in both space & time complexity.
  32. Pipe Operator • In F#, the pipe operator ( |>

    ) is one of the most commonly used operators • Clojure equivalent is two-fold: -> and ->> • "Pushes" output to the first and last argument of next statement, respectively ◦ ->> is exactly equivalent to |>
  33. Bonus: Binary Search (defn binary-search "Searches for the target and

    returns the index or -1 if not found. Has average complexity of O(log n)." [coll target] ((fn [coll target start end] (let [mid (quot (+ start end) 2) m (nth coll mid)] (cond (> start end) -1 (> target m) (recur coll target (inc mid) end) (< target m) (recur coll target start (dec mid)) (= target m) mid))) coll target 0 (dec (count coll))))
  34. Bonus: 4th-order Runge-Kutta (defn rk4 "Implements a fourth-order Runge-Kutta method"

    [f x y h] (let [k1 (* h (f x y)) k2 (* h (f (+ x (* 1/2 h)) (map #(+ % (* 1/2 k1)) y))) k3 (* h (f (+ x (* 1/2 h)) (map #(+ % (* 1/2 k2)) y))) k4 (* h (f (+ x h) (map #(+ % k3) y))) k (* 1/6 (+ k1 (* 2 k2) (* 2 k3) k4))] [(+ x h) (map #(+ % k) y)]))
  35. Bonus: 4th-order Runge-Kutta (defn rk4-seq "Implements a fourth-order Runge-Kutta as

    an infinite list" [f x y h] ((fn rk4-rec [rx ry] (let [result (rk4 f rx ry h)] (cons [rx ry] (lazy-seq (rk4-rec (first result) (last result)))))) x y)) (take 5 (rk4-seq (fn [x y] (* 2 x)) 0 '(0) 1)) => ([0 (0)] [1 (1)] [2 (4)] [3 (9)] [4 (16)])
  36. Bonus: Conway's Game of Life (defn neighbours [[x y]] (for

    [dx [-1 0 1] dy (if (zero? dx) [-1 1] [-1 0 1])] [(+ dx x) (+ dy y)])) (defn step [cells] (set (for [[loc n] (frequencies (mapcat neighbours cells)) :when (or (= n 3) (and (= n 2) (cells loc)))] loc))) Source: http://clj-me.cgrand.net/2011/08/19/conways-game-of-life/
  37. The Expression Problem The Expression Problem is a new name

    for an old problem. The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts). - Philip Wadler Source: http://homepages.inf.ed.ac. uk/wadler/papers/expression/expression.txt
  38. There Is Much More! • Records (defrecord) • Protocols (defprotocol)

    • Tail-recursion (recur) • Lazy sequences (lazy-seq) • Vectors, arrays, maps • Concurrency (actors, STM) • Metadata
  39. More Links Leiningen - a build tool github.com/technomancy/leiningen Incanter -

    an R-like platform for statistical computing and graphics http://incanter.org Immutant - JBoss AS7-based application server http://immutant.org