clojurebridge / curriculum Goto Github PK
View Code? Open in Web Editor NEWCurriculum for the ClojureBridge workshop series.
Home Page: https://clojurebridge.github.io/curriculum/
Curriculum for the ClojureBridge workshop series.
Home Page: https://clojurebridge.github.io/curriculum/
(defn leap-year?
"ever four years, except those divisible by 100, but including those
divisible by 400"
[year]
(and (zero? (mod year 4))
(not (zero? (mod year 100)))) ;;this line makes function work properly
(or (zero? (mod year 400)))
;(not (zero? (mod year 100)))) ;; this line was included here in original solution
)
(leap-year? 1900) ;false
(leap-year? 2000) ;true
Locally, everything is running just fine, but I keep getting an application error when I run heroku open
as instructed in "Putting Your Application Online"
Things I've tried already:
:min-lein-version "2.0.0"
as instructed on heroku dev center[hiccup.form :as form]
and [ring.adapter.jetty :as ring]
:main ^:skip-aot global-growth.web
(defn -main [] (ring/run-jetty #'main-routes {:port 8080 :join? false})
There was also talk of this being an issue with Procfile (there isn't one?), but I'm not sure what's going on. Any ideas? Let me know if you'd like me to copy in any of my heroku logs.
Related: Is there recommended documentation on clojure app deployment via github.io?
Thanks for all your help in advance! ๐
I needed to add
heroku keys:add %USERPROFILE%/.ssh/id_rsa.pub
to the instructions to get the student up and running. Otherwise Heroku didn't know who they were. heroku keys:add
prolly works for most situations.
Happy to add this if it's helpful. Looks like both windows installs are missing them.
Introduce Quil earlier and throughout the curriculum so it is not entirely presented at the end of the workshop.
After completing the project and following the deploy instructions for global-growth, Heroku's lein complains that it doesn't know how to lein with-profile production trampoline run
. This was fixed for me by added :min-lein-version "2.0.0"
to project.clj to force Heroku to use lein 2+ instead of 1.7. (The site worked great locally.)
Would love to hear if anyone else can replicate this. I'm using Nitrous.io so possible it's just me.
If confirmed this should likely be added to the clojurebridge lein template but could also be included in (or before) the deploy instructions. Happy to do the latter if needed.
Because we are using LightTable, I find myself never using println
in any examples. At a traditional REPL, we'd see the output of println
, but in LightTable, println
output shows in the console, which is hidden by default, and we see the results of calling a function inline.
Is this ok? Using println
seems to be a big part of my day when developing, but I've just started using LightTable full time, and it might not be that important.
I'd love to see input from @bridgethillyer, @flyingmachine, @seancorfield, and @7maples on this.
Attendees will likely get leiningen 2.3.4 for the 4/4 workshop. I think this version adds LICENSE when you do new app. Confirm that , and if it does, add that to the described directory structure.
I ran through these instructions on my new MacBook, and git push heroku master
failed until I added my new SSH public key to my Heroku account.
If it's possible to push to Heroku via HTTPS or there's some other way to avoid SSH key setup, that would be ideal.
Otherwise, we probably need to add some additional instructions to create and SSH key and add to Heroku. On the upside, the incremental effort to create and configure a GitHub account will be less.
Images in RevealJS presentation need /curriculum/
in its URL. For example, /img/instarepl.png
doesn't exists, but /curriculum/img/intarepl.png
does.
For this reason, the images don't show up on slides.
As I see it, there's four real choices: Nightcode, Catnip, Light Table, and Cursive Clojure. Here's the current knowledge about all four:
Simple Clojure IDE written in Clojure. Reasonably user-friendly.
lein
installed separately.Web-based editor, runs in the browser. Provided as a lein plugin.
Next-generation Clojure (+ Javascript and Python) editor/IDE.
Plugin for IntelliJ. A true IDE.
We're going to take out the namespace stuff, so just include the one namespace that will actually be used in the app.
CONTRIBUTING.md currently has a bullet under "Making Changes" that says
(Add more git guidance?)
That line should be replaced with information about how to use git add
, git commit
, etc. so that students, etc. could potentially submit PRs.
Create a cheatsheet with key syntax, function names, etc. that students are using in the exercises. A simple one-pager that could be printed out and used by students to help guide them during the exercises.
In data_structures.md, the explanation of maps begins with an example using keywords:
{:first "Sally", :last "Brown"}
{:a 1, :b "two"}
{}
However, in the "Introduction to Programming in Clojure" in intro.md, the definition for keywords does not prepare students for this usage:
Keywords are the strangest of the basic value types, because they don't have a real world analog like numbers, strings, and booleans do. You can think of them as a special type of string, one that's used for labels. They are easiest to understand when we cover maps later, as they are most commonly used as keys in maps.
There should be example code added to the keywords definition in intro.md, so that students will be able to recognize keywords when they appear, and the maps definition in data_structures.md should be further clarified to point out which parts of the example represent keys, and which values, and also more explicitly define what role keywords play in this context.
To demonstrate: looking at the current maps example, me-a-year-ago wouldn't be sure whether :first "Sally"
is a key, if :first
is a key, if "Sally"
is a key, or if all are valid as keys. If I wanted to retrieve the value "Brown"
, would I need to provide :first "Sally"
in its entirety? Or, should I provide :last
as the key, because the comma is slightly unclear? If I provided :first "Sally"
as a key, would :last "Brown"
be returned? How would I get rid of the :last
label in that case? What's the difference between a "key" and a "keyword," anyway?
The curriculum currently attempts to demonstrate this using examples ("Let's look at some functions we can use with maps"), but this isn't entirely clear.
Whatever was keeping Light Table from working on OS X 10.6.8 seems to have been resolved. So if you do the following, LT should be able to run:
"remov[e] key "LSMinimumSystemVersion" from Info.plist"
I am a person of metric system. In metric system world, people's hight is measured by "cm". (1m = 100cm)
Talking people's hight in meter sounds awkward to me.
Just a comment.
Currently, the "Infix vs. prefix notation" slide in Module 1 looks like this:
1 + 2 * 3 / 4 - 5
(- (+ 1 (/ (* 2 3) 4)) 5)
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9
(+ 1 2 3 4 5 6 7 8 9)
If you look at the rendered slide, though, that's pretty difficult to read.
Perhaps we should replace this with a table?
Typos on web.md
Line 8: Missing "the"
HTTP is the protocol for sending requests to ---[the]--- server and for the server to send responses to those requests.
Line 108: Should be "URLs" not "URLS"
The web browser gets to the right server with an address called a URL. Take a look at the following URL---[s]---:
Line 113: Extra "s"
If one web application---[s]--- handles all of that, it needs to track who takes care of what.
Please consider the following restructuring of this lesson. Some of the specific explanations may need revision, but I think overall it flows well and allows the student to build a working app as well as dig deeper into the functional components within the REPL for greater understanding. Please note it does assume some small changes to the current shell-project version of the global-growth app.
The World Bank provides a data collection of world development indicators, showing the current state of global development, as well as a web API to provide access to this data. A web API is a way to provide access for one program to call another program over HTTP. In this case, the World Bank Indicators API provides access to their set of data.
We will use the World Bank Indicators API to explore some of the world development indicators for different countries. We will sort and compare certain indicators. This is a task that Clojure is well suited for.
global-growth
folder, and click the Upload buttonYou should now see global-growth/
at the top left of the Light Table screen. Click this to expand the directory structure, and then click core.clj
to load the file in the main window.
This file has been pre-populated with 3 sections:
As seen in previous lessons, a namespace allows you to define new functions and data structures without worrying about whether the name you'd like is already taken.
Dependencies are just code libraries that others have written which you can incorporate in your own project. Click on project.clj
from the directory structure in Light Table to view this file. Take a look at the :dependencies
section, which includes entries like clj-http
, a library that allows you to communicate over HTTP, and cheshire
, another library that reads and understands the JSON format.
Click the core.clj
tab at the top to go back to our main file. Notice the :require
section, which is where the libraries loaded by project.clj
are included and given aliases (shorter names) to be referenced later in our program when we need functionality that they provide.
(defn remove-aggregate-countries
"Remove all countries that aren't actually countries, but are aggregates."
[countries]
(remove (fn [country]
(= (get-in country [:region :value]) "Aggregates")) countries))
(defn get-country-ids
"Get set of country ids so we can filter out aggregate values."
[]
(let [countries (remove-aggregate-countries (:results (get-api "/countries" {})))]
(set (map :iso2Code countries))))
(def country-ids (get-country-ids))
These functions serve to build up our list of countries from data returned by the API to be used by our application. Like many well-written functions in Clojure, you can often get a good idea about what they are doing even if you don't understand exactly what each part means.
This is the part of the application that controls how your app will appear in the browser, and what content will be in each control (such as a drop-down menu). It also handles basic text and number formatting and what page to load based on the URL requested.
In the next section, we will add the code necessary to download, filter, and sort the data based on the selections made by the user on the web page. Before we get to that, however, let's see what happens if we run our application in its current state.
Enter this at the command line:
cd global_growth
lein ring server
BOOM! You should see some ominous looking output similar to this:
Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: get-api in this context, compiling:(global_growth/core.clj:23:57)
at clojure.lang.Compiler.analyze(Compiler.java:6380)
at clojure.lang.Compiler.analyze(Compiler.java:6322)
...
The informative part of this error is Unable to resolve symbol: get-api in this context
on line 23, character 57. If you click on the core.clj
file in Light Table, you will see a line number and character count in the bottom right corner. Placing the cursor on line 23, character 57 (or whatever number is in your error message) will show you the problem, which is the call to the get-api
function. Since we have not yet written that function, the program cannot run. Let's do that now.
Our get-api
function will take two parameters to hold the path in the API we are calling (such as "/countries") and any additional query parameters that need to be added to the API URL. You can put all of the above pieces into the let
part of the function. You need: base-path
, query-params
, response
, metadata
, and results
. Then return a map with keys :metadata
and :results
, each of which has that corresponding value.
Add the following code to your core.clj
file right after the parse-json
line near the top of the file.
;; WORLD BANK API CALLS
(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}))
Define a var to hold the results of calling get-api
for the countries endpoint.
(def countries (get-api "/countries" {}))
Notice that when we call get-api
we are including the path
and enabling the passing in of query-params
to the API. Save your changes to core.clj
.
Let's take a moment now to look at what is going on here. In order to really understand how the get-api
function works, let's fire up the REPL and run through a few exercises.
Click the first line of the file (anywhere on the line with ns-global-growth.core
) and then press cmd-shift-enter
to load the project and any dependencies into the REPL session (you will see the activity in the lower left corner of the Light Table window while this is taking place).
Once everything is loaded up, press Ctrl-Space to bring up the search command box. Type "insta" and press enter when the "Instarepl: Open a Clojure instarepl" choice is highlighted.
You are now ready to execute functions.
As mentioned previously, we use the clj-http
library to enable making calls over the internet with HTTP. In the Instarepl tab, type in the following line:
(require '[clj-http.client :as client])
Now let's walk through the components of the get-api
function to see what each one does.
Go ahead and set the address that our application will use to reach the World Bank API by entering:
(def base-uri "http://api.worldbank.org")
Now whenever we need the address for the World Bank API, we can use base-uri
instead of the longer URL.
Each type of data available through the World Bank API has an API endpoint. The endpoint has a specific path added to the base URL. http://api.worldbank.org/countries is the endpoint that lists all of the countries with data available. Create a var to refer to that URL by combining the base-uri
and "/countries" by entering:
(def base-path (str base-uri "/countries"))
Now make a call using the get
function (courtesy of the clj-http
library) to the full API endpoint: http://api.worldbank.org/countries
, which should result in a bunch of light blue text with country names and other values appearing just to the right of what you typed within Light Table.
(client/get base-path)
There, you've done it! You have successfully used Clojure to ask the World Bank for information and received back a response with the raw data. The other lines in the get-api
function simply set more specific parameters for your query and assign values to vars for use elsewhere in the application.
This line,
query-params (merge qp {:format "json" :per_page 10000})
just means "please send me results in the JSON format, 10,000 values at a time."
The other two lines,
(def metadata (first response))
(def results (second response))
are used to assign the metadata, such as latitude and longitude for each country, and the actual indicator results, such as income level, for each country, respectively.
Click back to the core.clj
tab, and we will continue building out our application. To add the filtering functions, place the cursor after the supporting function section, right after the following line:
(def country-ids (get-country-ids))
On the next line create a function called get-value-map
where the two keys that we want to look up are parameters that can be passed to the function. Put the values into a map with "into {}"
;; FILTER THE RESULTS
(defn get-value-map
"Returns relation of two keys from API response"
[path query-params key1 key2]
(let [response (get-api path query-params)]
(into {} (for [item (:results response)]
[(key1 item) (key2 item)]))))
Now create another function called get-indicator-map
.
(defn get-indicator-map []
"Gets map of indicators.
/topics/16/indicators: All urban development"
(get-value-map "/topics/16/indicators" {} :name :id))
Define a var to hold the results of calling get-indicator-map.
(def indicator-map (get-indicator-map))
Finally, create a function called get-indicator-all
. The indicator name needs to be a parameter to the function. So should the data and two keys. We make these parameters so we will be able to get other indicators and pull out different pieces from the results.
(defn get-indicator-all
"Returns indicator for a specified year for all countries"
[indicator year key1 key2]
(get-value-map (str "/countries"
"/all"
"/indicators"
"/" indicator)
{:date (str year)}
key1
key2))
To get a better grasp on what we have just accomplished, click back to the Instarepl tab and place the cursor on a new line after everything we typed in the previous exercise.
Go ahead and add in the cheshire
library, which reads and understands the JSON format. Then we need a function to read (parse) the json we will receive back from the World Bank API. Passing the second argument as true tells the function to return Clojure keywords (i.e. :incomeLevel
) back for the map keys.
(require '[cheshire.core :as json])
(defn parse-json [str]
(json/parse-string str true))
Next, copy over the whole get-api
function from core.clj
, including the countries
definition line, and paste in the Instarepl window on the line after the parse-json
function.
We now have what we need (the ability to download and process the raw World Bank data) available in the Instarepl to allow us to walk through the components of the filtering functions.
Let's go through the raw data and pull out a couple values associated with keys in the results. You can use the for
function to do this. We want to pull out the values for the "name" and "longitude" items from the results section. Those will have been turned into Clojure keywords by the cheshire
json library, so you can refer to them as :name
and :longitude
. Add the following lines in the Instarepl:
(for [item (:results countries)]
[(:name item) (:longitude item)])
Behold! A list of countries and their longitudes should appear alongside your function. Our function has taken the raw data set (countries
) and returned only the specific information that we asked for, in this case country name and longitude. Look back at the get-value-map
in core.clj
to see how we are using similar logic to load up all of the indicator names and their values. To see this in action, copy the full get-value-map
function from core.clj
and paste it into the Instarepl.
"Hey, nothing happened!" That is because we simply added the function for use inside the Instarepl, but have not actually told it to run. Now let's put it to use. On the next line in the Instarepl, type:
(get-value-map "/topics/16/indicators" {} :name :id)
Voila! The names and id's of all the world's development indicators (all 16 of them) should appear next to your function.
You should be able to see now how the core.clj
function get-indicator-all
uses this logic to build a map of all the indicators and their values for each country. Go ahead and copy the full get-indicator-all
function to the Instarepl so we can use it in the next exercise.
After the filtering section, create a new function called sorted-indicator-map
.
;; SORT THE DATA
(defn sorted-indicator-map
"Sort the map of indicator numeric values"
[inds]
(take list-size
(sort-by val >
(into {} (for [[k v] inds
:when (and v (country-ids (:id k)))]
[(:value k) (read-string v)])))))
Save your changes to core.clj
.
To help you understand what is going on here, click back to the Instarepl tab and place the cursor on a new line after everything we typed in the previous exercise.
Enter the following line:
(def inds (get-indicator-all "EP.PMP.SGAS.CD" "2012" :country :value))
Since EP.PMP.SGAS.CD is the indicator name for gas pump price, you will see a list containing value pairs that describe each country with keys :id and :value, followed by a value with the actual pump price.
For the next step, you will need to first copy over the "Supporting Functions" to the Instarepl, which we talked about earlier in this lesson. These are remove-aggregate-countries
and get-country-ids
, as well as the line where we define country-id
. Go ahead and do that.
Now enter:
(for [[k v] inds
:when (and v (country-ids (:id k)))]
[(:value k) (read-string v)])
What this function does is take the list of countries (after the raw data has been processed by the supporting functions to remove aggregate countries and build a list of country names with matching id's), then creates a new list of gas pump price by country name. Next, load this list into a map by typing:
(into {} (for [[k v] inds
:when (and v (country-ids (:id k)))]
[(:value k) (read-string v)]))
Notice how instead of the previous ["name" price] ["name" price] ["name" price]
format, the map of countries and prices looks like ```{"name" price, "name" price, "name", price ... }.
Lastly, sort the list by price (value), and return the top 10.
(take 10
(sort-by val >
(into {} (for [[k v] inds
:when (and v (country-ids (:id k)))]
[(:value k) (read-string v)]))))
That is really all sorted-indicator-map
is doing. Make sense?
Now go back to your command line in the global_growth
directory and enter:
lein ring server
After several seconds, you should see a couple lines of INFO output followed by:
Started server on port 3000
To view the running application, open your web browser and go to http://localhost:3000
Congratulations, you have created your first Clojure web application!
This is going to be tricky. We need a plan for how to keep these up to date.
A few times at the SF workshop, the curriculum/slides/TAs guided students to cd
into a certain directory--leading to the natural question, "How do I open this file/directory in Light Table from the command line?"
We should either have the Light Table equivalent of subl
added to the installfest, or rewrite the sections that refer to cd
ing into directories in the curriculum.
In data_structures.md, the definition of the associative?
function for vectors states,
It lets us know whether we can look up values in this collection using keys. We can with a vector; each item in the vector has a key starting with zero and going up that points to it. In our vector
[:a :b :c]
, the key 0 points to:a
, the key 1 points to:b
, and the key 2 points to:c
.
However, at this point in the curriculum, we have not yet described what a map is, nor have we defined what a key is, so this definition is problematic.
For your consideration... old text in {{ }}, suggested text in << >>.
A web application is software that runs in a web browser (like Chrome, Firefox, Internet Explorer, Safari). It communicates with a server over the Internet that can do central processing and data storage. The browser is the client, or user interface, to the web app where users interact with it. The server does the heavy lifting.
The way that the client and server communicate with each other is through {{ a protocol called HTTP }} << the Hypertext Transfer Protocol, or HTTP >>. A protocol is a set of rules for communication. Think of the airport ground crew who use wands to communicate with the pilots taxiing the planes around the tarmac. They have a limited set of signals that mean specific things which guide what the pilot does. There are a set of guidelines for when and how the pilot will receive and respond to those signals. HTTP is the protocol for sending requests to the server and for the server to send responses to those requests.
A web app accepts an HTTP request from the client and gives it a response. Think of the server as just like the Clojure functions we have written. They take the input of a request and give the output of a response. In fact, we can write that function:
(defn app
[request]
{:status 200})
This function accepts 'request' as a parameter, then returns a map with a status of 200. In HTTP, a status of 200 means everything worked fine.
When you bring up something in your web browser you want more than a status code, though. You want to see words and images and dancing gifs (ok, maybe not that). You want it to be easy to read and pleasant to look at. {{ HTML }} << The Hypertext Markup Language, or HTML, >> is what makes web pages look that way. It is a markup language, which is different from a programming language. It is a way to put tags around words that suggest to the web browser the way you want them to look.
Go to the ClojureBridge web site. Then in your web browser, view the source.
Look at the parts that have angle brackets around them like <this>
. Those are HTML tags. Search for <h1>
. That is a tag that tells the browser to display the text until the close tag, </h1>
, as a level one header. Look at the web page and see how that text displays.
Let's make the simplest possible web application. It will say hello in the browser.
First, run lein new basic-app
in the console << to create a new project >>.
{{ Create the file src/basic_app.clj
and make it look like this: }}
<< Create a new file in the basic-app/src folder called basic_app.clj
with the following contents: >>
(ns basic-app)
(defn app
"Return a status."
;; A request comes in the handler.
[request]
;; The handler returns a response map.
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world."})
{{ We need some supporting pieces to run this. We need the project.clj for Leiningen. }}
<< Now open up basic-app/project.clj
and change that file so that it reads: >>
(defproject basic-app "0.1.0-SNAPSHOT"
;; We require ring.
:dependencies [[org.clojure/clojure "1.5.1"]
[ring "1.2.1"]]
;; We use the lein-ring plugin to start ring.
:plugins [[lein-ring "0.8.10"]]
;; We tell Ring what our handler function is and
;; what port to start on.
:ring {:handler basic-app/app
:port 3001})
There are multiple dependencies in this file: Ring and Lein-ring. Ring is a Clojure library {{ need to talk about libraries in First Program section }} << , or set of functionality written by someone else (so you don't have to do it all from scratch) >> that handles HTTP requests and responses. Lein-ring allows Leiningen to start ring << and run your web app >>.
To run the web app, type the following at the command line:
lein ring server
Now let's do the same thing but with HTML so that the text displays with formatting.
(ns basic-app
(:require [clojure.pprint :refer [pprint]]))
(defn app
"Return the request as HTML."
;; A request comes in the handler.
[request]
;; The handler returns a response map.
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "<h1>Hello World</h1><pre>"
(with-out-str (pprint request))
"</pre>")})
<< Requiring clojure.pprint
, or "pretty printer" allows you to display text in a more readable format on the page.
The change shown below tells the browser to display "Hello World" as a level one heading (big and bold), then calls the function with-out-str
which means "capture" the "pretty printed" web request that was sent to the server, and display it within a <pre>
(preformatted text) block in the browser.
:body (str "<h1>Hello World</h1><pre>"
(with-out-str (pprint request))
"</pre>")})
Refresh your browser to see the formatted page. You should see "Hello World" at the top in big, bold text, followed by a list of easy to read request related key-value pairs. >>
The web browser gets to the right server with an address called a {{ URL }} << Uniform Resource Locator, or URL >>. Take a look at the following URLs:
After the "http://", "www.google.com" identifies the server. Then the part after that, "/advanced_search" is the path to the resource {{ (or program?) }} << , program, or page >> on that server that will handle this request. In a single web application, you will almost certainly have many actions. For example, you may want to order a book, look up all the books you ordered, or check the status of an order. If one web application handles all of that, it needs to track who takes care of what.
The process of coordinating which path goes to what action is called routing. The Clojure library that does this is called Compojure. Here is an example of routing with Compojure.
(ns basic-app
(:use compojure.core)
(:require [clojure.pprint :refer [pprint]]
[compojure.route :as route]))
(defn index-page []
"index content")
(defn person-page [person]
"person content")
(defn add-debt-page []
"add debt content")
(defn add-debt-post [xfer]
"add debt post content")
(defroutes app
;verb route parameters handler
(GET "/" [] (index-page))
(GET "/debts/:person" [person] (person-page person))
(GET "/add-debt" [] (add-debt-page))
(POST "/add-debt" [from to amount]
(add-debt-post {:from from,
:to to,
:amount amount}))
(route/resources "/")
(route/not-found "Page not found"))
The verbs - GET, POST - are part of the http request and say what you want to do with this URL. GET is just fetching some data. POST is usually doing some processing on that data. The route is the path part of the URL. The handler is the Clojure function that is going to handle this particular request. The routes table maps that combination of verb and route to the handler function.
<< As an example from above, if a user just types in our server name (i.e. basic-app.com), this is the same as "/", meaning this is a request for our site's index page, and the request should be routed to our index-page
handler which probably does something like return a response containing the contents of our home page to be displayed in the user's browser. Similarly, a request for "/debts/:person" is sent to our person-page
handler which returns the correct page for the specified person. >>
One more thing about URLs. They can contain query strings at the end. These are a way to pass additional information to web applications.
Here you see the URL for Twitter search: https://twitter.com/search. The part after the ? is the query string: q=marmots&lang=en. What am I searching for? Marmots. What language do I want results for? English.
A brief word about {{ JSON }} << JavaScript Object Notation, or JSON >>. JSON is a format for providing data. Sometimes, instead of a web page you see in your browser, you want to provide a list of information from a web application. Having a format that everyone agrees on makes it easier to deal with the data. Here's an example that describes a book:
{"book": {
"title": "The Fault in our Stars",
"author": "John Green",
"characters": [
{"name": "Hazel Grace Lancaster", "age": 16},
{"name": "Augustus Waters", "age": 17}
]
}}
<< A handler on the server might query a database and receive a list of book details in the format shown above, which it will process and then return as a formatted HTML response to the user's browser. This allows the database to just store key-value pairs (i.e. "author": "John Green") and enables the handler to decide how to format the display depending on the browser or device type. >>
What it would look like with all edits accepted (so you can copy/paste if desired:
A web application is software that runs in a web browser (like Chrome, Firefox, Internet Explorer, Safari). It communicates with a server over the Internet that can do central processing and data storage. The browser is the client, or user interface, to the web app where users interact with it. The server does the heavy lifting.
The way that the client and server communicate with each other is through the Hypertext Transfer Protocol, or HTTP. A protocol is a set of rules for communication. Think of the airport ground crew who use wands to communicate with the pilots taxiing the planes around the tarmac. They have a limited set of signals that mean specific things which guide what the pilot does. There are a set of guidelines for when and how the pilot will receive and respond to those signals. HTTP is the protocol for sending requests to the server and for the server to send responses to those requests.
A web app accepts an HTTP request from the client and gives it a response. Think of the server as just like the Clojure functions we have written. They take the input of a request and give the output of a response. In fact, we can write that function:
(defn app
[request]
{:status 200})
This function accepts 'request' as a parameter, then returns a map with a status of 200. In HTTP, a status of 200 means everything worked fine.
When you bring up something in your web browser you want more than a status code, though. You want to see words and images and dancing gifs (ok, maybe not that). You want it to be easy to read and pleasant to look at. The Hypertext Markup Language, or HTML, is what makes web pages look that way. It is a markup language, which is different from a programming language. It is a way to put tags around words that suggest to the web browser the way you want them to look.
Go to the ClojureBridge web site. Then in your web browser, view the source.
Look at the parts that have angle brackets around them like <this>
. Those are HTML tags. Search for <h1>
. That is a tag that tells the browser to display the text until the close tag, </h1>
, as a level one header. Look at the web page and see how that text displays.
Let's make the simplest possible web application. It will say hello in the browser.
First, run lein new basic-app
in the console to create a new project.
Create a new file in the basic-app/src folder called basic_app.clj
with the following contents:
(ns basic-app)
(defn app
"Return a status."
;; A request comes in the handler.
[request]
;; The handler returns a response map.
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello, world."})
Now open up basic-app/project.clj
and change that file so that it reads:
(defproject basic-app "0.1.0-SNAPSHOT"
;; We require ring.
:dependencies [[org.clojure/clojure "1.5.1"]
[ring "1.2.1"]]
;; We use the lein-ring plugin to start ring.
:plugins [[lein-ring "0.8.10"]]
;; We tell Ring what our handler function is and
;; what port to start on.
:ring {:handler basic-app/app
:port 3001})
There are multiple dependencies in this file: Ring and Lein-ring. Ring is a Clojure library, or set of functionality written by someone else (so you don't have to do it all from scratch) that handles HTTP requests and responses. Lein-ring allows Leiningen to start ring and run your web app.
To run the web app, type the following at the command line:
lein ring server
Now let's do the same thing but with HTML so that the text displays with formatting.
(ns basic-app
(:require [clojure.pprint :refer [pprint]]))
(defn app
"Return the request as HTML."
;; A request comes in the handler.
[request]
;; The handler returns a response map.
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "<h1>Hello World</h1><pre>"
(with-out-str (pprint request))
"</pre>")})
Requiring clojure.pprint
, or "pretty printer" allows you to display text in a more readable format on the page.
The change shown below tells the browser to display "Hello World" as a level one heading (big and bold), then calls the function with-out-str
which means "capture" the "pretty printed" web request that was sent to the server, and display it within a <pre>
(preformatted text) block in the browser.
:body (str "<h1>Hello World</h1><pre>"
(with-out-str (pprint request))
"</pre>")})
Refresh your browser to see the formatted page. You should see "Hello World" at the top in big, bold text, followed by a list of easy to read request related key-value pairs.
The web browser gets to the right server with an address called a Uniform Resource Locator, or URL. Take a look at the following URLs:
After the "http://", "www.google.com" identifies the server. Then the part after that, "/advanced_search" is the path to the resource, program, or page on that server that will handle this request. In a single web application, you will almost certainly have many actions. For example, you may want to order a book, look up all the books you ordered, or check the status of an order. If one web application handles all of that, it needs to track who takes care of what.
The process of coordinating which path goes to what action is called routing. The Clojure library that does this is called Compojure. Here is an example of routing with Compojure.
(ns basic-app
(:use compojure.core)
(:require [clojure.pprint :refer [pprint]]
[compojure.route :as route]))
(defn index-page []
"index content")
(defn person-page [person]
"person content")
(defn add-debt-page []
"add debt content")
(defn add-debt-post [xfer]
"add debt post content")
(defroutes app
;verb route parameters handler
(GET "/" [] (index-page))
(GET "/debts/:person" [person] (person-page person))
(GET "/add-debt" [] (add-debt-page))
(POST "/add-debt" [from to amount]
(add-debt-post {:from from,
:to to,
:amount amount}))
(route/resources "/")
(route/not-found "Page not found"))
The verbs - GET, POST - are part of the http request and say what you want to do with this URL. GET is just fetching some data. POST is usually doing some processing on that data. The route is the path part of the URL. The handler is the Clojure function that is going to handle this particular request. The routes table maps that combination of verb and route to the handler function.
As an example from above, if a user just types in our server name (i.e. basic-app.com), this is the same as "/", meaning this is a request for our site's index page, and the request should be routed to our index-page
handler which probably does something like return a response containing the contents of our home page to be displayed in the user's browser. Similarly, a request for "/debts/:person" is sent to our person-page
handler which returns the correct page for the specified person.
One more thing about URLs. They can contain query strings at the end. These are a way to pass additional information to web applications.
Here you see the URL for Twitter search: https://twitter.com/search. The part after the ? is the query string: q=marmots&lang=en. What am I searching for? Marmots. What language do I want results for? English.
A brief word about JavaScript Object Notation, or JSON. JSON is a format for providing data. Sometimes, instead of a web page you see in your browser, you want to provide a list of information from a web application. Having a format that everyone agrees on makes it easier to deal with the data. Here's an example that describes a book:
{"book": {
"title": "The Fault in our Stars",
"author": "John Green",
"characters": [
{"name": "Hazel Grace Lancaster", "age": 16},
{"name": "Augustus Waters", "age": 17}
]
}}
A handler on the server might query a database and receive a list of book details in the format shown above, which it will process and then return as a formatted HTML response to the user's browser. This allows the database to just store key-value pairs (i.e. "author": "John Green") and enables the handler to decide how to format the display depending on the browser or device type.
A namespace exists in the file
src/global_growth/core.clj
. Open it, and find this line:(ns global-growth.core)
This is followed by instructions on requiring dependencies.
Confusingly, the aforementioned file already requires the dependencies, so no changes are necessary. We could either:
Which approach is preferable? I'll put together a pull request once we've decided. :)
In EXERCISE: Find the average in the functions section, it asks to pass a vector into the function, and I don't know that that has been done up to that point in the curriculum. So that might need some description.
Here are the instructions that come with Catnip.
Need to write up something beginner-focused like what Clinton did for Nightcode.
Since we no longer deploy a web app, the Heroku setup can be removed (or, at least, marked as optional) from the installfest instructions. Note: there are multiple versions of the instructions, so need to take them out from multiple places.
First, go through app and make a list with all necessary concepts that must be covered.
Then, take that list and look through the curriculum to make sure each concept is covered.
At least on Mac OS 10.8, git is not installed by default. The student needs to install it as part of setup. Probably the easiest path to done is downloading from git-scm.org.
In the "Arithmetic" section, there's currently a reference to BigInts:
(* 8 1/4) ;=> 2 ;; this produces 2N which means 2 is a BigInt.
BigInts are pretty far out of scope for a beginner-level workshop. Any feedback as to how we want to adjust this line?
We need to update the installation instructions. @bridgethillyer or @seancorfield, could you take this on?
As we encounter issues with environment setup, etc. in workshops, it would be great to have a section of the curriculum that has fixes for problems we've seen before.
Include Issue #75
At the beginning of the "Vectors" section of data_structures.md, there's an initial description of vectors:
Vectors are sequential collections of data. You could say they are lists of data, but we also have another collection called a list. If you have programmed in another language, these might have been called arrays in that language.
...this needs rephrasing. Suggestions?
When "first program" and "Make your own web application" sections are done, review entire curriculum and take out parts that:
I've personally managed to totally ignore this one. Probably just need to pull out the heroku part of the setup and add some details.
Low-hanging fruit here people! :)
When folks update the curriculum, the slides usually end up falling out of date, potentially requiring someone to manually go back through and correct them before each workshop.
We should probably have a section in CONTRIBUTING.md that addresses this--possibly just a section that says tl;dr "If you update the curriculum, your PR must also address updates in the slides"?
Add file to curriculum and link to it from README with instructions to teachers on how to use curriculum
The definition of vectors could use some clarification:
Vectors are sequential collections of data. You could say they are lists of data, but
we also have another collection called a list. If you have programmed in another
language, these might have been called arrays in that language.
This doesn't clearly define what vectors are; it mostly defines what they are by comparison (not "lists," but "arrays"). It would be worthwhile to rewrite this definition for students who have never programmed before.
To match the naming scheme in the actual project for the app.
Keith worked through the Windows 7 setup instructions. It took about 40 minutes altogether. It took a while for the JDK to download. Here is where he had some hiccups:
heroku open
I can make these changes. I thought the narrative around the issues he had was interesting perspective, though, on the kinds things that trip up students.
One of the concepts needed for the app is for. Daniel kindly wrote something up, but I don't know where it should go. It should go in data structures, but that section is very full. Also, I can't quite picture where it goes there. Thoughts @cndreisbach ?
Line 29
On line 149:
(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})
'parse-json' should be 'json/parse-string'
'parse-json' doesn't seem to exist according to https://github.com/dakrone/cheshire
(defn get-api
"Returns map representing API response."
[path qp](let [base-path %28str base-uri path%29
query-params %28merge 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
In https://github.com/ClojureBridge/curriculum/blob/master/outline/data_structures.md
Line 235
(set ["earth" "mars" "venus"])
;=> #{1 2 3}
expect should be #{"earth" "mars" "venus"}
Student with WIndows 7 kept getting errors when she ran lein repl
that had this in them:
Address family not supported by protocol family: connect
lein couldn't download its stuff. Found the resolution here:
http://stackoverflow.com/a/21383865
A program called Relevant Knowledge was somehow blocking the traffic. Possibly spyware. Anyways, uninstalling it (with her permission- she hadn't heard of it) fixed the problem. Documenting for posterity- not sure whether a troubleshooting section would be helpful for future groups. Happy to write something up if there's a place that makes sense.
curriculum/outline/functions.md has fairly long descriptions of how map
and reduce
work, then closes the section on "Functions that take other functions" with a reference to sort-by
. However, it doesn't explain how sort-by
works, other than with a very short code example demonstrating usage.
It would probably be useful for students to see another example of the sort-by
algorithm working step-by-step, even if only to preserve parallelism in the text.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.