forked from clj-syd/curriculum
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
643 additions
and
105 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,166 @@ | ||
Making Your First Program | ||
========================= | ||
|
||
* What is the difference between source code and the REPL? | ||
* Structure of a Clojure project | ||
* Namespaces | ||
* TODO | ||
Now that you know a bit about how to write Clojure code, let's look at how to create a standalone application. | ||
|
||
Design first program. Make sure it contains data structures and explain them. | ||
In order to do that, you'll first create a *project*. You'll learn how to organize your project with *namespaces*. You'll also learn how to specify your project's *dependencies*. Finally, you'll learn how to *build* your project to create the standalone application. | ||
|
||
Make sure it uses at least one dependency and show how to add that and use it. | ||
## Create a Project | ||
|
||
Up until now you've been experimenting in a REPL. Unfortunately, all the work you do in a REPL is lost when you close the REPL. You can think of a project as a permanent home for your code. You'll be using a tool called "Leiningen" to help you create and manage your project. To create a new project, run this command: | ||
|
||
```clojure | ||
lein new app world-bank | ||
``` | ||
|
||
This should create a directory structure that looks like this: | ||
|
||
``` | ||
| .gitignore | ||
| doc | ||
| | intro.md | ||
| project.clj | ||
| resources | ||
| README.md | ||
| src | ||
| | world_bank | ||
| | | core.clj | ||
| test | ||
| | world_bank | ||
| | | core_test.clj | ||
``` | ||
|
||
There's nothing inherently special or Clojure-y about this project skeleton. It's just a convention used by Leiningen. You'll be using Leiningen to build and run Clojure apps, and Leiningen expects your app to be laid out this way. Here's the function of each part of the skeleton: | ||
|
||
- `project.clj` is a configuration file for Leiningen. It helps | ||
Leiningen answer questions like, "What dependencies does this | ||
project have?" and "When this Clojure program runs, what function | ||
should get executed first?" | ||
- `src/world_bank/core.clj` is where we'll be doing our | ||
Clojure coding for awhile. In general, your source code will fall | ||
under `src/{world_bank}` | ||
- The `test` directory obviously contains tests, which we won't be covering. | ||
- `resources` is a place for you to store assets like images; we won't | ||
be using it for awhile. | ||
|
||
Now let's go ahead and actually run this project. Enter this at the command line: | ||
|
||
``` | ||
cd world_bank | ||
lein run | ||
``` | ||
|
||
You should see this: | ||
|
||
``` | ||
Hello, world! | ||
``` | ||
|
||
## Modify Project | ||
|
||
Pretty cool! But also pretty useless. To change the behavior of this project, open up `src/world_bank/core.clj` and monkey around with the `-main` function. Try changing it so that it reads: | ||
|
||
```clojure | ||
(defn -main | ||
[& args] | ||
(println "Hello, ClojureBridge!")) | ||
``` | ||
|
||
If you run `lein run` again, you should see `Hello, ClojureBridge!` printed. | ||
|
||
The `-main` function is the *entry point* to your program. Other than that there's nothing special about it. It acts just like any other Clojure function. It just happens to be the function which gets called first when you run your program. For example, you can write your own functions have `-main` call them: | ||
|
||
```clojure | ||
(defn quotify | ||
[quote author] | ||
(str quote "\n\n-- " author)) | ||
(defn -main | ||
[& args] | ||
(println (quotify "A man who carries a cat by the tail learns something he can learn in no other way." | ||
"Mark Twain"))) | ||
``` | ||
|
||
This should output: | ||
|
||
``` | ||
A man who carries a cat by the tail learns something he can learn in no other way. | ||
-- Mark Twain | ||
``` | ||
|
||
So, you can write programs of arbitrary complexity. Just make sure to use `-main` to kick them off. | ||
|
||
## Organization | ||
|
||
As your programs get more complex, you'll need to organize them. You organize your Clojure code by placing related functions and data in separate files. Clojure expects each file to correspond to a *namespace*, so you must *declare* a namespace at the top of each file. | ||
|
||
Until now, you haven't really had to care about namespaces. Namespaces allow you to define new functions and data structures without worrying about whether the name you'd like is already taken. For example, you could create a function named `println` within the custom namespace `my-special-namespace`, and it would not interfere with Clojure's built-in `println` function. You can use the *fully-qualified name* `my-special-namespace/println` to distinguish your function from the built-in `println`. | ||
|
||
Let's create a new namespace for making world bank API calls. First, create the file `src/world_bank/api.clj`. Then write this in it: | ||
|
||
```clojure | ||
(ns world-bank.api) | ||
``` | ||
|
||
This line establishes that everything you define in this file will be stored within the `world-bank.api` namespace. For example, add this to your file: | ||
|
||
```clojure | ||
(def base-uri "http://api.worldbank.org") | ||
``` | ||
|
||
You should be able to access that from your `core.clj` file. Change that file so that it reads: | ||
|
||
```clojure | ||
(ns world-bank.core) | ||
(require '[world-bank.api :as api]) | ||
|
||
(defn -main | ||
[& args] | ||
(println api/base-uri)) | ||
``` | ||
|
||
If you run the `-main` function it should print `http://api.worldbank.org`. | ||
|
||
There are couple things going on here. First, you use `require` to tell Clojure to load another namespace. The `:as` part of `require` allows you to *alias* the namespace, letting you refer to its definitions without having to type out the entire namespace. In this case, you can use `api/base-uri` instead of `world-bank.api/base-uri`. | ||
|
||
## Dependencies | ||
|
||
The final part of working with projects is managing their *dependencies*. Dependencies are just code libraries that others have written which you can incorporate in your own project. | ||
|
||
To add a dependency, open `project.clj`. You should see a line which reads | ||
|
||
``` | ||
:dependencies [[org.clojure/clojure "1.5.1"]] | ||
``` | ||
|
||
That's where you specify your dependencies. You can add a dependency by adding another vector with the dependency's name and its version. Let's add the `clj-http` and `cheshire` projects. `clj-http` will let us make HTTP calls so that we can get data using the World Bank API. `cheshire` will let us decode the results. Update the dependencies line in your `project.clj` so that it looks like this: | ||
|
||
``` | ||
:dependencies [[org.clojure/clojure "1.5.1"] | ||
[clj-http "0.9.0"] | ||
[cheshire "5.3.1"]] | ||
``` | ||
|
||
Now you can require the namespaces defined in `clj-http` within your own project. Update `src/world_bank/api.clj` so that it looks like this: | ||
|
||
```clojure | ||
(ns world-bank.api) | ||
(require '[clj-http.client :as client]) | ||
(require '[cheshire.core :as json] | ||
|
||
(def base-uri "http://api.worldbank.org") | ||
(def query-params {:format "json" :per_page 10000}) | ||
|
||
(defn get-api | ||
"Returns map representing API response." | ||
[path qp] | ||
(let [base-path (str base-uri path) | ||
query-params (merge qp {:format "json" :per_page 10000}) | ||
response (parse-json (:body (client/get base-path {:query-params query-params}))) | ||
metadata (first response) | ||
results (second response)] | ||
{:metadata metadata | ||
:results results})) | ||
``` | ||
|
||
Ta-da! |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,150 @@ | ||
Flow Control | ||
============ | ||
|
||
* Review of booleans | ||
* cond | ||
* do | ||
* if (maybe?) | ||
* doseq (?) | ||
* dotimes (?) | ||
|
||
I'm not even sure we should touch `doseq` or `dotimes`. Recursion is completely unnecessary for this course. | ||
"Flow control" is the programming term for deciding how to react to a given circumstance. We make decisions like this all the time. *If* it's a nice day out, *then* we should visit the park; *otherwise* we should stay inside and play board games. *If* your car's tank is empty, *then* you should visit a gas station; *otherwise* you should continue to your destination. | ||
|
||
Software is also full of these decisions. *If* the user's input is valid, *then* we should save her data; *otherwise* we show an error message. The common pattern here is that you test some condition and react differently based on whether the condition is *true* or *false*. | ||
|
||
## if | ||
|
||
In Clojure, the most basic tool we have for this process is the `if` operator. Here's how you might code the data validation scenario: | ||
|
||
```clojure | ||
(if (valid? data) | ||
(save! data) | ||
(error "Your data was invalid")) | ||
``` | ||
|
||
The general form of the `if` operator is: | ||
|
||
```clojure | ||
(if conditional-expression | ||
expression-to-evaluate-when-true | ||
expression-to-evaluate-when-false) | ||
``` | ||
|
||
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: | ||
|
||
```clojure | ||
(if (> 3 1) | ||
"3 is greater than 1" | ||
"3 is not greater than 1") | ||
;=> "3 is greater than 1" | ||
|
||
(if (> 1 3) | ||
"1 is greater than 3" | ||
"1 is not greater than 3") | ||
;=> "1 is not greater than 3" | ||
|
||
(if "anything other than nil or false is considered true" | ||
"A string is considered true" | ||
"A string is not considered true") | ||
;=> "A string is considered true" | ||
|
||
(if nil | ||
"nil is considered true" | ||
"nil is not 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") | ||
;=> "expressions which evaluate to nil are not considered true" | ||
``` | ||
|
||
### EXERCISE: Even more name formatting | ||
|
||
Write a function `format-name` that takes a map representing a user, with keys `:first`, `:last`, and possibly `:middle`. It should return their name as a string, like so: | ||
|
||
```clj | ||
(format-name {:first "Margaret" :last "Atwood"}) | ||
;=> "Margaret Atwood" | ||
|
||
(format-name {:first "Ursula" :last "Le Guin" :middle "K."}) | ||
;=> "Ursula K. Le Guin" | ||
``` | ||
|
||
### BONUS: Flexible name formatting | ||
|
||
Change `format-name` to take a second argument, `order`. If `order` equals `:last`, then the format should be "Last, First Middle"; otherwise, it should be "First Middle Last." | ||
|
||
|
||
## Boolean logic with and, or, and not | ||
|
||
`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`. | ||
|
||
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. | ||
|
||
Look at this truth table: | ||
|
||
| x | y | (and x y) | (or x y) | (not x) | (not y) | | ||
| ----- | ----- | --------- | -------- | ------- | ------- | | ||
| false | false | false | false | true | true | | ||
| true | false | false | true | false | true | | ||
| true | true | true | true | false | false | | ||
| false | true | false | true | true | false | | ||
|
||
`and`, `or`, and `not` work like other functions (they aren't exactly functions, but work like them), so they are in _prefix notation_, like we've seen with arithmetic. | ||
|
||
`and`, `or`, and `not` can be combined. This can be hard to read. Here's an example: | ||
|
||
```clj | ||
(defn leap-year? | ||
"Every four years, except years divisible by 100, but yes for years divisible by 400." | ||
[year] | ||
(and (zero? (mod year 4)) | ||
(or (zero? (mod year 400)) | ||
(not (zero? (mod year 100)))))) | ||
``` | ||
|
||
|
||
## cond | ||
|
||
Sometimes you might want to do multiple conditional checks. For example, you might want to check whether a number is within a certain range. Here's the general form of `cond`: | ||
|
||
```clojure | ||
(cond | ||
test-condition-1 expression-to-evaluate-when-test-condition-1-is-true | ||
test-condition-2 expression-to-evaluate-when-test-condition-2-is-true | ||
test-condition-3 expression-to-evaluate-when-test-condition-3-is-true | ||
:else expression-to-evaluate-when-no-test-conditions-are-true) | ||
``` | ||
|
||
Here's how you could use it to check that a number is within a range: | ||
|
||
```clojure | ||
(let [breaths-taken-today 100] | ||
(cond | ||
(and (> breaths-taken-today 0) (< breaths-taken-today 50)) | ||
"That's a good start! You probably want to breathe more, though." | ||
|
||
(and (> breaths-taken-today 51) (< breaths-taken-today 100)) | ||
"Wow, you're breathing like a pro!" | ||
|
||
:else | ||
"Hold on there buddy, that's a lot of breathing. Might want to hold off on that a bit.")) | ||
``` | ||
|
||
Clojure has other conditional operators, but they're just there to make your code slightly more concise. `if`, `do`, and `cond` will allow you to express the behavior you want for every condition! | ||
|
||
### EXERCISE: Revisit leap year | ||
|
||
Rewrite the `leap-year?` function shown earlier to use `cond` instead of nested boolean logic. | ||
|
||
|
||
## do | ||
|
||
One thing you may have noticed is that you are only allowed to evaluate one expression for each branch of the `if` expression. The `do` operator allows you to "bundle up" multiple expressions so that you can "do" multiple things. Here's an example: | ||
|
||
```clojure | ||
(if (> 5 0) | ||
(do | ||
(println "5 is indeed greater than 0!") | ||
(println "I wonder if 0 has low self-esteem")) | ||
(do | ||
(println "0 is indeed greater than 5!") | ||
(println "Because numbers have been broken I guess"))) | ||
; => "5 is indeed greater than 0!" | ||
; => "I wonder if 0 has low self-esteem" | ||
``` | ||
|
Oops, something went wrong.