JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃
Posted 逗逼程序员博爷的日记本
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃相关的知识,希望对你有一定的参考价值。
Clojure Tutorial 从入门到放弃
此教程是Clojure语言的入门教程。嗯嗯,没错!随便写着玩的那种=_=,害怕学完了。。。。。。过两个月就忘了。。。。。。
开始前的准备
在开始前,需要准备准备Clojure环境,有以下两种。懒癌患者推荐第一种,如果你能科学上网的话。为了文章简(懒)捷(癌)流(发)畅(作),第二种直接安装的也只是贴了链接
Clojure online IDE https://repl.it/repls/ShockingCircularBrains
Clojure environment https://clojure.org/guides/getting_started
使用Clojure的原因
为了了解Clojure到底是个东西,本文整理了stackOverflow关于Clojure的一些有趣的撕逼和Clojure官网的相关语言特性介绍,总结出了Clojure的可以干啥,以及它好在哪里,不好再哪里。
Clojure可以做什么
看了StackOverflow和softwareengineering上的若干关于语言的撕逼之后,本文整理的它的以下特点。(emmm。。。都别争了,php是世界上最好的语言)
Scraping web pages 爬取网页Shell scripts 命令行脚本
Building websites 创建网页
Playing around with OpenGL 使用OpenGL (注: https://github.com/ztellman/penumbra 目前只有这一个项目)
Writing async webservers 写异步web服务器 (主要用途)
html Templating 做HTML生成模版
Running parallel tasks 执行并行任务(fetching multiple URLs and process in parallel)
Simulations 模拟一些操作,比如自动化测试中的打开关闭数据库,甚至ssh,tar等shell脚本都可以用clojure来写。
Clojure positive features
Dynamic Development 动态开发 Clojure不是像C,java一样,仅仅是编译完再运行。而是像Python那样允许交互式编程,个人感觉jupyter notebook交互性更好,但是神奇的一点是Clojure是并不是脚本语言,却能达到这样的效果。
Functional Programming 函数式编程 强调递归和迭代而不是循环,并且数据结构不可变。强调好的程序应该是功能强大,结构稳定清晰的。这些特征在一定程度上限制了Clojure程序的自由度。但是在我们也知道,自由度和稳定和功能鱼与熊掌不可兼得。所以这个特征即是Clojure好的地方,也是它的局限性。
Lisp 编程语言
Clojure 是 Lisp的一种方言。然而,Lisp本身的含义就是方言的意思。Lisp语言是基本上都是各种语言的方言。而Clojure是Lisp风格的java方言。
Runtime Polymorphism 运行时多态 其实这个特性就是java的多态,只是因为Lisp的语法风格和JAVA的不同而已。
Concurrent Programming 并行程序 这是Clojure语言的重中之重,也是JVM之上的在并行领域做的比较好的程序之一。它的并行程序编写体验更像是openMP和CUDA等C类语言,在代码简捷性和可读性是优于JAVA很多的。
Hosted on the JVM 寄生在JVM之上的语言 Clojure是 JVM之上的语言。JVM之上有很多非语言,Clojure,Groovy,JRuby,Jython,Kotlin,Scala等。也就是说,Clojure语言有非常稳定的虚拟机,以及一切java语言的生态环境(maven中的各种java包)并且,clojure有自己独有的工具包环境
Clojure negative features
Lisp语言学习成本比较高。虽然LISP语言的表达方式不同于C类语言,Python和Java,它的语法很多都是很独特的。对于没有接触股Lisp语言的同学来说,上手难度不低。
Clojure语言对JVM依赖比较大。因为Clojure的很多工具包都是源自JAVA生态的。所以要真正的使用起来,还需要对java的log,网络等包有一定的了解。
language grammer
本文主要从一下5个方面简要地介绍了Clojure的语法。
Syntax 句法
基本类型和集合的表达
;; Numeric types
42 ; Long - 64-bit integer (from -2^63 to 2^63-1)
6.022e23 ; Double - double-precision 64-bit floating point
42N ; BigInt - arbitrary precision integer
1.0M ; BigDecimal - arbitrary precision fixed-point decimal
22/7 ; Ratio
;; Character types
"hello" ; String
\e ; Character
;; Other types
nil ; null value
true ; Boolean (also, false)
#"[0-9]+" ; Regular expression
:alpha ; Keyword
:release/alpha ; Keyword with namespace
map ; Symbol
+ ; Symbol - most punctuation allowed
clojure.core/+ ; Namespaced symbol
'(1 2 3) ; list
[1 2 3] ; vector
#{1 2 3} ; set
{:a 1, :b 2} ; map
计算过程
略
REPL
使用Clojure的大多数情况都是在一个编辑器或者一个REPL(Read-Eval-Print-Loop)环境。REPL做以下几个环节
Read an expression (a string of characters) to produce Clojure data.
读表达式并且处理数据
Evaluate the data returned from #1 to yield a result (also Clojure data).
计算第一步表达式的返回值
Print the result by converting it from data back to characters.
通过把数据转换成字符打印结果
Loop back to the beginning.
循环到开始
REPL例子如下
*1 (the last result) 代表上次计算结果
*2 (the result two expressions ago) 代表上两次的计算结果
*3 (the result three expressions ago) 代表上三次的计算结果
user=> (+ 3 4)
7
user=> (+ 10 *1)
17
user=> (+ *1 *2)
24
repl被包含在Clojure标准库中
(require '[clojure.repl :refer :all])
介绍一些有用的函数
doc函数查文档
user=> (doc +)
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
user=> (doc doc)
clojure.repl/doc
([name])
Macro
Prints documentation for a var or special form given its name
apropos模糊匹配函数名
user=> (apropos "+")
(clojure.core/+ clojure.core/+')
find-doc 模糊匹配函数名并且查找文档
user=> (find-doc "trim")
clojure.core/subvec
([v start] [v start end])
Returns a persistent vector of the items in vector from
start (inclusive) to end (exclusive). If end is not supplied,
defaults to (count vector). This operation is O(1) and very fast, as
the resulting vector shares structure with the original and no
trimming is done.
clojure.string/trim
([s])
Removes whitespace from both ends of string.
clojure.string/trim-newline
([s])
Removes all trailing newline \n or return \r characters from
string. Similar to Perl's chomp.
clojure.string/triml
([s])
Removes whitespace from the left side of string.
clojure.string/trimr
([s])
Removes whitespace from the right side of string.
定义和打印
def定义常量和函数名(ps:在Clojure中其实没有变量,定义函数和定义函数名其实是等价的,都是定义一个可计算表达式)
user=> (def x 7)
#'user/x
四种打印
打印人可读和机器可读的区别在于,机器可读的打印可以作为程序输入,而人可读就只能给人读,一个是字符串类型的数据,一个是字符串
Human-Readable | Machine-Readable |
---|---|
With newline | println prn |
Without newline print | pr |
user=> (println "What is this:" (+ 1 2))
What is this: 3
user=> (prn "one\n\ttwo")
"one\n\ttwo"
Functions 函数
Creating Functions 创建函数
defn
用于创建函数
;; name params body
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
user=> (greet "students")
"Hello, students"
多种参数数量的函数
(defn messenger
([] (messenger "Hello world!"))
([msg] (println msg)))
user=> (messenger)
Hello world!
nil
user=> (messenger "Hello class!")
Hello class!
nil
可变参数函数
可变参数必须在参数列表的最后
可变参数前用“&”符号标记
(defn hello [greeting & who]
(println greeting who))
user=> (hello "Hello" "world" "class")
Hello (world class)
匿名函数
;; An anonymous function can be created with fn
;; params body
;; --------- -----------------
(fn [message] (println message) )
;; operation (function) argument
;; -------------------------------- --------------
( (fn [message] (println message)) "Hello world!" )
;; Hello world!
defn vs fn
(defn greet [name] (str "Hello, " name))
(def greet (fn [name] (str "Hello, " name)))
匿名函数句法
% is used for a single argument 单个参数
%1, %2, %3, etc are used for multiple arguments 多个参数
%& is used for any remaining (variadic) arguments 可变参数
;; Equivalent to: (fn [x] (+ 6 x))
#(+ 6 %)
;; Equivalent to: (fn [x y] (+ x y))
#(+ %1 %2)
;; Equivalent to: (fn [x y & zs] (println x y zs))
#(println %1 %2 %&)
Applying Functions 应用函数
apply 表达式
(apply f '(1 2 3 4)) ;; same as (f 1 2 3 4)
(apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4)
(apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4)
(apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4)
Locals and Closures 局部表达式和Closures
let用于定义局部变量
(let [x 1
y 2]
(+ x y))
(defn messenger [msg]
(let [a 7
b 5
c (clojure.string/capitalize msg)]
(println a b c)
) ;; end of let scope
) ;; end of function
因为fn形式的特殊,产生了Closure函数
(defn messenger-builder [greeting]
(fn [who] (println greeting who))) ; closes over greeting
;; greeting provided here, then goes out of scope
(def hello-er (messenger-builder "Hello"))
;; greeting value still available because hello-er is a closure
(hello-er "world!")
;; Hello world!
Java Interop java代码的调用
调用JAVA代码
Task | Java | Clojure |
---|---|---|
Instantiation | new Widget("foo") | (Widget. "foo") |
Instance method | rnd.nextInt() | (.nextInt rnd) |
Instance field | object.field | (.field object) |
Static method | Math.sqrt(25) | (Math/sqrt 25) |
Static field | Math.PI | Math/PI |
Sequential Collections 顺序集合
Vectors 向量
向量
[1 2 3]
按索引取值
user=> (get ["abc" false 99] 0)
"abc"
user=> (get ["abc" false 99] 1)
false
user=> (get ["abc" false 99] 14)
nil
计算数量
user=> (count [1 2 3])
3
构造函数
user=> (vector 1 2 3)
[1 2 3]
添加元素
user=> (conj [1 2 3] 4 5 6)
[1 2 3 4 5 6]
不可更改性
user=> (def v [1 2 3])
#'user/v
user=> (conj v 4 5 6)
[1 2 3 4 5 6]
user=> v
[1 2 3]
Lists 列表
构造
(def cards '(10 :ace :jack 9))
user=> (first cards)
10
user=> (rest cards)
'(:ace :jack 9)
加入元素
user=> (conj cards :queen)
(:queen 10 :ace :jack 9)
用作栈
user=> (def stack '(:a :b))
#'user/stack
user=> (peek stack)
:a
user=> (pop stack)
(:b)
Hashed Collections 散列集合
集合
(def players #{"Alice", "Bob", "Kelly"})
加入集合
user=> (conj players "Fred")
#{"Alice" "Fred" "Bob" "Kelly"}
移出集合
user=> (disj players "Bob" "Sal")
#{"Alice" "Kelly"}
检查存在性
user=> (contains? players "Kelly")
true
排序
user=> (conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha")
#{"Alpha" "Bravo" "Charlie" "Sigma"}
into操作和计conj不同,它把后一个集合加入到第一个集合中,并把第一个集合返回,而不是创造一个新的集合
user=> (def players #{"Alice" "Bob" "Kelly"})
user=> (def new-players ["Tim" "Sue" "Greg"])
user=> (into players new-players)
#{"Alice" "Greg" "Sue" "Bob" "Tim" "Kelly"}
哈希表
创建哈希表
也可加上逗号(Clojure中逗号不起任何作用)
(def scores {"Fred" 1400
"Bob" 1240
"Angela" 1024})
;; same as the last one!
(def scores {"Fred" 1400, "Bob" 1240, "Angela" 1024})
加入一个新的键值对或者更新以有键的值
user=> (assoc scores "Sally" 0)
{"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0}
user=> (assoc scores "Bob" 0)
{"Angela" 1024, "Bob" 0, "Fred" 1400}
删除键值对
user=> (dissoc scores "Bob")
{"Angela" 1024, "Fred" 1400}
通过键取值
也可通过把键当作函数的方式取值
user=> (get scores "Angela")
1024
user=> (def directions {:north 0
:east 1
:south 2
:west 3})
#'user/directions
user=> (directions :north)
0
不能直接调用一个哈希表除非它非空
user=> (def bad-lookup-map nil)
#'user/bad-lookup-map
user=> (bad-lookup-map :foo)
NullPointerException
取值时候带有默认值防止为空
user=> (get scores "Sam" 0)
0
user=> (directions :northwest -1)
-1
检查存在性
user=> (contains? scores "Fred")
true
user=> (find scores "Fred")
["Fred" 1400]
取得key集合和取出value集合
user=> (keys scores)
("Fred" "Bob" "Angela")
user=> (vals scores)
(1400 1240 1024)
建立一个哈希表
user=> (def players #{"Alice" "Bob" "Kelly"})
#'user/players
user=> (zipmap players (repeat 0))
{"Kelly" 0, "Bob" 0, "Alice" 0}
合并哈希表
user=> (def new-scores {"Angela" 300 "Jeff" 900})
#'user/new-scores
user=> (merge scores new-scores)
{"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300}
合并并且更新旧值
user=> (def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000})
#'user/new-scores
user=> (merge-with + scores new-scores)
{"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924}
排序哈希表
user=> (def sm (sorted-map
"Bravo" 204
"Alfa" 35
"Sigma" 99
"Charlie" 100))
{"Alfa" 35, "Bravo" 204, "Charlie" 100, "Sigma" 99}
user=> (keys sm)
("Alfa" "Bravo" "Charlie" "Sigma")
user=> (vals sm)
(35 204 100 99)
表示应用领域信息
(def person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"})
属性取值
user=> (get person :occupation)
"Programmer"
user=> (person :occupation)
"Programmer"
user=> (:occupation person)
"Programmer"
user=> (:favorite-color person "beige")
"beige"
更新属性
user=> (assoc person :occupation "Baker")
{:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"}
删除属性
user=> (dissoc person :age)
{:last-name "Keen", :first-name "Kelly", :occupation "Programmer"}
嵌套实体
(def company
{:name "WidgetCo"
:address {:street "123 Main St"
:city "Springfield"
:state "IL"}})
get-in访问内层实体
user=> (get-in company [:address :city])
"Springfield"
用assoc-in和update-in来修改内层的实体
user=> (assoc-in company [:address :street] "303 Broadway")
{:name "WidgetCo",
:address
{:state "IL",
:city "Springfield",
:street "303 Broadway"}}
记录,或者称作类的实例
;; Define a record structure
(defrecord Person [first-name last-name age occupation])
;; Positional constructor - generated
(def kelly (->Person "Kelly" "Keen" 32 "Programmer"))
;; Map constructor - generated
(def kelly (map->Person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"}))
user=> (:occupation kelly)
"Programmer"
Flow Control 控制流
陈述句和表达式
在JAVA中表达式有返回值,而一般的语句没有。在clojure中,不论是表达式,还是一般的语句,它们都有返回值,或者说clojure中只有表达式,一班的语句也是表达式。
流程控制表达式
if语句(else可选)
user=> (str "2 is " (if (even? 2) "even" "odd"))
2 is even
user=> (if (true? false) "impossible!") ;; else is optional
nil
Clojure中,包括0的所有数值都是true,false值只有flase和nil
if和do的组合使用
(if (even? 5)
(do (println "even")
true)
(do (println "odd")
false))
when 语句 when是单分支if语句
when (neg? x)
(throw (RuntimeException. (str "x must be positive: " x))))
cond语句 想当与switch case
(let [x 5]
(cond
(< x 2) "x is less than 2"
(< x 10) "x is less than 10"))
cond and else 当没有cond的表达式为true时,else 执行
(let [x 11]
(cond
(< x 2) "x is less than 2"
(< x 10) "x is less than 10"
:else "x is greater than or equal to 10"))
case
不像cond,如果没有值匹配,case会抛出异常
user=> (defn foo [x]
(case x
5 "x is 5"
10 "x is 10"))
#'user/foo
user=> (foo 10)
x is 10
user=> (foo 11)
IllegalArgumentException No matching clause: 11
case with else-expression
case可以有尾随表达式如果没有测试匹配的话
user=> (defn foo [x]
(case x
5 "x is 5"
10 "x is 10"
"x isn't 5 or 10"))
#'user/foo
user=> (foo 11)
x isn't 5 or 10
迭代
dotimes 表达式
user=> (dotimes [i 3]
(println i))
0
1
2
nil
doseq 表达式
user=> (doseq [n (range 3)]
(println n))
0
1
2
nil
doseq 使用多维绑定,相当于嵌套for循环
user=> (doseq [letter [:a :b]
number (range 3)] ; list of 0, 1, 2
(prn [letter number]))
[:a 0]
[:a 1]
[:a 2]
[:b 0]
[:b 1]
[:b 2]
nil
循环for语句
for像doseq ,也是多维的
user=> (for [letter [:a :b]
number (range 3)] ; list of 0, 1, 2
[letter number])
([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])
递归
递归和迭代
Clojure提供了recur和sequence抽象
recur是经典的迭代,用户不去控制它,它是低等级的设施
sequence把迭代器看成数值
Reducers把迭代器看作是函数组成,被加在了Clojure1.5版本中
loop and recur
(loop [i 0]
(if (< i 10)
(recur (inc i))
i))
defn and recur
(defn increase [i]
(if (< i 10)
(recur (inc i))
i))
defn and recur
(defn increase [i]
(if (< i 10)
(recur (inc i))
i))
表达式
异常处理
trycatchfinally和java一样
(try
(/ 2 1)
(catch ArithmeticException e
"divide by zero")
(finally
(println "cleanup")))
抛出异常
(try
(throw (Exception. "something went wrong"))
(catch Exception e (.getMessage e)))
带有Clojure数据的异常
(try
(throw (ex-info "There was a problem" {:detail 42}))
(catch Exception e
(prn (:detail (ex-data e)))))
with-open 语句用来减少异常的编写
(let [f (clojure.java.io/writer "/tmp/new")]
(try
(.write f "some text")
(finally
(.close f))))
;; Can be written:
(with-open [f (clojure.java.io/writer "/tmp/new")]
(.write f "some text"))
主要参考文档
https://clojure.org/
关注博爷,不定时更新哦~
以上是关于JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃的主要内容,如果未能解决你的问题,请参考以下文章