More Related Content
Similar to Clojure: Towards The Essence Of Programming (What's Next? Conference, May 2011) (20)
More from Howard Lewis Ship (12)
Clojure: Towards The Essence Of Programming (What's Next? Conference, May 2011)
- 2. essence
noun
the intrinsic nature or indispensable quality of
something, esp. something abstract, that
determines its character : conflict is the essence of
drama.
© 2011 Howard M. Lewis Ship
- 8. Stock Stock Stock
ticker: AAPL ticker: MSFT ticker: ORCL
lastTrade: 203.25 lastTrade: 29.12 lastTrade: 21.90
open: 204.50 open: 29.08 open: 21.83
shares: 100 shares: 50 shares: 200
public static void sortByLastTrade(List<Stock> portfolio) {
Comparator<Stock> c = new Comparator<Stock>() {
public int compare(Stock o1, Stock o2) {
return o1.getLastTrade() - o2.getLastTrade();
}
};
Collections.sort(portfolio, c);
}
public static void sortByOpen(List<Stock> portfolio) {
Comparator<Stock> c = new Comparator<Stock>() {
public int compare(Stock o1, Stock o2) {
return o1.getOpen() - o2.getOpen();
}
};
Collections.sort(portfolio, c);
}
© 2011 Howard M. Lewis Ship
- 9. :ticker AAPL :ticker MSFT :ticker ORCL
:last-trade 203.25 :last-trade 29.12 :last-trade 21.90
{ :open 204.50 } { :open 29.08 }{ :open 21.83 }
:shares 100 :shares 50 :shares 200
user=> portfolio
[{:ticker "AAPL", :last-trade 203.25M, :open 204.50M, :shares 100}
{:ticker "MSFT", :last-trade 29.12M, :open 29.08M, :shares 50}
{:ticker "ORCL", :last-trade 21.90M, :open 21.83M, :shares 200}]
user=> (sort-by :last-trade portfolio)
({:ticker "ORCL", :last-trade 21.90M, :open 21.83M, :shares 200}
{:ticker "MSFT", :last-trade 29.12M, :open 29.08M, :shares 50}
{:ticker "AAPL", :last-trade 203.25M, :open 204.50M, :shares 100})
user=> (sort-by :shares portfolio)
({:ticker "MSFT", :last-trade 29.12M, :open 29.08M, :shares 50}
{:ticker "AAPL", :last-trade 203.25M, :open 204.50M, :shares 100}
{:ticker "ORCL", :last-trade 21.90M, :open 21.83M, :shares 200})
user=> (defn value-at-open [stock] …)
#'user/value-at-open
user=> (sort-by value-at-open portfolio)
({:ticker "MSFT", :last-trade 29.12M, :open 29.08M, :shares 50}
{:ticker "ORCL", :last-trade 21.90M, :open 21.83M, :shares 200}
{:ticker "AAPL", :last-trade 203.25M, :open 204.50M, :shares 100})
© 2011 Howard M. Lewis Ship
- 11. Aargh!
(defn render-json
"Renders JSON content (typically, a map or a seq) as the
response. The response content type is set to
"application/json". Returns true."
[env json-value]
(let [response (-> env :servlet-api :response)]
(.setContentType response "application/json")
(with-open [writer (.getWriter response)]
(binding [*out* writer]
(print-json json-value))))
true)
© 2011 Howard M. Lewis Ship
- 12. (defn render-json
"Renders JSON content (typically, a map or a seq) as the
response. The response content type is set to
"application/json". Returns true."
[env json-value]
(let [response (-> env :servlet-api :response)] Panic Ensues
(.setContentType response "application/json")
(with-open [writer (.getWriter response)]
(binding [*out* writer]
(print-json json-value))))
true)
Ouch!
© 2011 Howard M. Lewis Ship
- 14. defn render-json env json-value
let
response -> env :servlet-api :response
.setContentType response "application/json"
with-open
writer .getWriter response
binding
*out* writer
print-json json-value
© 2011 Howard M. Lewis Ship
- 15. public static boolean renderJSON(Environment env, JSONObject object) {
HttpServletResponse response = env.getServletAPI().getResponse();
response.setContentType("application/json");
Writer writer = null;
try {
writer = response.getWriter();
printJSON(writer, object);
}
finally {
if (writer != null) {
writer.close();
}
}
return true;
}
© 2011 Howard M. Lewis Ship
- 16. (defn render-json
"Renders JSON content (typically, a map or a seq) as the
response. The response content type is set to
"application/json". Returns true."
[env json-value]
(let [response (-> env :servlet-api :response)]
(.setContentType response "application/json")
(with-open [writer (.getWriter response)]
(binding [*out* writer]
(print-json json-value)))) 24/234
true)
public static boolean renderJSON(Environment env, JSONObject object) {
HttpServletResponse response = env.getServletAPI().getResponse();
response.setContentType("application/json");
Writer writer = null;
try {
writer = response.getWriter();
printJSON(writer, object);
}
finally {
if (writer != null) {
writer.close();
}
}
return true; 24/375
}
© 2011 Howard M. Lewis Ship
- 17. public static float toFahrenheit(float celcius) {
return 9 * celcius / 5 + 32;
}
High Low
Precedence Precedence
+
(+
/ 32 (/
(* 9 c)
5)
* 5
32)
9 celcius
© 2011 Howard M. Lewis Ship
- 18. public static float toFahrenheit(float celcius) {
return 9 * celcius / 5 + 32;
}
$ hexdump bin/org/example/Conversions.class
0000000 ca fe ba be 00 00 00 31 00 17 07 00 02 01 00 17
public static toFahrenheit(F)F 0000010 6f 72 67 2f 65 78 61 6d 70 6c 65 2f 43 6f 6e 76
L0 0000020 65 72 73 69 6f 6e 73 07 00 04 01 00
0000030 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63
10
74
6a
01
61
00
76
06
LINENUMBER 5 L0 0000040 3c 69 6e 69 74 3e 01 00 03 28 29 56 01 00 04 43
0000050 6f 64 65 0a 00 03 00 09 0c 00 05 00 06 01 00 0f
LDC 9.0 0000060 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01
0000070 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54
FLOAD 0 0000080 61 62 6c 65 01 00 04 74 68 69 73 01 00 19 4c 6f
FMUL 0000090 72 67 2f 65 78 61 6d 70 6c 65 2f 43 6f 6e 76 65
00000a0 72 73 69 6f 6e 73 3b 01 00 0c 74 6f 46 61 68 72
LDC 5.0 00000b0 65 6e 68 65 69 74 01 00 04 28 46 29 46 04 41 10
00000c0 00 00 04 40 a0 00 00 04 42 00 00 00 01 00 07 63
FDIV 00000d0 65 6c 63 69 75 73 01 00 01 46 01 00 0a 53 6f 75
LDC 32.0 00000e0 72 63 65 46 69 6c 65 01 00 10 43 6f
00000f0 73 69 6f 6e 73 2e 6a 61 76 61 00 21
6e
00
76
01
65
00
72
03
FADD 0000100 00 00 00 00 00 02 00 01 00 05 00 06 00 01 00 07
0000110 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 08
FRETURN 0000120 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00
0000130 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00
L1 0000140 0d 00 00 00 09 00 0e 00 0f 00 01 00 07 00 00 00
LOCALVARIABLE celcius F L0 L1 0 0000150 35 00 02 00 01 00 00 00 0b 12 10 22 6a 12 11 6e
0000160 12 12 62 ae 00 00 00 02 00 0a 00 00 00 06 00 01
MAXSTACK = 2 0000170 00 00 00 05 00 0b 00 00 00 0c 00 01 00 00 00 0b
0000180 00 13 00 14 00 00 00 01 00 15 00 00 00 02 00 16
MAXLOCALS = 1 0000190
© 2011 Howard M. Lewis Ship
- 21. '(1 2 "three") List of values
(biggest 5 42) Function call
(defn biggest
"Find the maximum of two numbers" Function
[x y] definition
(if (> x y) x y))
© 2011 Howard M. Lewis Ship
- 22. Read Eval Print Loop
user=> (defn biggest
"Find the maximum of two numbers"
[x y]
(if (> x y) x y))
#=(var user/biggest)
user=> (biggest 5 42)
42
user=> (doc biggest)
-------------------------
user/biggest
([x y])
Find the maximum of two numbers
nil
user=> '(1 2 3)
(1 2 3)
user=> '(biggest 5 42)
(biggest 5 42)
user=> (first '(biggest 5 42))
biggest
user=> (eval '(biggest 5 42))
42
© 2011 Howard M. Lewis Ship
- 23. Source Code
Repl Input Clojure
User Classes Java
Evaluator
Compiler
Clojure
Source Files Java Libraries
JVM
Operating System
23 © 2011 Howard M. Lewis Ship
- 24. Simple Literals
• Strings
• Characters user=> "A Clojure String"
"A Clojure String"
user=> space
• nil space
user=> A
• true A
user=> nil nil is Java null
• false nil
user=> true
true
• integers user=> false
false
• floating point numbers user=> 0
0
• ratios user=> 2.5
2.5
user=> 22/7
22/7
© 2011 Howard M. Lewis Ship
- 25. Keywords
• Literal values
• Singleton instances
• Especially useful as map keys
user=> (identical? :a-keyword :a-keyword)
true
user=> (str "a" "-" "string")
"a-string"
user=> (identical? "a-string" (str "a" "-" "string"))
false
user=> (keyword "a-keyword")
:a-keyword
user=> (identical? :a-keyword (keyword "a-keyword"))
true
© 2011 Howard M. Lewis Ship
- 26. Lists
lst
user=> (def lst '(1 2 3))
#=(var user/lst) 1
user=> lst
(1 2 3)
user=> (first lst) 2
1
user=> (rest lst)
(2 3) 3
© 2011 Howard M. Lewis Ship
- 27. Lists
4
lst
conjoin: Add
element to list
1
user=> (conj lst 4)
(4 1 2 3)
user=> (cons 4 lst) 2
(4 1 2 3)
3
construct: new seq
with new first element
© 2011 Howard M. Lewis Ship
- 28. Vectors
user=> (def v [:moe :larry :curly])
#=(var user/v)
user=> v
[:moe :larry :curly] first, rest and others
user=> (first v) work on lists, vectors or
:moe any seq
user=> (rest v)
(:larry :curly)
user=> (conj v :shemp)
[:moe :larry :curly :shemp]
user=> (cons :shemp v)
(:shemp :moe :larry :curly)
user=> v
[:moe :larry :curly]
© 2011 Howard M. Lewis Ship
- 29. Maps
user=> (def m {:first-name "Howard" :last-name "Lewis Ship"})
#=(var user/m)
user=> m
{:last-name "Lewis Ship", :first-name "Howard"}
user=> (get m :last-name)
"Lewis Ship"
Keywords act as a function
user=> (:first-name m)
"Howard" that takes a map
user=> (assoc m :company "TWD")
{:company "TWD", :last-name "Lewis Ship", :first-name "Howard"}
user=> m
{:last-name "Lewis Ship", :first-name "Howard"}
user=> (get m:ssn)
nil
© 2011 Howard M. Lewis Ship
- 30. Sets
user=> (def s #{"Howard" "Suzanne" "Molly" "Jim"})
#=(var user/s)
user=> s
#{"Howard" "Jim" "Molly" "Suzanne"}
user=> (contains? s "Howard")
true
user=> (contains? s "howard")
false
user=> (conj s "Howard")
#{"Howard" "Jim" "Molly" "Suzanne"}
user=> (conj s "Scott")
#{"Howard" "Jim" "Molly" "Suzanne" "Scott"}
© 2011 Howard M. Lewis Ship
- 32. My First Program
10 X = 1
20 PRINT X "No it doesn't"
-- Miss Dimascio
30 X = X + 1
40 GOTO 20
© 2011 Howard M. Lewis Ship
- 34. function |ˈfə ng k sh ən|
noun
A function, in a mathematical sense, expresses
the idea that one quantity (the argument of the
function, also known as the input) completely
determines another quantity (the value, or
the output).
© 2011 Howard M. Lewis Ship
- 37. Inline anoymous
Java Interop
function
(filter (fn [name] (not (.startsWith name "."))) names)
#(…) anonymous
function
(filter #(not (.startsWith % ".")) names)
% anonymous
parameter
user=> (def names ["fred" "barney" ".hidden" "wilma"])
#=(var user/names)
user=> (filter #(not (.startsWith % ".")) names)
("fred" "barney" "wilma")
user=> (remove #(.startsWith % ".") names)
("fred" "barney" "wilma")
© 2011 Howard M. Lewis Ship
- 38. Function as
parameter
(filter #(not (.startsWith % ".")) names)
(defn require-extension [ext]
(fn [file-name]
(= ext (last (split-string file-name ".")))))
Function as
return value
❝Closure
Oriented
(filter (require-extension "gz") names)
Programming❞
Composing
functions
© 2011 Howard M. Lewis Ship
- 40. Stock Stock Stock
ticker: AAPL ticker: MSFT ticker: ORCL
lastTrade: 203.25 lastTrade: 29.12 lastTrade: 21.90
open: 204.50 open: 29.08 open: 21.83
shares: 100 shares: 50 shares: 200
public static List<Double> getOpens(List<Stock> portfolio) {
List<Double> result = new ArrayList<Double>();
for (Stock stock : portfolio) {
result.add(stock.getOpen());
}
return result;
}
© 2011 Howard M. Lewis Ship
- 42. :ticker AAPL :ticker MSFT :ticker ORCL
:last-trade 203.25 :last-trade 29.12 :last-trade 21.90
{ :open 204.50 } { :open 29.08 }{ :open 21.83 }
:shares 100 :shares 50 :shares 200
user=> portfolio
[{:ticker "AAPL", :last-trade 203.25M, :open 204.50M, :shares 100}
{:ticker "MSFT", :last-trade 29.12M, :open 29.08M, :shares 50}
{:ticker "ORCL", :last-trade 21.90M, :open 21.83M, :shares 200}]
user=> (map #(get % :open) portfolio)
(204.50M 29.08M 21.83M)
user=> (map :open portfolio)
(204.50M 29.08M 21.83M)
© 2011 Howard M. Lewis Ship
- 44. user=> (take 20 (iterate inc 1))
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)
user=> (take 20 (map * (iterate inc 1) (iterate inc 1)))
(1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400)
© 2011 Howard M. Lewis Ship
- 46. La
f
zy
user=> (defn last-trade-value
[stock]
(* (:last-trade stock) (:shares stock)))
#'user/last-trade-value
user=> (map last-trade-value portfolio)
(20325.00M 1456.00M 4380.00M)
user=> (map #(assoc % :last-trade-value (last-trade-value %)) portfolio)
({:last-trade-value 20325.00M, :ticker "AAPL", :last-trade 203.25M,
:open 204.50M, :shares 100}
{:last-trade-value 1456.00M, :ticker "MSFT", :last-trade 29.12M,
:open 29.08M, :shares 50}
{:last-trade-value 4380.00M, :ticker "ORCL", :last-trade 21.90M,
:open 21.83M, :shares 200})
© 2011 Howard M. Lewis Ship
- 47. f
user=> (map last-trade-value portfolio)
(20325.00M 1456.00M 4380.00M)
user=> (reduce + (map last-trade-value portfolio))
26161.00M
user=> (reduce + 0 [])
0
user=> (reduce + nil)
0
Initial
Value
© 2011 Howard M. Lewis Ship
- 48. f
user=> (def input-string "Clojure is a fascinating language with unique
capabilities and total integration with Java.")
#'user/input-string
user=> (seq input-string)
(C l o j u r e space i s space a space f a s c i n a t
i n g space l a n g u a g e space w i t h space u n i q
u e space c a p a b i l i t i e s space a n d space t o
t a l space i n t e g r a t i o n space w i t h space J
a v a .)
user=> (reduce
(fn [m k] (update-in m [k] #(inc (or % 0))))
{}
(seq input-string))
{space 12, a 12, b 1, C 1, c 2, d 1, e 5, f 1, g 4, h 2, i 11,
J 1, j 1, l 4, . 1, n 7, o 3, p 1, q 1, r 2, s 3, t 8, u 4,
v 1, w 2}
© 2011 Howard M. Lewis Ship
- 49. List Comprehension
La
zy
user=> (range 0 4)
(0 1 2 3)
user=> (for [suit [:hearts :clubs :spades :diamonds]
value (range 1 4)]
[suit value])
([:hearts 1] [:hearts 2] [:hearts 3]
[:clubs 1] [:clubs 2] [:clubs 3]
[:spades 1] [:spades 2] [:spades 3]
[:diamonds 1] [:diamonds 2] [:diamonds 3])
user=> (for [x (range 0 4)
y (range 0 (inc x))]
[x y])
([0 0]
[1 0] [1 1]
[2 0] [2 1] [2 2]
[3 0] [3 1] [3 2] [3 3])
user=> (for [x (range 0 9) :when (odd? x)
y (range 1 (inc x))]
[x y])
([1 1]
[3 1] [3 2] [3 3]
[5 1] [5 2] [5 3] [5 4] [5 5]
[7 1] [7 2] [7 3] [7 4] [7 5] [7 6] [7 7])
© 2011 Howard M. Lewis Ship
- 50. ❝Somehow the idea of
reusability got attached to
object-oriented
programming in the 1980s,
and no amount of evidence
to the contrary seems to be
able to shake it free.❞
Paul Graham © 2011 Howard M. Lewis Ship
- 52. Who Owns The Java
Language?
Brian Goetz Mark Reinhold
© 2011 Howard M. Lewis Ship
- 53. Not You
© 2011 Howard M. Lewis Ship
- 54. Source Code
Repl Input Clojure
User Classes Java
Evaluator
Compiler
Clojure
Source Files Java Libraries
JVM
Operating System
© 2011 Howard M. Lewis Ship
- 57. Short Circuiting
Evaluation
if (person.isPharaoh() &&
Dead Pharoahs
person.isDead() &&
buildPyramid(person)) { get a Pyramid
person.entomb();
}
boolean isPharoah = person.isPharoah();
boolean isDead = person.isDead();
boolean pyramidComplete = buildPyramid(person); Everyone gets a
Pyramid!
if (isPharoah && isDead && pyramidComplete) {
person.entomb();
}
© 2011 Howard M. Lewis Ship
- 58. (if Function invocation:
(all-true evaluate all parameters
(.isPharaoh person) first
(.isDead person)
(build-pyramid person))
(.entomb person))
(defn all-true
([] true)
([x] x)
([x & more]
(if x (apply all-true more) x)))
public static boolean allTrue(boolean... inputs) {
for (boolean input : inputs) {
if (!input) return false;
}
return true;
}
Java version of
all-true
© 2011 Howard M. Lewis Ship
- 59. (if and short-circuits, so
(and it's not a function
(.isPharaoh person)
(.isDead person)
(build-pyramid person))
(.entomb person))
And what
exactly is if ?
user=> (doc and)
-------------------------
clojure.core/and
([] [x] [x & next])
Macro
Evaluates exprs one at a time, from left to right. If a form
returns logical false (nil or false), and returns that value and
doesn't evaluate any of the other expressions, otherwise it returns
the value of the last expr. (and) returns true.
nil
© 2011 Howard M. Lewis Ship
- 61. Forms
Vectors Lists Maps Sets
Literals Function
[…] '(1 2 3) {…} #{ … } Calls,
"Hello" Special
2.5 Forms,
nil Macros
(a b c)
© 2011 Howard M. Lewis Ship
- 62. If: Special Form
user=> (doc if)
-------------------------
if
Special Form
Please see http://clojure.org/special_forms#if
nil
(if test then else?)
Evaluates test. If not the singular values nil or false,
evaluates and yields then, otherwise, evaluates and
yields else. If else is not supplied it defaults to nil.
…
© 2011 Howard M. Lewis Ship
- 63. Clojure Macros
Reader
Macro
Evaluator Expansion
Bytecode
Generation
© 2011 Howard M. Lewis Ship
- 64. (if
(if
(and
(if (.isPharaoh person)
(.isPharaoh person)
(.isDead person) Macro Expansion (if (.isDead person)
(if (build-pyramid person)
(build-pyramid person))
(.entomb person)))))
(.entomb person))
Approximate
expansion of macro
© 2011 Howard M. Lewis Ship
- 65. (defmacro and
([] true)
([x] x)
([x & next]
`(let [and# ~x]
(if and# (and ~@next) and#))))
Evaluate
(.isPharaoh person)
only once
(if
(let [and_4422_auto (.isPharaoh person)]
(if
(and Macro Expansion (if and_4422_auto
(and
(.isPharaoh person)
(.isDead person)
(.isDead person)
(build-pyramid person))
(build-pyramid person))
and_4422_auto))
(.entomb person))
(.entomb person))
© 2011 Howard M. Lewis Ship
- 66. Code Forms
Macro Expansion
def if let fn . …
Bytecode
Generation
© 2011 Howard M. Lewis Ship
- 67. Boilerplate
public void testLink() {
IMocksControl control = EasyMock.createControl();
HttpServletRequest request = control.newMock(HttpServletRequest.class);
HttpServletResponse response = control.newMock(HttpServletResponse.class);
EasyMock.expect(request.getContextPath()).andReturn("/ctx");
EasyMock.expect(response.encodeURL("/ctx/accounts/list")).andReturn("*encoded*");
control.replay();
assertEquals(…, "*encoded*");
control.verify();
}
(deftest test-link
(with-mocks [request HttpServletRequest
response HttpServletResponse]
(:train
(expect .getContextPath request "/ctx")
(expect .encodeURL response "/ctx/accounts/list" "*encoded*"))
(:test
(is (= (link request response list-accounts-with-loop)
"*encoded*")))))
© 2011 Howard M. Lewis Ship
- 68. Domain Specific Languages
(defview root-index
[env]
:html [
:head [
:title [ "Cascade Blog" ]
]
:body [
:h1 [ "Cascade Blog" ]
:ul { :class "recent-postings" } [
(template-for [posting (recent-postings env)]
:li [
(render-link env show-posting (posting :id) (posting :title))
])
] :html
]
])
:head :body
:title :h1 :ul
"Cascade Blog" "Cascade Blog" (template-for …)
© 2011 Howard M. Lewis Ship
- 69. (defn list-items [coll]
(template
(format "%d items" (count coll))
:ul {:class :item-list} [
(template-for [item coll] :li [item])))
language from within
executable code
Extend Clojure
Expand simple
placeholder to
Clojure
(defn list-items [coll]
(cascade.internal.viewbuilder/combine
(format "%d items" (count coll))
(cascade.dom/element-node :ul {:class :item-list}
(cascade.internal.viewbuilder/combine
(for [item coll]
(cascade.dom/element-node :li nil
(cascade.internal.viewbuilder/combine item)))))))
© 2011 Howard M. Lewis Ship
- 70. Wrap Up
© 2011 Howard M. Lewis Ship
- 71. essence
noun
the intrinsic nature or indispensable quality of
something, esp. something abstract, that
determines its character : conflict is the essence of
drama.
© 2011 Howard M. Lewis Ship
- 77. Clojure
• 1.2 release: 19 Aug 2010
• Simple, regular syntax
• Improves on Lisp: vectors, maps, sets
• Fully integrates with Java
http://www.clojure.org
• Impressive functional & concurrency
support
• Most features not covered here
© 2011 Howard M. Lewis Ship
- 83. Image Credits
© 2007 John Kannenberg
http://www.flickr.com/photos/jkannenberg/541057337/
© 2008 Jonathan Ziapour
http://www.flickr.com/photos/jonathanziapour/2613204502/
© 2008 Alexandre Pizzera
http://www.flickr.com/photos/alecss/2563917055
© 2006 scott ogilvie
http://www.flickr.com/photos/scottog/100582274/
© 2008 Ariel H.
http://www.flickr.com/photos/fotosrotas/2730733412/
© 2008 Howard M. Lewis Ship
http://www.flickr.com/photos/hlship/3108306576
© 2010 yuichi.sakuraba
http://www.flickr.com/photos/skrb/5107280055
© 2007 Jon Fife
http://flickr.com/photos/good-karma/577632972/
© Randall Munroe
http://xkcd.com/297/
© 2008 Manu Gómez
http://www.flickr.com/photos/manugomi/2884678938/
© 2008 Marcin Wichary
http://www.flickr.com/photos/mwichary/2827326852/
© 2011 Howard M. Lewis Ship
- 84. Image Credits
© 2009 Howard M. Lewis Ship
http://www.flickr.com/photos/hlship/3603090614/
© 2006 John Ryan Brubaker
http://www.flickr.com/photos/subconscience/297682093/
© 2003 A. Lipson
http://www.andrewlipson.com/escher/relativity.html
© 2007 Alan Chia
http://flickr.com/photos/seven13avenue/2080281038/
© 2003 Randall Munroe
http://xkcd.com/224/
© 2011 Howard M. Lewis Ship