view 6-Clojure/README.txt @ 80:a83309cdf5d3

Add details of Clojure's loop recursion approach
author IBBoard <dev@ibboard.co.uk>
date Thu, 06 Jun 2019 20:26:42 +0100
parents 29025305bbb7
children 0f57e5c2ae82
line wrap: on
line source

Install Clojure with "curl -O https://download.clojure.org/install/linux-install-1.10.0.442.sh; chmod +x linux-install-1.10.0.442.sh; sudo ./linux-install-1.10.0.442.sh" (it's a downloader that downloads the real package)

Clojure has a repl (clj), which needs rlwrap ("sudo zypper in rlwrap"). This then downloads some MORE files from Maven. Don't you just LOVE the Java ecosystem?!

Clojure is a Lisp dialect. It feels a little like Prolog in that it's logic and brackets galore, but Prolog is about predicates and pattern matching whereas Lisp has more of an imperative language with odd syntax
but sensible things like "if" statements. It uses strong, dynamic typing.

Running commands at the repl and prints output *and* the return value (which can be "nil").

Commands are prefix, not infix:
  "Normal" languages: 1 + 1
  Clojure: (+ 1 1)

Division of integers creates a (normalised) "ratio" type to maintain accuracy:
  (/ 2 4) → 1/2
  (/ 2.0 4) → 0.5

Prefix does have its advantages, though - it's more like a function and can have variable numbers of parameters:
  (+ 2 2 2 2) → 8

And it can be used to check order:
  (< 1 2 3) → true
  (< 1 3 2 4) → false

Strings are double-quoted, but characters can be escaped with a backslash outside quotes to become a string. When done with "str" it makes a string:
  (str \h \e \l \l \o) → "hello"
This seems like it would rarely be useful.

Strings and characters are different (just like in Java)
  (= "a" \a) → false

Clojure can pass code as arguments, e.g.:
  (if true (println "It's true!"))
but that's quite normal for functional languages.

The "if" function is basically "if <condition> <true>[ <false>]".

Note: 0 and "" are true but nil is false, so only false and the null value are falsey.

Clojure has four main data structures - lists (for code), vectors (for data), sets and maps.

You can't build a list Python style with "(1 2 3)". You need either "(list 1 2 3)" or the odd syntax "'(1 2 3)".
Lists can then be operated on with "first", "last", "rest" and "cons" (construct from head and tail) functions.
There are also functions like "nth" (nth <list> <0-based index>)

Vectors use sequare brackets. They're like a list, but you can implicitly index them (i.e. you don't need "nth") by doing ([vector] pos) because they're treated as functions.

first, last and rest can be used for pattern matching.

Sets use hash followed by squiggly brackets (which is easily confused with a map). Sorted sets use "(sorted-set <values> …)".
Manipulating sets is less clean - (clojure.set/union <set1> <set2>) for unioning sets and (clojure.set/difference <set1> <set2>) for difference.
Sets are also functions and so you can check if an item is in a set with (#{set} <value>). Returns "nil" if not in the set, else returns the value.

Maps are defined with squiggly brackets and have implicit key-value positions, so you can write"{:chewie :wookie :lea :human}" for keys :chewie and :lea with values :wookie and :human.
You can also put in commas, though.


":text" appears to be a unique identifier ("keyword"), but the book doesn't mention this until three pages after it started using it. They're like Ruby symbols and Prolog/Erland atoms.
Symbols point to something else and keywords point to themselves (although it isn't made clear what that means).
Keywords are also functions, so (map key) and (key map) are equivalent.

Maps can be merged with "merge" or with "merge-with", which takes a function to calculate the merged value.

Variables are assigned to with "(def <variable-name> <value>)". Functions are defined with "(defn <func-name> [<params list>] (<body>))".
There's also an optional docstring after <func-name>. Docs can be read with "(doc <func-name>)".

Function arguments can be quite complex and use pattern matching, e.g. getting the second value in a vector with:
  (defn line-end [[_ second]] second)
which takes a single arg (the vector "[_ second]") and the body just returns "second". Clojure calls this "destructuring". It ignores any additional values (e.g. "[_ c]" matches "[1 2 3 4 5]" and puts "2" in c and ignores the trailing 3,4,5).

Where "def" is used for long-term variables (which are created in user-space - the return result is #'user/<def-name>), "let" is used for local variables, which is useful within function definitions, e.g.:
  (defn centre_space [board] (let [[_ [_ c]] board] c))
which is a function with one argument (board) where the function body uses "let" with a vector containing a pattern to match and a variable to match against, and then a function that uses that vector.

There are also anonymous functions that use "(fn [args] (body))" - like a defn, but without a name. Alternatively, "#(function-body-with-%-as-a-variable)" defines an anonymous "reader macro" function with % bound to each value.
  (map (fn [w] (* 2 (count w))) my_list)
is equivalent to:
  (map #(* 2 (count %)) my_list)

As well as the map function, there's "apply" and "filter".

Also, some helper functions exist, like "odd?".

Functional languages do lazy tail recursion. Unless they're Clojure, because the JVM doesn't support it. Clojure does it with a  "loop" and a "recur" function. "loop" takes x and y with initial values and a function to call. See loop_recur.clj.