-
Notifications
You must be signed in to change notification settings - Fork 82
/
names_and_places.clj
150 lines (139 loc) · 7.86 KB
/
names_and_places.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(ns ^{:skip-wiki true} labs.names-and-places
(:use clojure.contrib.math
clojure.contrib.with-ns
labrepl.util
solutions.dialect)
(:require [clojure.contrib.math :as m])
(:import [java.util Date Random]))
(System/getProperties)
(defn cool
[])
(defn overview
[]
[[:h3 "Overview"]
[:p "In this lab you will learn to manage Clojure namespaces and Java packages. In short:"
[:ul
[:li "use " [:code "require"] " to load clojure libraries"]
[:li "use " [:code "refer"] " to refer to functions in the current namespace"]
[:li "use " [:code "use"] " to load and refer all in one step"]
[:li "use " [:code "import"] " to refer to Java classes in the current namespace"]]]])
(defn require-section
[]
[[:h3 "Require"]
[:ol
[:li "You can load the code for any clojure library with "
(c "(require libname)")
". Try it with " (c clojure.contrib.math) ":"
(code (require 'clojure.contrib.math))]
[:li "Then print the directory of names available in the namespace"
(code (dir clojure.contrib.math))]
[:li "Show using " [:code "lcm"] " to calculate the least common multiple:"
(repl-code (clojure.contrib.math/lcm 11 41))]
[:li "Writing out the namespace prefix on every function call is a pain, so you can specify a shorter alias using " [:code "as"] ":"
(code (require '[clojure.contrib.math :as m]))]
[:li "Calling the shorter form is much easier:"
(repl-code (m/lcm 120 1000))]
[:li "You can see all the loaded namespaces with"
(code (all-ns))]]])
(defn refer-and-use
[]
[[:h3 "Refer and Use"]
[:ol
[:li "It would be even easier to use a function with no namespace prefix at all. You can do this by " [:i "referring"] " to the name, which makes a reference to the name in the current namespace:"
(code (refer 'clojure.contrib.math))]
[:li "Now you can call " [:code "lcm"] " directly:"
(repl-code (lcm 16 30))]
[:li "If you want to load and refer all in one step, call " [:code "use"] ": "
(code (use 'clojure.contrib.math))]
[:li "Referring a library refers all of its names. This is often undesirable, because"
[:ul
[:li "it does not clearly document intent to readers"]
[:li "it brings in more names than you need, which can lead to name collisions"]]
"Instead, use the following style to specify only those names you want:"
(code (use '[clojure.contrib.math :only (lcm)]))
"The " [:code ":only"] " option is available on all the namespace management forms. (There is also an " [:code ":exclude"] " which works as you might expect.)"]
[:li "The variable " [:code "*ns*"] " always contains the current namespace, and you can see what names your current namespace refers to by calling"
(code (ns-refers *ns*))]
[:li "The refers map is often pretty big. If you are only interested in one symbol, pass that symbol to the result of calling " [:code "ns-refers"] ": "
(with-ns 'labs.names-and-places
(repl-code ((ns-refers *ns*) 'dir)))]]])
(defn import-section
[]
[[:h3 "Import"]
[:ol
[:li "Importing is like referring, but for Java classes instead of Clojure namespaces. Instead of "
(code (java.io.File. "woozle"))
" you can say "
(code (import java.io.File) (File. "woozle"))]
[:li "You can import multiple classes in a Java package with the form "
(code (import [package Class Class]))
"For example: "
(code (import [java.util Date Random])
(Date. (long (.nextInt (Random.)))))]
[:li "Programmers new to Lisp are often put off by the \"inside-out\" reading of forms like the date creation above. Starting from the inside, you "
[:ul
[:li "get a new " [:code "Random"]]
[:li "get the next random integer"]
[:li "cast it to a long"]
[:li "pass the long to the " [:code "Date"] " constructor"]]
"You don't have to write inside-out code in Clojure. The " [:code "->"] " macro takes its first form, and passes it as the first argument to its next form. The result then becomes the first argument of the next form, and so on. It is easier to read than to describe:"
(repl-code (-> (Random.) (.nextInt) (long) (Date.)))]]])
(defn load-and-reload
[]
[[:h3 "Load and Reload"]
[:p "The REPL isn't for everything. For work you plan to keep, you will want to place your source code in a separate file. Here are the rules of thumb to remember when creating your own Clojure namespaces."
[:ol
[:li "Clojure namespaces (a.k.a. libraries) are equivalent to Java packages."]
[:li "Clojure respects Java naming conventions for directories and files, but Lisp naming conventions for namespace names. So a Clojure namespace " [:code "com.my-app.utils"] " would live in a path named " [:code "com/my_app/utils.clj"] ". Note especially the underscore/hyphen distinction."]
[:li "Clojure files normally begin with a namespace declaration, e.g."
(code (ns com.my-app.utils))]
[:li "The syntax for import/use/refer/require presented in the previous sections is for REPL use. Namespace declarations allow similar forms--similar enough to aid memory, but also different enough to confuse. The following forms at the REPL:"
(code (use 'foo.bar)
(require 'baz.quux)
(import '[java.util Date Random]))
" would look like this in a source code file:"
(code (ns com.my-app.utils
(:use foo.bar)
(:require baz.quux)
(:import [java.util Date Random])))
" Symbols become keywords, and quoting is no longer required."]
[:li "At the time of this writing, the error messages for doing it wrong with namespaces are, well, opaque. Be careful."]]]
[:p "Now let's try creating a source code file. We aren't going to bother with explicit compilation for now. Clojure will automatically (and quickly) compile source code files on the classpath. Instead, we can just add Clojure (.clj) files to the " [:code "src"] " directory."
[:ol
[:li "Create a file named " [:code "student/dialect.clj"] " in the " [:code "src"] " directory, with the appropriate namespace declaration:"
(code (ns student.dialect))]
[:li "Now, implement a simple " [:code "canadianize"] " function that takes a string, and appends " [:code ", eh?"]
(code (defn canadianize
[sentence]
(str sentence ", eh")))]
[:li "From your REPL, use the new namespace:"
(code (use 'student.dialect))]
[:li "Now try it out."
(let [canadianize
#(str % ", eh")]
(repl-code (canadianize "Hello, world.")))]
[:li "Oops! We need to trim the period off the end of the input. Fortunately, " [:code "clojure.contrib.str-utils2"] " provides " [:code "chop"] ". Go back to " [:code "student/dialect.clj"] " and add require in " [:code "clojure.contrib.str-utils2"] ": "
(code (ns student.dialect
(:require [clojure.contrib.str-utils2 :as s])))]
[:li "Now, update " [:code "canadianize"] " to use " [:code "chop"] ": "
(code (defn canadianize
[sentence]
(str (s/chop sentence) ", eh?")))]
[:li "If you simply retry calling " [:code "canadianize"] " from the repl, you will not see your new change, because the code was already loaded. However, you can use namespace forms with " [:code "reload"] " ( or " [:code "reload-all"] ") to reload a namespace (and its dependencies)."
(code (use :reload 'student.dialect))]
[:li "Now you should see the new version of " (c canadianize) ": "
(repl-code (canadianize "Hello, world."))]]]])
(defn bonus
[]
[[:h3 "Bonus"]
[:ol
[:li "Canadianize was too easy. Implement " [:code "pig-latinize"] "."]
[:li "Pig latin was too easy. Implement " [:code "germanize"] "."]]])
(defn instructions
[]
(concat (overview)
(require-section)
(refer-and-use)
(import-section)
(load-and-reload)
(bonus)))