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

  1. Dynamic Development 动态开发 Clojure不是像C,java一样,仅仅是编译完再运行。而是像Python那样允许交互式编程,个人感觉jupyter notebook交互性更好,但是神奇的一点是Clojure是并不是脚本语言,却能达到这样的效果。


  2. Functional Programming 函数式编程 强调递归和迭代而不是循环,并且数据结构不可变。强调好的程序应该是功能强大,结构稳定清晰的。这些特征在一定程度上限制了Clojure程序的自由度。但是在我们也知道,自由度和稳定和功能鱼与熊掌不可兼得。所以这个特征即是Clojure好的地方,也是它的局限性。


  3. Lisp 编程语言


Clojure 是 Lisp的一种方言。然而,Lisp本身的含义就是方言的意思。Lisp语言是基本上都是各种语言的方言。而Clojure是Lisp风格的java方言。

  1. Runtime Polymorphism 运行时多态 其实这个特性就是java的多态,只是因为Lisp的语法风格和JAVA的不同而已。


  2. Concurrent Programming 并行程序 这是Clojure语言的重中之重,也是JVM之上的在并行领域做的比较好的程序之一。它的并行程序编写体验更像是openMP和CUDA等C类语言,在代码简捷性和可读性是优于JAVA很多的。


  3. Hosted on the JVM 寄生在JVM之上的语言 Clojure是 JVM之上的语言。JVM之上有很多非语言,Clojure,Groovy,JRuby,Jython,Kotlin,Scala等。也就是说,Clojure语言有非常稳定的虚拟机,以及一切java语言的生态环境(maven中的各种java包)并且,clojure有自己独有的工具包环境


Clojure negative features

  1. Lisp语言学习成本比较高。虽然LISP语言的表达方式不同于C类语言,Python和Java,它的语法很多都是很独特的。对于没有接触股Lisp语言的同学来说,上手难度不低。


  2. Clojure语言对JVM依赖比较大。因为Clojure的很多工具包都是源自JAVA生态的。所以要真正的使用起来,还需要对java的log,网络等包有一定的了解。


language grammer

本文主要从一下5个方面简要地介绍了Clojure的语法。

Syntax 句法

基本类型和集合的表达

 
   
   
 
  1. ;; Numeric types

  2. 42 ; Long - 64-bit integer (from -2^63 to 2^63-1)

  3. 6.022e23 ; Double - double-precision 64-bit floating point

  4. 42N ; BigInt - arbitrary precision integer

  5. 1.0M ; BigDecimal - arbitrary precision fixed-point decimal

  6. 22/7 ; Ratio

  7. ;; Character types

  8. "hello" ; String

  9. \e ; Character

  10. ;; Other types

  11. nil ; null value

  12. true ; Boolean (also, false)

  13. #"[0-9]+" ; Regular expression

  14. :alpha ; Keyword

  15. :release/alpha ; Keyword with namespace

  16. map ; Symbol

  17. + ; Symbol - most punctuation allowed

  18. clojure.core/+ ; Namespaced symbol

 
   
   
 
  1. '(1 2 3) ; list

  2. [1 2 3] ; vector

  3. #{1 2 3} ; set

  4. {:a 1, :b 2} ; map

计算过程

REPL

使用Clojure的大多数情况都是在一个编辑器或者一个REPL(Read-Eval-Print-Loop)环境。REPL做以下几个环节

  1. Read an expression (a string of characters) to produce Clojure data.

读表达式并且处理数据

  1. Evaluate the data returned from #1 to yield a result (also Clojure data).

计算第一步表达式的返回值

  1. Print the result by converting it from data back to characters.

通过把数据转换成字符打印结果

  1. Loop back to the beginning.

循环到开始

REPL例子如下

*1 (the last result) 代表上次计算结果

*2 (the result two expressions ago) 代表上两次的计算结果

*3 (the result three expressions ago) 代表上三次的计算结果

 
   
   
 
  1. user=> (+ 3 4)

  2. 7

  3. user=> (+ 10 *1)

  4. 17

  5. user=> (+ *1 *2)

  6. 24

repl被包含在Clojure标准库中

 
   
   
 
  1. (require '[clojure.repl :refer :all])

介绍一些有用的函数

doc函数查文档

 
   
   
 
  1. user=> (doc +)

  2. clojure.core/+

  3. ([] [x] [x y] [x y & more])

  4. Returns the sum of nums. (+) returns 0. Does not auto-promote

  5. longs, will throw on overflow. See also: +'

 
   
   
 
  1. user=> (doc doc)

  2. clojure.repl/doc

  3. ([name])

  4. Macro

  5. Prints documentation for a var or special form given its name

apropos模糊匹配函数名

 
   
   
 
  1. user=> (apropos "+")

  2. (clojure.core/+ clojure.core/+')

find-doc 模糊匹配函数名并且查找文档

 
   
   
 
  1. user=> (find-doc "trim")

  2. clojure.core/subvec

  3. ([v start] [v start end])

  4. Returns a persistent vector of the items in vector from

  5. start (inclusive) to end (exclusive). If end is not supplied,

  6. defaults to (count vector). This operation is O(1) and very fast, as

  7. the resulting vector shares structure with the original and no

  8. trimming is done.

  9. clojure.string/trim

  10. ([s])

  11. Removes whitespace from both ends of string.

  12. clojure.string/trim-newline

  13. ([s])

  14. Removes all trailing newline \n or return \r characters from

  15. string. Similar to Perl's chomp.

  16. clojure.string/triml

  17. ([s])

  18. Removes whitespace from the left side of string.

  19. clojure.string/trimr

  20. ([s])

  21. Removes whitespace from the right side of string.

定义和打印

def定义常量和函数名(ps:在Clojure中其实没有变量,定义函数和定义函数名其实是等价的,都是定义一个可计算表达式)

 
   
   
 
  1. user=> (def x 7)

  2. #'user/x

四种打印

打印人可读和机器可读的区别在于,机器可读的打印可以作为程序输入,而人可读就只能给人读,一个是字符串类型的数据,一个是字符串

Human-Readable Machine-Readable
With newline println prn
Without newline print pr
 
   
   
 
  1. user=> (println "What is this:" (+ 1 2))

  2. What is this: 3

 
   
   
 
  1. user=> (prn "one\n\ttwo")

  2. "one\n\ttwo"

Functions 函数

Creating Functions 创建函数

defn用于创建函数

 
   
   
 
  1. ;; name params body

  2. ;; ----- ------ -------------------

  3. (defn greet [name] (str "Hello, " name) )

  4. user=> (greet "students")

  5. "Hello, students"

多种参数数量的函数

 
   
   
 
  1. (defn messenger

  2. ([] (messenger "Hello world!"))

  3. ([msg] (println msg)))

  4. user=> (messenger)

  5. Hello world!

  6. nil

  7. user=> (messenger "Hello class!")

  8. Hello class!

  9. nil

可变参数函数

可变参数必须在参数列表的最后

可变参数前用“&”符号标记

 
   
   
 
  1. (defn hello [greeting & who]

  2. (println greeting who))

  3. user=> (hello "Hello" "world" "class")

  4. Hello (world class)

匿名函数

 
   
   
 
  1. ;; An anonymous function can be created with fn

  2. ;; params body

  3. ;; --------- -----------------

  4. (fn [message] (println message) )

  5. ;; operation (function) argument

  6. ;; -------------------------------- --------------

  7. ( (fn [message] (println message)) "Hello world!" )

  8. ;; Hello world!

defn vs fn

 
   
   
 
  1. (defn greet [name] (str "Hello, " name))

  2. (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 可变参数

 
   
   
 
  1. ;; Equivalent to: (fn [x] (+ 6 x))

  2. #(+ 6 %)

  3. ;; Equivalent to: (fn [x y] (+ x y))

  4. #(+ %1 %2)

  5. ;; Equivalent to: (fn [x y & zs] (println x y zs))

  6. #(println %1 %2 %&)

Applying Functions 应用函数

apply 表达式

 
   
   
 
  1. (apply f '(1 2 3 4)) ;; same as (f 1 2 3 4)

  2. (apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4)

  3. (apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4)

  4. (apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4)

Locals and Closures 局部表达式和Closures

let用于定义局部变量

 
   
   
 
  1. (let [x 1

  2. y 2]

  3. (+ x y))

  4. (defn messenger [msg]

  5. (let [a 7

  6. b 5

  7. c (clojure.string/capitalize msg)]

  8. (println a b c)

  9. ) ;; end of let scope

  10. ) ;; end of function

因为fn形式的特殊,产生了Closure函数

 
   
   
 
  1. (defn messenger-builder [greeting]

  2. (fn [who] (println greeting who))) ; closes over greeting

  3. ;; greeting provided here, then goes out of scope

  4. (def hello-er (messenger-builder "Hello"))

  5. ;; greeting value still available because hello-er is a closure

  6. (hello-er "world!")

  7. ;; 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. [1 2 3]

按索引取值

 
   
   
 
  1. user=> (get ["abc" false 99] 0)

  2. "abc"

  3. user=> (get ["abc" false 99] 1)

  4. false

  5. user=> (get ["abc" false 99] 14)

  6. nil

计算数量

 
   
   
 
  1. user=> (count [1 2 3])

  2. 3

构造函数

 
   
   
 
  1. user=> (vector 1 2 3)

  2. [1 2 3]

添加元素

 
   
   
 
  1. user=> (conj [1 2 3] 4 5 6)

  2. [1 2 3 4 5 6]

不可更改性

 
   
   
 
  1. user=> (def v [1 2 3])

  2. #'user/v

  3. user=> (conj v 4 5 6)

  4. [1 2 3 4 5 6]

  5. user=> v

  6. [1 2 3]

Lists 列表


构造

 
   
   
 
  1. (def cards '(10 :ace :jack 9))

  2. user=> (first cards)

  3. 10

  4. user=> (rest cards)

  5. '(:ace :jack 9)

加入元素

 
   
   
 
  1. user=> (conj cards :queen)

  2. (:queen 10 :ace :jack 9)

用作栈

 
   
   
 
  1. user=> (def stack '(:a :b))

  2. #'user/stack

  3. user=> (peek stack)

  4. :a

  5. user=> (pop stack)

  6. (:b)

Hashed Collections 散列集合

集合

 
   
   
 
  1. (def players #{"Alice", "Bob", "Kelly"})

加入集合

 
   
   
 
  1. user=> (conj players "Fred")

  2. #{"Alice" "Fred" "Bob" "Kelly"}

移出集合

 
   
   
 
  1. user=> (disj players "Bob" "Sal")

  2. #{"Alice" "Kelly"}

检查存在性

 
   
   
 
  1. user=> (contains? players "Kelly")

  2. true

排序

 
   
   
 
  1. user=> (conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha")

  2. #{"Alpha" "Bravo" "Charlie" "Sigma"}

into操作和计conj不同,它把后一个集合加入到第一个集合中,并把第一个集合返回,而不是创造一个新的集合

 
   
   
 
  1. user=> (def players #{"Alice" "Bob" "Kelly"})

  2. user=> (def new-players ["Tim" "Sue" "Greg"])

  3. user=> (into players new-players)

  4. #{"Alice" "Greg" "Sue" "Bob" "Tim" "Kelly"}

哈希表

创建哈希表

也可加上逗号(Clojure中逗号不起任何作用)

 
   
   
 
  1. (def scores {"Fred" 1400

  2. "Bob" 1240

  3. "Angela" 1024})

  4. ;; same as the last one!

  5. (def scores {"Fred" 1400, "Bob" 1240, "Angela" 1024})

加入一个新的键值对或者更新以有键的值

 
   
   
 
  1. user=> (assoc scores "Sally" 0)

  2. {"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0}

  3. user=> (assoc scores "Bob" 0)

  4. {"Angela" 1024, "Bob" 0, "Fred" 1400}

删除键值对

 
   
   
 
  1. user=> (dissoc scores "Bob")

  2. {"Angela" 1024, "Fred" 1400}

通过键取值

也可通过把键当作函数的方式取值

 
   
   
 
  1. user=> (get scores "Angela")

  2. 1024

 
   
   
 
  1. user=> (def directions {:north 0

  2. :east 1

  3. :south 2

  4. :west 3})

  5. #'user/directions

  6. user=> (directions :north)

  7. 0

不能直接调用一个哈希表除非它非空

 
   
   
 
  1. user=> (def bad-lookup-map nil)

  2. #'user/bad-lookup-map

  3. user=> (bad-lookup-map :foo)

  4. NullPointerException

取值时候带有默认值防止为空

 
   
   
 
  1. user=> (get scores "Sam" 0)

  2. 0

  3. user=> (directions :northwest -1)

  4. -1

检查存在性

 
   
   
 
  1. user=> (contains? scores "Fred")

  2. true

  3. user=> (find scores "Fred")

  4. ["Fred" 1400]

取得key集合和取出value集合

 
   
   
 
  1. user=> (keys scores)

  2. ("Fred" "Bob" "Angela")

  3. user=> (vals scores)

  4. (1400 1240 1024)

建立一个哈希表

 
   
   
 
  1. user=> (def players #{"Alice" "Bob" "Kelly"})

  2. #'user/players

  3. user=> (zipmap players (repeat 0))

  4. {"Kelly" 0, "Bob" 0, "Alice" 0}

合并哈希表

 
   
   
 
  1. user=> (def new-scores {"Angela" 300 "Jeff" 900})

  2. #'user/new-scores

  3. user=> (merge scores new-scores)

  4. {"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300}

合并并且更新旧值

 
   
   
 
  1. user=> (def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000})

  2. #'user/new-scores

  3. user=> (merge-with + scores new-scores)

  4. {"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924}

排序哈希表

 
   
   
 
  1. user=> (def sm (sorted-map

  2. "Bravo" 204

  3. "Alfa" 35

  4. "Sigma" 99

  5. "Charlie" 100))

  6. {"Alfa" 35, "Bravo" 204, "Charlie" 100, "Sigma" 99}

  7. user=> (keys sm)

  8. ("Alfa" "Bravo" "Charlie" "Sigma")

  9. user=> (vals sm)

  10. (35 204 100 99)

表示应用领域信息

 
   
   
 
  1. (def person

  2. {:first-name "Kelly"

  3. :last-name "Keen"

  4. :age 32

  5. :occupation "Programmer"})

属性取值

 
   
   
 
  1. user=> (get person :occupation)

  2. "Programmer"

  3. user=> (person :occupation)

  4. "Programmer"

  5. user=> (:occupation person)

  6. "Programmer"

  7. user=> (:favorite-color person "beige")

  8. "beige"

更新属性

 
   
   
 
  1. user=> (assoc person :occupation "Baker")

  2. {:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"}

删除属性

 
   
   
 
  1. user=> (dissoc person :age)

  2. {:last-name "Keen", :first-name "Kelly", :occupation "Programmer"}

嵌套实体

 
   
   
 
  1. (def company

  2. {:name "WidgetCo"

  3. :address {:street "123 Main St"

  4. :city "Springfield"

  5. :state "IL"}})

get-in访问内层实体

 
   
   
 
  1. user=> (get-in company [:address :city])

  2. "Springfield"

用assoc-in和update-in来修改内层的实体

 
   
   
 
  1. user=> (assoc-in company [:address :street] "303 Broadway")

  2. {:name "WidgetCo",

  3. :address

  4. {:state "IL",

  5. :city "Springfield",

  6. :street "303 Broadway"}}

记录,或者称作类的实例

 
   
   
 
  1. ;; Define a record structure

  2. (defrecord Person [first-name last-name age occupation])

  3. ;; Positional constructor - generated

  4. (def kelly (->Person "Kelly" "Keen" 32 "Programmer"))

  5. ;; Map constructor - generated

  6. (def kelly (map->Person

  7. {:first-name "Kelly"

  8. :last-name "Keen"

  9. :age 32

  10. :occupation "Programmer"}))

  11. user=> (:occupation kelly)

  12. "Programmer"

Flow Control 控制流

陈述句和表达式

在JAVA中表达式有返回值,而一般的语句没有。在clojure中,不论是表达式,还是一般的语句,它们都有返回值,或者说clojure中只有表达式,一班的语句也是表达式。

流程控制表达式

if语句(else可选)

 
   
   
 
  1. user=> (str "2 is " (if (even? 2) "even" "odd"))

  2. 2 is even

  3. user=> (if (true? false) "impossible!") ;; else is optional

  4. nil

Clojure中,包括0的所有数值都是true,false值只有flase和nil

if和do的组合使用

 
   
   
 
  1. (if (even? 5)

  2. (do (println "even")

  3. true)

  4. (do (println "odd")

  5. false))

when 语句 when是单分支if语句

 
   
   
 
  1. when (neg? x)

  2. (throw (RuntimeException. (str "x must be positive: " x))))

cond语句 想当与switch case

 
   
   
 
  1. (let [x 5]

  2. (cond

  3. (< x 2) "x is less than 2"

  4. (< x 10) "x is less than 10"))

cond and else 当没有cond的表达式为true时,else 执行

 
   
   
 
  1. (let [x 11]

  2. (cond

  3. (< x 2) "x is less than 2"

  4. (< x 10) "x is less than 10"

  5. :else "x is greater than or equal to 10"))

case

不像cond,如果没有值匹配,case会抛出异常

 
   
   
 
  1. user=> (defn foo [x]

  2. (case x

  3. 5 "x is 5"

  4. 10 "x is 10"))

  5. #'user/foo

  6. user=> (foo 10)

  7. x is 10

  8. user=> (foo 11)

  9. IllegalArgumentException No matching clause: 11

case with else-expression

case可以有尾随表达式如果没有测试匹配的话

 
   
   
 
  1. user=> (defn foo [x]

  2. (case x

  3. 5 "x is 5"

  4. 10 "x is 10"

  5. "x isn't 5 or 10"))

  6. #'user/foo

  7. user=> (foo 11)

  8. x isn't 5 or 10

迭代

dotimes 表达式

 
   
   
 
  1. user=> (dotimes [i 3]

  2. (println i))

  3. 0

  4. 1

  5. 2

  6. nil

doseq 表达式

 
   
   
 
  1. user=> (doseq [n (range 3)]

  2. (println n))

  3. 0

  4. 1

  5. 2

  6. nil

doseq 使用多维绑定,相当于嵌套for循环

 
   
   
 
  1. user=> (doseq [letter [:a :b]

  2. number (range 3)] ; list of 0, 1, 2

  3. (prn [letter number]))

  4. [:a 0]

  5. [:a 1]

  6. [:a 2]

  7. [:b 0]

  8. [:b 1]

  9. [:b 2]

  10. nil

循环for语句

for像doseq ,也是多维的

 
   
   
 
  1. user=> (for [letter [:a :b]

  2. number (range 3)] ; list of 0, 1, 2

  3. [letter number])

  4. ([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])

递归

递归和迭代

Clojure提供了recur和sequence抽象

recur是经典的迭代,用户不去控制它,它是低等级的设施

sequence把迭代器看成数值

Reducers把迭代器看作是函数组成,被加在了Clojure1.5版本中

loop and recur

 
   
   
 
  1. (loop [i 0]

  2. (if (< i 10)

  3. (recur (inc i))

  4. i))

defn and recur

 
   
   
 
  1. (defn increase [i]

  2. (if (< i 10)

  3. (recur (inc i))

  4. i))

defn and recur

 
   
   
 
  1. (defn increase [i]

  2. (if (< i 10)

  3. (recur (inc i))

  4. i))

表达式

异常处理

trycatchfinally和java一样

 
   
   
 
  1. (try

  2. (/ 2 1)

  3. (catch ArithmeticException e

  4. "divide by zero")

  5. (finally

  6. (println "cleanup")))

抛出异常

 
   
   
 
  1. (try

  2. (throw (Exception. "something went wrong"))

  3. (catch Exception e (.getMessage e)))

带有Clojure数据的异常

 
   
   
 
  1. (try

  2. (throw (ex-info "There was a problem" {:detail 42}))

  3. (catch Exception e

  4. (prn (:detail (ex-data e)))))

with-open 语句用来减少异常的编写

 
   
   
 
  1. (let [f (clojure.java.io/writer "/tmp/new")]

  2. (try

  3. (.write f "some text")

  4. (finally

  5. (.close f))))

  6. ;; Can be written:

  7. (with-open [f (clojure.java.io/writer "/tmp/new")]

  8. (.write f "some text"))

主要参考文档

https://clojure.org/


关注博爷,不定时更新哦~

以上是关于JVM上小众语言的苦苦挣扎——Clojure Tutorial 从入门到放弃的主要内容,如果未能解决你的问题,请参考以下文章

Clojure 中纸牌游戏的状态

在 mongo 上苦苦挣扎的地理空间查询

Clojure编程pdf

苦苦挣扎的贝宝通知网址

我要学的最后一门编程语言:LISP (Clojure)

JavaClojure快餐教程-运行在JVM上的Lisp方言