Last active
September 3, 2015 00:03
-
-
Save joakin/99fde46a8a50cc352764 to your computer and use it in GitHub Desktop.
Diving into Clojurescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# The repl | |
This is a repl, a read-eval-print-loop. Very similar to the developer console | |
on the browser when working with JS. | |
The repl is a very important part of ClojureScript development, since it | |
allows us to immediately try the code we write and experiment without having | |
to run the application from the begining."} | |
{:type :stop} | |
{:type :markdown :value | |
"To evaluate a form, focus on the input at the bottom, and write `true`, for | |
example. When you hit enter you will see the entry appear on the history repl | |
at the bottom like this:"} | |
{:type :input :value "true"} | |
{:type :stop} | |
{:type :markdown :value | |
"# Basic types | |
Most of the basic types on cljs are the same as in JS. | |
That includes booleans, numbers and strings."} | |
{:type :input :value "false"} | |
{:type :input :value "1.02"} | |
{:type :input :value "\"Hi! I'm learning ClojureScript\""} | |
{:type :stop} | |
{:type :markdown :value | |
"In CLJS `null` is called `nil`, but it is equivalent to js's null."} | |
{:type :input :value "nil"} | |
{:type :stop} | |
{:type :markdown :value | |
"There's another basic type called *keywords*. They are similar to strings, but more like constants. | |
We usually use keywords as constants or enumerations of values, and as keys in maps/objects. | |
To create a keyword just put a colon in front of a symbol, like this:"} | |
{:type :input :value ":hi-friends!"} | |
{:type :input :value ":red"} | |
{:type :input :value ":blue"} | |
{:type :stop} | |
{:type :markdown :value | |
"As you can see in CLJS the rules for a symbol name are more flexible than in JS. | |
In JS you can only have ascii chars, _, $ and numbers. In CLJS we can have | |
richer symbols that allow us to express ourselves better. You can use `-` to separate words, and symbols like `!` and `?`"} | |
{:type :input :value ":omg-this-is-awesome!"} | |
{:type :input :value ":done?"} | |
{:type :input :value ":user/name"} | |
{:type :stop} | |
{:type :markdown :value | |
"Now we know about the primitive types, let's move on to more interesting | |
basics, like **[Lesson 2: Playing with | |
functions](#/file/2)**"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Mutability in CLJS | |
As you've seen, previously all functions that operate on CLJS data structures | |
do **not mutate** the variable, but return a new one. | |
How do we then, store state in our application if every time we change | |
something it gives us a new value and we can't keep a reference to the | |
current state? | |
The answer in CLJS is `atom`s. An `atom` is like a JS variable, it is a reference | |
to a data structure. | |
CLJS provides functions to change the reference of the atom in a way that | |
allows us to keep the functions that manipulate the data pure and | |
referentially trasparent. | |
"} | |
{:type :stop} | |
{:type :markdown :value | |
"## `atom` | |
To create a mutable reference to data, use the function `atom` on some data."} | |
{:type :input :value "(def counter (atom 0))"} | |
{:type :markdown :value | |
"To get the data out of an `atom` use the `@` sign or the `deref` function:"} | |
{:type :input :value "@counter"} | |
{:type :stop} | |
{:type :markdown :value | |
"To update an `atom`, use the function `swap!`:"} | |
{:type :input :value "(swap! counter inc)"} | |
{:type :input :value "(swap! counter + 2)"} | |
{:type :stop} | |
{:type :markdown :value | |
"To reset the value of an `atom`, use the function `reset!`:"} | |
{:type :input :value "(reset! counter {:count 0 :inc-by 2})"} | |
{:type :stop} | |
{:type :markdown :value | |
"We could then define a pure function to update our counter:"} | |
{:type :input :value "(defn inc-counter [c] (assoc c :count (+ (:count c) (:inc-by c))))"} | |
{:type :markdown :value | |
"And use `swap!` when we want to modify the state:"} | |
{:type :input :value "(swap! counter inc-counter)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Atoms are a pretty useful abstraction for managing state, while making it | |
easy to have your logic in pure functions. | |
They can also have watchers, peek at `(doc add-watch)` or look on clojuredocs | |
to learn more about them. | |
Let's move to the final recap, since we've already learnt a lot! | |
[Final recap](#/file/100)"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Congratulations! | |
You've learnt a lot about the CLJS language, and are now ready to dive into | |
learning with real projects. | |
With time, you'll dive into more topics of the language, like destructuring | |
(which we haven't covered), requiring libraries and namespaces, and setting | |
up projects. | |
For now, I highly recommend that you install | |
[Leiningen](http://leiningen.org/), which is Clojure's project management | |
tool (kind of like `npm` is for node) and you try out a new project using the | |
amazing templates that the community has been creating. | |
For example, for trying out awesome live reloading, and reagent or om for building a web app, do: | |
lein new figwheel my-awesome-project -- --reagent | |
# or | |
lein new figwheel my-awesome-project -- --om | |
Then read the *README* to see how to start the project. | |
## Doubts and community | |
If you have doubts and you can't find an answer online, try luck on | |
`#clojurescript` on irc (freenode), the #clojurescript tag on stackoverflow, | |
or the | |
[ClojureScript mailing | |
list](https://groups.google.com/forum/#!forum/clojurescript). The community | |
is very helpful and awesome. | |
I wish you the best in your future endeavors! Please contact with us on [the | |
issues tracker](https://github.com/joakin/cljs-browser-repl/issues) and tell | |
us anything good or bad, I'd love to hear from you! | |
"} | |
] | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Calling functions | |
To call a function, you just wrap the function name in parenthesis, instead | |
of putting the parenthesis after the function call as we do in JS."} | |
{:type :input :value "(inc 5) ; Let's increment 5\n; In JS we would do inc(5)"} | |
{:type :input :value "(inc (inc 5))"} | |
{:type :stop} | |
{:type :markdown :value | |
"How do would we know what that function does? | |
In cljs there's an easy way. You can use the special repl form `doc` and call | |
it with the name of a function, and it will give you the functions | |
documentation. | |
"} | |
{:type :input :value "(doc +)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Now you know by looking at the docs how adding numbers works, as you can see | |
it tells you that you can call it with 0, 1, 2 or more arguments, and | |
a description of what the function does. | |
Let's try it out!"} | |
{:type :input :value "(+)"} | |
{:type :input :value "(+ 1)"} | |
{:type :input :value "(+ 1 3)"} | |
{:type :input :value "(+ 4 5 6 7)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Feel free to play with other math functions like `-`, `*` and `/`. | |
Since we are feeling adventurous, lets also try with `str`, `even?` and | |
`odd?`, and `inc` and `dec` for example 👊. | |
Can you guess what `number?` does? | |
When you're ready, click *Next* to continue 😛."} | |
{:type :stop} | |
{:type :markdown :value | |
"Great! We've learnt about how to invoke functions, and looking up what | |
a function does. This is getting interesting! | |
Here's a quick reference of these basic functions: [ClojureDocs Quick | |
Ref](https://clojuredocs.org/quickref). In general, | |
[ClojureDocs](https://clojuredocs.org) is a great resource for looking up | |
documentation and examples of usage of such functions, like | |
[even?](https://clojuredocs.org/clojure.core/even_q) for example. | |
Let's move on to **[Lesson 3: Truth and lies](#/file/3)**"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Truth and lies | |
We've seen CLJS's `true` and `false` are the same as JS's booleans. | |
To compare in CLJS we can use the functions `=`, `not` and `not=`."} | |
{:type :input :value "(= 1 1 1)"} | |
{:type :input :value "(= (not false) true)"} | |
{:type :input :value "(not= 5 6)"} | |
{:type :input :value "(not= :blue :red)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Equality comparisons are strict by default in CLJS (It's like using JS's | |
`===` all the time). There's no type coercion insanity. Keep that in mind!"} | |
{:type :input :value "(= \"2\" 2)"} | |
{:type :input :value "(= false nil)"} | |
{:type :stop} | |
{:type :markdown :value | |
"For number comparisons we have `<`, `>`, `<=`, `>=` and the previous functions."} | |
{:type :input :value "(< 2 3)"} | |
{:type :input :value "(< 4 2)"} | |
{:type :input :value "(< 1 2 3 4)"} | |
{:type :input :value "(< 1 2 4 3)"} | |
{:type :stop} | |
{:type :markdown :value | |
"And for boolean logic we have `and` and `or` of course."} | |
{:type :input :value "(and true false)"} | |
{:type :input :value "(or false nil true)"} | |
{:type :stop} | |
{:type :markdown :value | |
"# What is truthy? | |
It's important to note that in CLJS **the only** falsey values are `false` | |
and `nil`. | |
`0`, `\"\"`, and all these other JS falsy values will behave as `true` in | |
CLJS, so there's no need to safeguard against unexpected zeroes or empty | |
strings as in JS."} | |
{:type :input :value "(and \"\" true)"} | |
{:type :input :value "(and 0 true)"} | |
{:type :input :value "(and nil true)"} | |
{:type :stop} | |
{:type :markdown :value | |
"`and` and `or` are shortcircuiting and behave exactly as in JS, so you can | |
use them as in JS, but taking into account the *truthyness* of values is | |
different."} | |
{:type :input :value "(and true 1 \"last truthy\")"} | |
{:type :input :value "(or false nil :first-truthy \"last truthy\")"} | |
{:type :stop} | |
{:type :markdown :value | |
"# Fill the blanks | |
Try to fix the expressions so that they evaluate to true by filling the `__` | |
gaps! | |
Click on the unfinished exercise code to load it into the repl input and then | |
fix it. | |
You can also select a part of the code to auto fill the repl input and test | |
parts of the code separately. | |
When you are done, go back and click next to finish the lesson."} | |
{:type :input :sample? true :value "(= true (__ (even? 5) (odd? 3)))"} | |
{:type :input :sample? true :value "(= true (__ (and 0 nil 1) nil (or 0 nil \"\")))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Now that we know about handling boolean logic, let's move to [Lesson 4: | |
Conditionals](#/file/4) 👏."} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Conditionals | |
It's about time that we start learning how to structure our code. | |
The most basic construct in any programming language are conditionals. | |
In CLJS we have a few branching constructs that behave as expressions and that will allow us to express our logic."} | |
{:type :stop} | |
{:type :markdown :value | |
"## if | |
The most basic and useful construct is the `if`. You call it the same way you | |
do with a function, by wrapping it in parenthesis, followed by the condition, | |
the true body, and the false *else* body. | |
(if condition true-branch false-branch) | |
The result of the `if` expression will be the result of the evaluated branch, | |
for example:"} | |
{:type :input :value "(if true 1 2)"} | |
{:type :input :value | |
"(if (= :red :blue) | |
(+ 1 2) | |
(* 5 2))"} | |
{:type :stop} | |
{:type :markdown :value | |
"As stated before, most constructs in CLJS are expressions, so you can use | |
them inside other constructs. Think about the CLJS `if` as the ternary | |
operator in JS (`true ? 1 : 2`)."} | |
{:type :input :value "(+ 2 (if false 2 10))"} | |
{:type :stop} | |
{:type :markdown :value | |
"There are a few other if short hand forms, like `if-not`, that help a bit | |
with some syntax sugar."} | |
{:type :input :value "(if-not (= 1 2) 5)"} | |
{:type :stop} | |
{:type :markdown :value | |
"As before, fill the gaps and get the expression to evaluate correctly!"} | |
{:type :input :silent? true :value "(def x 5)"} | |
{:type :input :silent? true :value "(def y 2)"} | |
{:type :input :value "x"} | |
{:type :input :value "y"} | |
{:type :input :sample? true :value | |
"(__ (= (* x 2) (* y 5)) | |
\"Nope!\" | |
\"Correct!\")"} | |
{:type :stop} | |
{:type :markdown :value | |
"## when | |
When you are only interested on the true branch of the condition, you can use | |
`when`, which only takes a true branch. `when` will return the value of the | |
last evaluated expression (you can pass multiple expressions)."} | |
{:type :input :value | |
"(when (< 1 2) | |
(+ 1 2 3) ; This will be executed but not returned | |
(- 3 1) ; This will be executed but not returned | |
(* 6 6))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Enough braching for now. | |
If you want to explore more advanced forms of branching, have a look at: | |
- [case](https://clojuredocs.org/clojure.core/case): like JS switch | |
- [cond](https://clojuredocs.org/clojure.core/cond) | |
- [condp](https://clojuredocs.org/clojure.core/condp) | |
Let's continue to the useful stuff, [Lesson 5: How do I define variables and | |
functions?](#/file/5)"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Giving names to values and functions | |
Let's have a look to see how to define variables. | |
## Local variables with `let` | |
Use `let` to create local variables that will only exist inside the let body. | |
(let [name1 value1 name1 value2 ...] | |
things-to-do | |
things-to-do | |
...) | |
"} | |
{:type :input :value | |
"(let [n 5] | |
(inc n))"} | |
{:type :input :value "n"} | |
{:type :markdown :value | |
"As you can see, `n` is not defined any more, it was only defined inside the | |
`let` form."} | |
{:type :stop} | |
{:type :markdown :value | |
"Try using `let` and fix the blanks:"} | |
{:type :input :sample? true :value | |
"(_________ | |
(if (and (< x y) | |
(= (/ y x) 3)) | |
\"Correct!\"))"} | |
{:type :stop} | |
{:type :input :sample? true :value | |
"(let [__ __ | |
__ __] | |
(when (= (str a \" \" b) \"ClojureScript rocks\") | |
\"Correct!\"))"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Top level variables | |
To define a variable in the current namespace/module, we use the `def` form."} | |
{:type :input :value "(def age 29)"} | |
{:type :markdown :value | |
"Then we can use it globally on the current module:"} | |
{:type :input :value "(* age 100)"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Functions | |
We can define functions 3 different ways (top-level, anonymous, and short-anonymous). | |
### Anonymous functions | |
To define a function we use `fn` like this: | |
(fn [params] | |
body) | |
"} | |
{:type :input :value "(fn [n] (+ 1 n))"} | |
{:type :stop} | |
{:type :markdown :value | |
"We can call it by surrounding it with parenthesis as usual:"} | |
{:type :input :value "((fn [n] (+ 1 n)) 5)"} | |
{:type :markdown :value | |
"Or give it a name:"} | |
{:type :input :value | |
"(let [my-AWESOME!-inc (fn [n] (+ 1 n))] | |
(my-AWESOME!-inc 8))"} | |
{:type :stop} | |
{:type :markdown :value | |
"We can also define anonymous functions in a super short form:"} | |
{:type :input :value "(let [short-inc #(+ 1 %)] (short-inc 2))"} | |
{:type :markdown :value | |
"The arguments are `%` `%2` `%3`. Use this only for super short anon functions."} | |
{:type :stop} | |
{:type :markdown :value | |
"### Module functions | |
The previous way of defining functions is fine for quick things but for we | |
usually want to define functions on the top level, as we did with `age` | |
before. | |
To define a function in the current module we use `defn`. | |
(defn function-name \"documentation\" [args] | |
body) | |
"} | |
{:type :input :value | |
"(defn square | |
\"Takes a number and returns the mathematical square of it.\" | |
[n] (* n n))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Now we can use it:"} | |
{:type :input :value "(square 25)"} | |
{:type :markdown :value | |
"Or check it's docs:"} | |
{:type :input :value "(doc square)"} | |
{:type :markdown :value | |
"Documentation is optional, so omit the string if you don't want to write it."} | |
{:type :stop} | |
{:type :markdown :value | |
"Functions are fun. | |
Define a function that receives a nice message, and turns it into an evil | |
one. | |
Then run the sample test cases and see if it works."} | |
{:type :input :sample? true :value | |
"(and (= \"Hi guys! MUAHAHAHAH!!!!\" (str->evil-str \"Hi guys!\")) | |
(= \"I love dogs. MUAHAHAHAH!!!!\" (str->evil-str \"I love dogs.\")))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Define a function that receives your age, and gives you the number of | |
decades you've lived. | |
*Hint: check the `quot` function* | |
Then run the sample test cases and see if it works."} | |
{:type :input :sample? true :value | |
"(and (= 2 (decades-lived 26)) | |
(= 5 (decades-lived 53)) | |
(= 7 (decades-lived 70)))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Define a function that will return true/false when the number argument | |
is a multiple of 2 and 3. (Hint: mod)"} | |
{:type :input :sample? true :value | |
"(and | |
(= true (mult-2-3? 6)) | |
(= false (mult-2-3? 50)) | |
(= false (mult-2-3? 13)) | |
(= true (mult-2-3? 24)))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Okay! 👍 We're almost pro's already. | |
A bit tired of strings and numbers, right? Let's dive into more interesting | |
data structures in [Lesson 6: Maps and | |
vectors](#/file/6)"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Maps and vectors | |
Just as JS has arrays and maps, CLJS provides us also with an immutable | |
version of these data structures. | |
## Vectors | |
This is a vector `[1 2 3]`. Vectors are can contain any type of data inside. | |
"} | |
{:type :input :value "(def mixed-vector [1 true :nope nil])"} | |
{:type :stop} | |
{:type :markdown :value | |
"There's **a lot** of functions on the CLJS standard library for dealing with | |
data structures. | |
Have a look at the usual suspects: `map`, `filter`, and `reduce` *(there are a lot more)*."} | |
{:type :stop} | |
{:type :markdown :value | |
"Convert the people on the `people` var into distinguished people"} | |
{:type :input :value | |
"(def people [\"John\" \"Mary\" \"Lucas\"])"} | |
{:type :input :sample? true :value | |
"(= __ | |
[\"Dr. John\" \"Dr. Mary\" \"Dr. Lucas\"])"} | |
{:type :stop} | |
{:type :markdown :value | |
"Get me the average age of the older than 50."} | |
{:type :input :value | |
"(def ages [55 20 75])"} | |
{:type :input :sample? true :value "(= __ 65)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Awesome! If you want have a look at `count`, `first`, `rest` and `nth`. They are very useful functions."} | |
{:type :input :sample? true :value "(count ages)"} | |
{:type :input :sample? true :value "(range 10)"} | |
{:type :input :sample? true :value "(repeat 3 \"Hi!\")"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Maps | |
The other main data structure are maps. What would we do without them? | |
Use `{}` for creating them, followed by `{key value key2 value2}`. No need to | |
use commas. Both the keys and the values can be anything."} | |
{:type :input :value "(def me {:name \"Joaquin\" :age 29})"} | |
{:type :stop} | |
{:type :markdown :value | |
"To get an item of the map we use `get`."} | |
{:type :input :value "(get me :name)"} | |
{:type :markdown :value | |
"We usually use keywords as the keys, because we can use them directly to get values from the map like this:"} | |
{:type :input :value "(:name me)"} | |
{:type :stop} | |
{:type :markdown :value | |
"There is also get-in for nested keys."} | |
{:type :input :value | |
"(def anon {:name \"Anonymous\" | |
:prefs {:food :shushi}})"} | |
{:type :input :value "(get-in anon [:prefs :food])"} | |
{:type :stop} | |
{:type :markdown :value | |
"Let's do some exercises. Given this data structure with people:"} | |
{:type :input :value | |
"(def users [{:name \"Juan\" :age 31 :city \"Alicante\"} | |
{:name \"Maria\" :age 28 :city \"Valencia\"} | |
{:name \"Pablo\" :age 66 :city \"Barcelona\"} | |
{:name \"German\" :age 51 :city \"Madrid\"} | |
{:name \"David\" :age 43 :city \"Madrid\"} | |
{:name \"Alba\" :age 25 :city \"Albacete\"}])"} | |
{:type :markdown :value | |
"Get the average age of all users."} | |
{:type :input :silent? true :value | |
"(defn solution-average-age | |
\"Gets the average age of a collection of x with :age\" | |
[xs] | |
(let [len (count xs) | |
sum (reduce + (map :age xs))] | |
(/ sum len)))"} | |
{:type :input :sample? true :value | |
"(= (average-age users) (solution-average-age users))"} | |
{:type :stop} | |
{:type :markdown :value | |
"Which cities are the +40 years old people from? (*Hint: distinct*)"} | |
{:type :input :sample? true :value | |
"(= (cities-of-elders users) [\"Barcelona\" \"Madrid\"])"} | |
{:type :stop} | |
{:type :markdown :value | |
"List of alphabetically ordered names (*Hint: sort*)"} | |
{:type :input :silent? true :value "(def ordered-users ((comp sort (partial map :name)) users))"} | |
{:type :input :sample? true :value "(= (names users) ordered-users)"} | |
{:type :stop} | |
{:type :markdown :value | |
"Awesome! 💪 | |
Have a look at these functions on maps: `keys`, `vals`. | |
For the whole reference of the libraries on the CLJS core, have a look at the | |
[ClojureDocs reference](https://clojuredocs.org/clojure.core). | |
It's also about time to get the link to the [CLJS | |
cheatsheet](http://cljs.info/cheatsheet/), which is very useful. Related to | |
this lessons see the *Collections* and *Sequences* sections, feel free to | |
experiment more in the repl. | |
When you're ready, move on to [Lesson 7: Sequences](#/file/7). | |
"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Sequences | |
You may have noticed that `map`, `filter`, etc. return a collection that is shown with parenthesis instead of the brackets that you passed in with the vector: | |
"} | |
{:type :input :value "(map inc [1 2 3])"} | |
{:type :stop} | |
{:type :markdown :value | |
"That is because most functions that operate on a sequence return a list. | |
If we want to map and return a vector, we can use `mapv`."} | |
{:type :input :value "(mapv inc [1 2 3])"} | |
{:type :markdown :value | |
"Or use `vec` to transform it back to a vector."} | |
{:type :input :value "(vec (map inc [1 2 3]))"} | |
{:type :stop} | |
{:type :markdown :value | |
"## List | |
These lists with parenthesis are a `list`, which differ from vectors: | |
- **Vectors**: Efficient random access. Efficient append and count. | |
- **Lists**: Efficient prepend. | |
"} | |
{:type :input :value "(= (list 1 2 3) '(1 2 3))"} | |
{:type :stop} | |
{:type :markdown :value | |
"## The `seq` abstraction | |
All CLJS data structures operate under a sequence abstraction, which makes | |
all functions work on all data structures: | |
"} | |
{:type :input :value "(map inc [1 2 3])"} | |
{:type :input :value "(map inc '(1 2 3))"} | |
{:type :input :value "(map (fn [[k v]] [k (inc v)]) {:a 1 :b 2 :c 3})"} | |
{:type :stop} | |
{:type :markdown :value | |
"There are a lot of interesting functions to operate on sequences, that's why | |
the core library is so interesting and versatile. | |
Here are some interesting examples, feel free to play with them and look | |
their docs up:"} | |
{:type :input :value "(partition 2 (range 10))"} | |
{:type :input :value "(interpose \",\" (repeat 5 \"Joaquin\"))"} | |
{:type :input :value "(interleave (repeat 5 :hi) (range 5))"} | |
{:type :stop} | |
{:type :markdown :value | |
"# Infinite sequences | |
There are some functions that will return infinite sequences. We can then use | |
`take` to lazily get values out of them: | |
"} | |
{:type :input :value "(take 5 (repeat \"HI!\"))"} | |
{:type :input :value "(take 10 (iterate inc 1))"} | |
{:type :input :value "(take 10 (iterate #(str % \"0\") \"\"))"} | |
{:type :input :value "(take 5 (cycle [:blue :red]))"} | |
{:type :stop} | |
{:type :markdown :value | |
"# List comprehensions | |
We can do list comprehensions as we do in es2015, by using `for`. | |
Let's create a list of points of a 4x4 board:"} | |
{:type :input :value | |
"(for [x (range 4) y (range 4)] | |
[x y])"} | |
{:type :stop} | |
{:type :markdown :value "Just with the diagonal points:"} | |
{:type :input :value | |
"(for [x (range 10) y (range 10) :when (= x y)] | |
[x y])"} | |
{:type :markdown :value "We can define variables in scope of the loop:"} | |
{:type :input :value | |
"(for [x (range 10) y (range 10) :when (= x y) :let [prod (* x y)]] | |
[x y prod])"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Playing with TODOs | |
Given some todos:"} | |
{:type :input :value | |
"(def todos [\"Buy milk\" | |
\"Read newspaper\" | |
\"Pick up olives\" | |
\"1h jog\"])"} | |
{:type :markdown :value | |
"Let's make a function to display todo items, `todos->str`. | |
Produce numbered and line separated text of the todos. Ex | |
1. asdf | |
2. fdsa | |
"} | |
{:type :stop} | |
{:type :markdown :value | |
"Well done! 👻. Let's move on to [Lesson 8: Exercising our | |
skills!](#/file/8) and play some more!"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Let's make some exercises! | |
## FizzBuzz! | |
O ye so typical, but so fun 👾! | |
Make a function that gives us fizzbuzz from 1 to n (argument to the fn). | |
fizzbuzz: multiples of 2 say Fizz | |
multiples of 3 say Buzz | |
multiples of 2 & 3 say FizzBuzz | |
Hint: `mod`, `cond`, `range` | |
"} | |
{:type :input :silent? true :value | |
"(defn fizzbuzz-sol [n] | |
(let [mult? (fn [n d] (= (mod n d) 0)) | |
num->fizzbuzz | |
#(cond | |
(and (mult? % 2) | |
(mult? % 3)) \"FizzBuzz\" | |
(mult? % 2) \"Fizz\" | |
(mult? % 3) \"Buzz\" | |
:else %)] | |
(map num->fizzbuzz (range 1 (inc n)))))"} | |
{:type :input :silent? true :value "(def fizzbuzz-25 (fizzbuzz-sol 25))"} | |
{:type :input :sample? true :value "(= (fizzbuzz 25) fizzbuzz-25)"} | |
{:type :stop} | |
{:type :markdown :value | |
"# Modifying data! 😱 | |
Up until now we haven't modified any data. How are we going to achieve | |
anything if we can't change our data? | |
For adding things to vectors or lists, we use `conj`. | |
"} | |
{:type :input :silent? true :value | |
"(def todos [\"Buy milk\" | |
\"Read newspaper\" | |
\"Pick up olives\" | |
\"1h jog\"])"} | |
{:type :input :value "(conj todos \"Learn ClojureScript\")"} | |
{:type :input :value "(conj (range 5) \"To the front?\")"} | |
{:type :markdown :value | |
"Each data-structure gets the data to where its optimized for (vectors at the | |
end, lists at the front)."} | |
{:type :stop} | |
{:type :markdown :value | |
"For maps, we use `assoc` or `assoc-in`."} | |
{:type :input :value "(def me {:name \"Joaquin\" :age 29})"} | |
{:type :input :value "(assoc me :name \"John\")"} | |
{:type :stop} | |
{:type :markdown :value | |
"# Immutable data FTW! | |
Data is immutable in CLJS, so even though we've added things to our vector | |
and map, they still remain the same. The modification was a new data | |
structure."} | |
{:type :input :value "(def not-me (assoc me :name \"John\"))"} | |
{:type :input :value "me"} | |
{:type :input :value "not-me"} | |
{:type :stop} | |
{:type :markdown :value | |
"Working with immutable data is a bit challenging coming from a mutable world | |
like JS, but once you get used to it it's very difficult to go back. | |
It gives you guarantees that once something is defined, no functions will | |
modify it without you knowing about it. | |
It also means that we're working with values, and we don't have to think | |
about references and objects."} | |
{:type :input :value "(= [1 2 3] [1 2 3])"} | |
{:type :markdown :value | |
"In CLJS we think about values, and those two vectors are the same, so the | |
equality comparison is true. | |
As you know, that's not true in JS:"} | |
{:type :input :value "(= #js[1 2 3] #js[1 2 3])"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Sets | |
There's another very useful data-structure called a `set`. It's a collection | |
of unique elements. We define it using `#{ ... }`."} | |
{:type :input :value "(def allowed-colors #{:red :blue :yellow})"} | |
{:type :markdown :value "Can't hold duplicate elements"} | |
{:type :input :value "(conj allowed-colors :yellow)"} | |
{:type :stop} | |
{:type :markdown :value | |
"And it's very easy to check for existence of an item on the set:"} | |
{:type :input :value "(if (allowed-colors :red) \"Red allowed!\")"} | |
{:type :input :value | |
"(if (allowed-colors :purple) | |
\"Purple allowed!\" | |
\"Nope\")"} | |
{:type :stop} | |
{:type :markdown :value | |
"## Parsing keys to game commands | |
Make a function that when passed in a key (:up, :down, ...) gives us a game | |
command (:move-up, :move-down, etc.) Here's the list of command <-> keys: | |
:move-left -> :left :a | |
:move-right -> :right :d | |
:shoot -> :space :s :enter | |
:turbo -> :t :w | |
:menu -> :esc | |
"} | |
{:type :input :sample? true :value | |
"(and (= (key->command :d) :move-right) | |
(= (key->command :left) :move-left) | |
(= (key->command :enter) :shoot) | |
(= (key->command :w) :turbo))"} | |
{:type :markdown :value | |
"Great! Let's move to the next [Lesson 9: Talking to | |
JS](#/file/9)"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown :value | |
"# Talking with JS | |
We've mentioned that CLJS is good an accessing the JS environment, and that's | |
true. It's about time to learn how to interact with JS. | |
In JS, there is only a global namespace. As you may have seen when defining | |
variables and functions, CLJS returns the name of the namespace/module and | |
the name of the var: | |
"} | |
{:type :input :value "(def counter 0)"} | |
{:type :markdown :value | |
"In the same way that you could access this variable with | |
`cljs.user/counter`, you can access the JS namespace by using the prefix | |
`js/`. | |
Try evaluating the following expressions, and check the results:"} | |
{:type :input :sample? true :value "(js/alert \"Hi clojurescripters!\")"} | |
{:type :input :sample? true :value "js/location"} | |
{:type :stop} | |
{:type :markdown :value | |
"Now that we know how to access things on the global scope, let's learn about | |
accessing and manipulating the host objects. | |
To make a `new Object` we access the constructor and invoke it by appending | |
a dot:"} | |
{:type :input :value "(def now (js/Date.))"} | |
{:type :stop} | |
{:type :markdown :value | |
"To access properties on the object, we use `(.-prop object)` or | |
`(aget object \"prop\")`."} | |
{:type :input :value "(let [me #js {:name \"Joaquin\"}] (.-name me))"} | |
{:type :stop} | |
{:type :markdown :value | |
"To call a method on the object, we use `(.method object args)`."} | |
{:type :input :value "(.now js/Date)"} | |
{:type :markdown :value | |
"That's equivalent to `Date.now()`"} | |
{:type :stop} | |
{:type :markdown :value | |
"We can use the `#js` tag before a data-structure to have it converted to JS at compile time. | |
We can also convert between JS and CLJS with `clj->js` and `js->clj`."} | |
{:type :stop} | |
{:type :markdown :value | |
"To set a property on a JS object we can use `set!`:"} | |
{:type :input :value "(def js-map #js {:name \"Joaquin\"})"} | |
{:type :input :value "js-map"} | |
{:type :input :value "(set! (.-name js-map) \"Joan\")"} | |
{:type :input :value "js-map"} | |
{:type :stop} | |
{:type :markdown :value | |
"There are a few more shortcuts for interacting with JS objects, like the `.` | |
and `..` operator. Look them up to learn about how to use them. | |
Finally, let's move into the next lesson, [Lesson 10: Mutability in CLJS](#/file/10)"} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{:type :markdown | |
:value | |
"# Welcome to Diving into Clojurescript | |
Let's learn Clojurescript interactively in the browser! | |
"} | |
{:type :stop} | |
{:type :markdown | |
:value | |
"## What is Clojurescript? | |
It is language that runs in all JS environments (browser, node.js, etc). | |
As a language, is a hosted language with a focus on **functional | |
programming**, remaining very **practical**, **immutability** and great | |
**interoperability** with it's host language (in this case, the JS | |
environment). | |
"} | |
{:type :stop} | |
{:type :markdown | |
:value | |
"## So why is Clojurescript great? | |
* Consistently designed and well thought out language. | |
* Immutability is the default. | |
* Programming with values. | |
But it is a highly practical language: | |
* Can use mutability explicitly when needed. | |
* Easy interop with the ecosystem libraries of JavaScript. | |
"} | |
{:type :stop} | |
{:type :markdown | |
:value | |
"But enough talking. Let's get to learn! We'll appreciate the benefits and | |
elegance of the language when we've learnt more about it 😃 | |
--- | |
For getting started visit **[Lesson 1: Starting with the very | |
basics](#/file/1)** | |
"} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment