- This workshop is aimed specifically at women. Why? In the past 7 years of my engineering career I have seen the disparity in the gender ratio at all the companies I have worked with. In some teams, I was the only female developer. In some teams, I was the only female. I have never had a female mentor before. But, I have also seen companies change, grow, become more aware of this issue, and find ways to fix it. This is a small attempt towards that. Getting more females to learn programming, learn Clojure and do awesome things that come with ability to command a computer.
- “So before we begin, I want to reinforce that you can program, that you can do math, that you can design car suspensions and fire suppression systems and spacecraft control software and distributed databases, regardless of what your classmates and media and even fellow engineers think.” - aphyr
- A quick round of thanks before we start. We would like to thank all
these people and groups for having created great content for clojure
workshops.
- Aditya Athalye
- Kyle Kingsbury (aphyr)
- ClojureBridge Berlin and London
- We have taken most of our ideas for this workshop from the content these folks have created.
- Also, thanks to nilenso, IN/Clojure and ClojureBridge core team.
- https://tinyurl.com/clj-bridge-blr
-> outline.org
- Programming is the process of creating a set of instructions that tell a computer how to perform a task. Programming can be done using a variety of computer “languages”.
- There are many such languages to choose from. Some of the other languages you might have heard of (or will hear of!) are C, JavaScript, Python, and Java etc.
- And Clojure, of course.
- Every program has two readers: the computer, and the human. Our job, as programmers, is to communicate clearly to both.
- Clojure is a modern dialect of a very old family of computer languages, called Lisp.
- Lisp is one of the oldest of all programming languages, invented by John McCarthy in 1958.
- The original language spawned many variant dialects, the most predominant of which today are Common Lisp and Scheme.
- Clojure (pronounced “closure”) is a new dialect of Lisp created by Rich Hickey. Like Scheme, Clojure is a functional dialect, meaning that it supports and encourages programming in a “functional style”.
- It is also closely related to Java as it runs on the Java platform and fully supports calling Java code from Clojure (you can ignore this for now).
- What can you do in Clojure?
- You can create web things, mobile things, sketch things. Practically, it is good for writing web async servers, running parallel tasks, simulations, data pipelines etc.
- A quick fun demo of live coding Flappy Bird in Clojure - https://www.youtube.com/watch?v=KZjFVdU8VLI
- Clojure is simple. That’s not to say it’s not powerful; it is. The number of concepts you have to know to start programming in Clojure is very small, however, and easy to grasp. Clojure grows with you as you learn it, and you can be very productive with a small subset of the language.
- Clojure is all-purpose. Some languages have a specific focus. JavaScript, for example, was traditionally used only in web pages (although that’s changed somewhat). Objective-C is used mainly for iPhone apps. We’re going to write small fun programs today, but you can use Clojure for any sort of application easily.
- Clojure is fun. That’s a matter of opinion, of course, but we think it holds true for most people who use Clojure. I hope that during this course you experience the joy of seeing a Clojure program come together and do something powerful and surprising.
- REPL stands for Read, Evaluate, Print Loop
- Many programming languages, including Clojure, have a way to execute code interactively so you get instant feedback.
- In other words, the code is read, then it is evaluated, then the result is printed, and you begin again–thus, a loop.
- Let’s start a REPL. and we can type and evaluate things as we go on learning them.
- As a first thing, type `nil` in your REPL, and see what it returns.
- `nil` is the most basic value in Clojure, it represents emptiness, nothing-doing, not-a-thing.
nil
Evaluate some normal functions in your REPL
(print "I am at ClojureBridge Bangalore")
(+ 1 2)
(- 2 1)
- s-expressions or symbolic expressions
(prn "Hello World")
1
2
"foo"
-2
1/2
(+ 1 2)
- All program code is written as s-expressions, and all s-expressions evaluate to a value.
- All Clojure code is written in a uniform structure. Unlike most other languages, no distinction is made between “expressions” and “statements”; all code and data are written as expressions. Clojure recognizes two kinds of structures:
- Literal representations of data structures (like numbers, strings, maps, and vectors)
1
2
"foo"
-2
1/2
'(1 2 3)
- we will go into these data structures later
- The last literal in the above list is a list (a list of numbers).
- Notice the quote in front of it. Try evaulating it without the quote:
(1 2 3)
- This failure tells you that Long is not a function.
- To go into detail a little, the REPL is trying to evaluate the above as an expression and expects the first element to be a function.
- So, how do you write a list without the REPL throwing you an exception, you quote it.
'(1 2 3)
- The single quote ’ escapes a sentence
- A quote says “Rather than evaluating this expression’s text, simply return the text itself, unchanged.”
- Try: ‘123, ‘“foo”, ‘true
- Quoting a value will return just the same value back
- Quoting an expression will return that expression unevaluated
'(+ 2 3)
(prn "Hello World")
(+ 1 2)
- Notice the parentheses. Parentheses enclose instructions to the machine in Clojure. A left parenthesis is the start of an expression, and a matching right parenthesis is the end of the expression. Normally, Clojure code has a lot of nested parentheses, or in other words, nested enclosed expressions.
- As you might have noticed an expression looks like a list of some kind.
- LISP originally stood for LISt Processing, and lists are still at the core of the language.
- Most of the “code” that you will write will be in the form of lists.
(+ 1 (- 4 (* 2 5)))
()
( ;; see what happens (you can't because your editor is too smart)
- The Clojure “Reader” (the ‘R’ part of the R.E.P.L) expects each open bracket to be accompanied by a corresponding closing bracket. i.e. all parentheses must be “balanced”.
- Clojure uses whitespace to separate operands, and it treats commas as whitespace.
(+ 1 2)
(+ 1, 2)
(+ 1,,,,, 2)
(+, 1, 2)
;; you can even do this!
(,+ 1 2)
- Infix and prefix notation refer to two different ways of representing an expression
- Infix notation places the operator in between the operands
1 + 2 + 3 * 4 1 + 2 + 3 * 4 1 + 2 + 12 3 + 3 * 4 1 + 14 6 * 4 15 24
- Infix notation should be familiar to most people. In infix notation, the order of operations is not always obvious, deciding which operation should happen first requires first deciding on an operator precedence hierarchy. In this case - BODMAS or PEMDAS or something similar.
- Prefix notation places the operator before or pre the operands
+ 1 + 2 * 3 4 + 1 + 2 12 + 1 14 15
- Prefix notation sidesteps the need to have an operator precedence hierarchy. Since the operands always follow the operator, there is no ambiguity around which operator applies to a given operand.
- Clojure uses the prefix notation, and pairs it with the
s-expressions and parantheses we’ve already seen to give us a
concise and unambiguous notation.
(+ 1 2 (* 3 4)) (+ 1 2 12 6 7 8 9 10 63 37826) 15
- In any expression, you have nouns and verbs
- Nouns are things in the world
- These are the values that we pass to an expression, or what an expression evaluates to.
- The values can be of different types.
- Most languages have some basic data types.
- number, string (text), boolean (true/false)
- nil, true, 0, and “hi there!” are all different types of values
- Functions are the verbs in programming.
- Given some values (arguments/parameters), they return a value.
(+ 1 2)
(prn "Hello World")
- In the examples you have seen so far, + and prn are functions.
- We have already covered what values/things are
- 1, 2.5, nil, true, “hello”; these are all values
- But as you can probably already tell, they are all different values
- In other words - different types of values
- Types relate to each other. for example in math, both 1 and 2.5 are
numbers; but 1 is a natural number while 2.5 is real number
- Every language has a type system; a particular way of organizing nouns into groups, figuring out which verbs make sense on which types, and relating types to one another
- Clojure’s type system is:
- strong in that operations on improper types are simply not allowed
(+ "invalid number" 2)
- dynamic because they are enforced when the program is run, instead of when the program is first read by the computer (in other words, at runtime vs compile time)
- strong in that operations on improper types are simply not allowed
List of basic types:
- Numbers: Integer, Ratio, Float - 1, 3/4, 2.5
- Strings: text like “hey”, “cats are so nice”, “ありがとう”
- Characters: \a, \b, \c
- Clojure has a special type called keyword
- eg - :foo, :bar
- Special because they are symbolic identifiers that evaluate to themselves
:foo
- They provide very fast equality tests and are most commonly used as keys in a map. (more on this map later)
- Identifiers that are used to refer to something else
- For example, there is a function called `inc` which is used to increment a number
- Unlike 0, “hi”, inc is a symbol
- When Clojure evaluates a symbol, it looks up that symbol’s meaning
- Type inc in your REPL, you will get the meaning of what inc represents
- We can also refer to symbol itself without evaluating it - `’inc`
- We use def to bind a symbol to its value
(def chosen-one "Harry Potter")
- Here, we have bound the symbol ‘chosen-one to the string “Harry Potter”
- If we had to type the same values over and over, it would be very hard to write a program. So, we bind them to symbols so we can refer to them in a way we can remember.
- Functions we have seen so far
(+ 1 2)
(str "this is a function " "that combines two strings")
(prn "this function prints whatever you give it")
(inc 42)
- A function is an independent, discrete piece of code that takes in some values (called arguments) and returns a value
- Each function takes in zero or more number of arguments and returns one value
- By now you’ve seen many examples of function calls
(+ 1 2 3 4)
- All Clojure operations have the same syntax: opening parenthesis, operator, operands, closing parenthesis.
- Function call is just another term for an operation where the operator is a function or a function expression (an expression that returns a function)
(1 2 3 4)
("test" 1 2 3)
- The above examples are not valid function calls as the operator is not a function
- Evaulate them to see what result you get
- You might see that exception a lot while coding in Clojure
- <x> cannot be cast to clojure.lang.IFn just means that you’re trying to use something as a function when it’s not
(defn same [x] x)
- Function definitions are composed of five main parts:
- defn
- Function name
- A docstring describing the function (optional)
- Parameters listed in brackets
- Function body (a list of expressions that will get evaluated when the fn is called)
- In the above example, you have defined a fn that takes an argument and returns it back
- Try it out
(same 42)
(same "am I the same")
(same :foo)
- write a function to add 42 to a number.
- Define a function greeting which:
- Given no arguments, returns “Hello, World!”
- Given one argument x, returns “Hello, x!”
- Given two arguments x and y, returns “x, y!”
;; Hint use the str function to concatenate strings
- This is another way of defining a fn
(fn same [x] x)
- You can also write functions without any names
(fn [x] x)
- The function body can contain forms of any kind
- Clojure automatically returns the last form evaluated
(defn return-something []
1
(+ 1 2)
2
"foo")
- All functions are created equal, there are no “special” functions. Even the core fns are the same as the ones you create.
(defn + [x y] (- x y)) ;; you can change anything you want
- But remember, with great power comes great responsibility
- Define a function make-thingy which takes a single argument x. It should return another function, which takes any number of arguments and always returns x.
((make-thingy 1) 2)
;=> 1
;; this is the definition of the function `constantly` in Clojure.
- So far, we’ve dealt with discrete pieces of data: one number, one string, one value. When programming, it is more often the case that you want to work with groups of data.
- Clojure has great facilities for working with these groups, or collections, of data. Not only does it provide four different types of collections, but it also provides a uniform way to use all of these collections together.
- A vector is a sequential collection of values.
- A vector may be empty.
- A vector may contain values of different types.
- Each value in a vector is numbered starting at 0, that number is called its index.
- The index is used to refer to each value when looking them up.
- To imagine a vector, imagine a box split into some number of equally-sized compartments.
- Each of those compartments has a number.
- You can put a piece of data inside each compartment and always know where to find it, as it has a number.
0 1 2 3 4 5 |-----+-----+-----+-----+-----+----| | "a" | "b" | "c" | "d" | "e" | "f | |-----+-----+-----+-----+-----+----|
- Note that the numbers start with 0. That may seem strange, but we often count from zero when programming.
- Vectors are written using square brackets with any number of pieces of data inside them, separated by spaces.
- Examples:
[1 2 3 4 5]
[56.9 60.2 61.8 63.1 54.3 66.4 66.5 68.1 70.2 69.2 63.1 57.1]
[]
[1 "abc" :foo]
- The next two functions are used to make new vectors.
- The vector function takes any number of items and puts them in a new vector.
- `conj` is an interesting function that you’ll see used with all the data structures.
- With vectors, it takes a vector and an item and returns a new vector with that item added to the end of the vector.
- Why the name conj? conj is short for conjoin, which means to join or combine.
- This is what we’re doing: we’re joining the new item to the vector.
(vector 5 10 15)
;=> [5 10 15]
(conj [5 10] 15)
;=> [5 10 15]
- Now, take a look at these four functions.
- count gives us a count of the number of items in a vector.
(count [5 10 15])
;=> 3
- nth gives us the nth item in the vector.
(nth [5 10 15] 1)
;=> 10
- Note that we start counting at 0, so in the example, calling nth with the number 1 gives us what we’d call the second element when we aren’t programming.
- first returns the first item in the collection.
(first [5 10 15])
(nth [5 10 15] 0)
;=> 5
- rest returns all except the first item.
(rest [5 10 15])
;=> (10 15)
- Try not to think about that and nth at the same time, as they can be confusing.
- Maps hold a set of keys and values associated with them.
- You can think of it like a dictionary: you look up things using a word (a keyword) and see the definition (its value).
- If you’ve programmed in another language, you might have seen something like maps–maybe called dictionaries, hashes, or associative arrays.
{:first "Harry"
:middle "James"
:last "Potter"
:occupation "Auror"}
- We write maps by enclosing alternating keys and values in curly braces, like above.
- Maps are useful because they can hold data in a way we normally think about it.
- Take our made up example, Harry Potter.
- A map can hold his first name, middle name and last name, his occupation, or anything else.
- It’s a simple way to collect that data and make it easy to look up.
{:a 1 :b "two"}
- This example is an empty map. It is a map that is ready to hold some things, but doesn’t have anything in it yet.
{}
- assoc and dissoc are paired functions: they associate and disassociate items from a map.
- See how we add the last name “Granger” to the map with assoc, and then we remove it with dissoc.
(assoc {:first "Hermione"} :last "Granger")
;=> {:first "Hermione", :last "Granger"}
(dissoc {:first "Hermione" :last "Granger"} :last)
;=> {:first "Hermione"}
- merge merges two maps together to make a new map.
(merge {:first "Hermione"} {:last "Granger"})
;=> {:first "Hermione", :last "Granger"}
- `count`, every collection has this function.
(count {:first "Hermione" :last "Granger"})
;=> 2
- Why do you think the answer is two? count is returning the number of associations.
- Since map is a key-value pair, the key is used to get a value from a map.
- One of the ways often used in Clojure is the examples below.
(get {:first "Hermione" :last "Granger"} :first)
;=> "Hermione"
(get {:first "Hermione"} :last)
;=> nil
(get {:first "Hermione"} :last "NA")
;=> "NA"
- In the last example, we supplied a backup value “NA”. This works when the key we asked for is not in the map.
- We can use also use keyword like using a function in order to look up values in a map.
(:first {:first "Hermione" :last "Granger"})
;=> "Hermione"
(:last {:first "Hermione"})
;=> nil
(:last {:first "Hermione"} "NA")
;=> "NA"
- Then we have keys and vals, which are pretty simple: they return the keys and values in the map.
(keys {:first "Hermione" :last "Granger"})
;=> (:first :last)
(vals {:first "Hermione" :last "Granger"})
;=> ("Hermione" "Granger")
- The order is not guaranteed, so we could have gotten (:first :last) or (:last :first).
- After the creation, we want to save a new value associated to the key.
- The assoc function can be used by assigning a new value to the existing key.
(def hello {:count 1 :words "hello"})
(assoc hello :words "bye")
;=> {:count 1, :words "bye"}
- Also, there’s handy function update.
- The function takes map and a key with a function.
- The value of specified key will be the first argument of the given function.
(update hello :count inc)
;=> {:count 2, :words "hello"}
(update hello :words str ", world")
;=> {:count 1, :words "hello, world"}
- The update-in function works like update, but takes a vector of keys to update at a path to a nested map.
(def mine {:pet {:age 5 :name "able"}})
(update-in mine [:pet :age] - 3)
;=> {:pet {:age 2, :name "able"}}
- Simple values such as numbers, keywords, and strings are not the only types of things you can put into collections.
- You can also put other collections into collections, so you can have a vector of maps, or a list of vectors, or whatever combination fits your data.
(def wizards [{:name "Harry Potter"
:house "Gryffindor"}
{:name "Draco Malfoy"
:house "Slytherin"}])
(def houses {:gryffindor {:colors ["scarlet" "gold"]
:points 200}
:slytherin {:colors ["green" "silver"]
:points 150}})
- add Ron and Hermione to the collection of wizards
- write a function to to extract points of a house given the name
- let is a Clojure special form, a fundamental building block of the language
- When you are creating functions, you may want to assign names to values in order to reuse those values or make your code more readable.
- Inside of a function, however, you should not use def, like you would outside of a function.
- Instead, you should use a special form called let.
- Like def, let creates a binding
(let [mangoes 3
oranges 5]
(+ mangoes oranges))
(def x 32)
(prn x)
(let [x 42]
(prn x))
- let lets you evaulate expressions in the context of its bindings
- In other languages, it is called a local variable assignment
- In Clojure, it has the different name: lexical binding
- Clojure’s lexically bound variables are available to use in a limited code block (scope)
- Names defined in a let take precedence over the names in the outer context.
- Write as many bindings (key-value pairs) as we want within the square brackets
(let [x 1
y 1
z (+ x y)
z (* 2 z)]
(println z)
x)
- let also returns the last value in its body
- Control flow is the programming term for deciding how to react to a given circumstance. We make decisions like this all the time
- If your charging station is dead, take a cab
- If your wet and dry waste is not segregated, pay a fine to bbmp
- If something is true or false or a bunch of things are true or false, react
- Most of what we do today in software is this kind of decision making
- Is the user input valid? if yes, save her data, otherwise throw an error
- Hence, changing the order of evaluation in a language is called control flow, and lets programs make decisions based on varying circumstances
(if (= 2 2) "yes" "no")
(if (< (+ y 40) 150)
(+ y 40)
-150)
(if "conditional-expression"
"expression-to-evaluate-when-true"
"expression-to-evaluate-when-false")
- Truthiness:
- When testing the truth of an expression, Clojure considers the values nil and false to be false and everything else to be true. Here are some examples
(if "anything other than nil or false is considered true"
"A string is considered true"
"A string is not considered true")
(if nil
"nil is considered true"
"nil is not considered true")
(if (get {:a 1} :b)
"expressions which evaluate to nil are considered true"
"expressions which evaluate to nil are not considered true")
- if statements are not limited to testing only one thing, you can test multiple conditions using boolean logic. Boolean logic refers to combining and changing the results of predicates using and, or, and not.
(or 1 2)
(or false 2)
(or true false)
(and 1 2)
(and false false)
(and false 2)
(and 2 false)
(not false)
(defn leap-year? [year]
"Every four years, except years divisible by 100, but yes for years divisible by 400.")
(defn leap-year?
"Every four years, except years divisible by 100, but yes for years divisible by 400."
[year]
(= 0 (mod year 4)))
(defn leap-year?
"Every four years, except years divisible by 100, but yes for years divisible by 400."
[year]
(and (= 0 (mod year 4)
(= 0 (mod year 400)))))
(defn leap-year?
"Every four years, except years divisible by 100, but yes for years divisible by 400."
[year]
(and (= 0 (mod year 4))
(or (= 0 (mod year 400))
(not (= 0 (mod year 100))))))
- If you’ve never seen this concept in programming before, remember
that it follows the common sense way you look at things normally.
- Is this and that true? Only if both are true.
- Is this or that true? Yes, if either – or both! – are.
- Is this not true? Yes, if it’s false.
- When you only want to take one branch of an if, you can use when:
(when false
(prn :hi)
(prn :there))
(when true
(prn :hi)
(prn :there))
- Because there is only one path to take, when takes any number of expressions, and evaluates them only when the predicate is truthy. If the predicate evaluates to nil or false, when does not evaluate its body, and returns nil.
Using the control flow constructs we’ve learned, write a schedule function which, given an hour of the day, returns what you’ll be doing at that time.
(schedule 18) ;; for us, returns :dinner
- So far, we’ve seen and written functions that take in some data as arguments, perform a simple process or calculation using this data, and return some data as a result
- Now lets look at a function that performs a slightly more complex task
(def wizards {:hermione {:name "Hermione Granger"
:house :gryffindor}
:draco {:name "Draco Malfoy"
:house :slytherin}
:padma {:name "Padma Patil"
:house :ravenclaw}
:cedric {:name "Cedric Diggory"
:house :hufflepuff}})
(def houses {:gryffindor {:colors ["scarlet" "gold"]
:points 200}
:slytherin {:colors ["green" "silver"]
:points 150}
:ravenclaw {:colors ["blue" "bronze"]
:points 200}
:hufflepuff {:colors ["yellow" "black"]
:points 170}})
- Given this data, lets say we want to add points to the wizard :cedric’s house
- We can break this task down into two steps:
- Figure out the wizard’s house using the wizards data
(defn get-wizards-house [wizard] (:house (wizard wizards)))
- Add points to the house
(defn add-points-to-house [points house] (update-in houses [house :points] + points))
- Clojure allows us to pass in the result of one function as an
argument to another function. This is referred to as “Function
Composition”
- Lets compose our get-wizards-house and add-points-to-house functions into a third function so we can add points using wizards
(defn add-points-by-wizard [points wizard] (add-points-to-house points (get-wizards-house wizard))) ;; Now try this: (add-points-by-wizard 100 :cedric)
- We can use function composition to build up a complex multi-step computation or process using small, simple, and reusable functions. This makes it much easier to reason about our code in bite-sized pieces and helps keep repetition down to a minimum
Write a function to subtract points from a house using a wizard, as above. Try to reuse as much code as possible