如果你注释想试试, 可以看看这个交互式教程.
Hello, 我在在尝试给 ClojureScript 语法写一份简明的教程. ClojureScript 是一门适用于 Web 前端开发的 Lisp 方言, 它被编译到 JavaScript 在浏览器中使用.
ClojureScript 在根本上就和 JavaScript 或者其他编译到 JavaScript 的语言不一样, 比如和 Dart, CoffeeScript 和 TypeScript. 它使用了更强大的语法, 但也更简单. 也有一些其他一些语法之外的区别, 比如说默认采用不可变性, 用来解决状态可变的对象造成"新型 spaghetti code", 同时提供正常的状态管理功能用来实现语言级别的数据绑定.
我相信对于新手来说 ClojureScript 最大的障碍在于他令人感到陌生的语法. 在这份教程里, 我希望能尽可能平常并且简洁解释一遍.
同时, 如果你需要为 ClojureScript 找一个更简单的管理括号的方案, 请了解一下 Parinfer.
这是__literal data(字面量数据)__:
; number(数字)
1.23
; string(字符串)
"foo"
; keyword(关键字, 就像字符串, 但是用于 map 的键)
:foo
; vector(向量, 或者说数组, array)
[:bar 3.14 "hello"]
; map(关联数字, associative array)
{:msg "hello" :pi 3.14 :primes [2 3 5 7 11 13]}
; set(distinct elements, 元素唯一)
#{:bar 3.14 "hello"}
这是__symbolic data(符号化数据)__:
; symbol(符号, 表示一个有名字的值)
foo
; list(链表, 表示一次"调用")
(foo :bar 3.14)
ClojureScript 可以对数据求值从而创建出新的"数值".
-
字面量数据求值之后得到自身, 显然地:
1.23 ; => 1.23 "foo" ; => "foo" [:bar 3.14 "hello"] ; => [:bar 3.14 "hello"]
-
__符号__求值之后得到绑定在上面的数值:
foo ; => 3
-
__list__求值之后得到"调用"的结果.
(+ 1 2 3) ; => 6 (= 1 2) ; => false (if true "y" "n") ; => "y"
如果列表的第一个元素是一个__函数__, 那么其余元素会被求值并传给它 (prefix notation).
; 字符串合并函数
(str "Hello " "World") ; => "Hello World"
; 运算函数
(= a b) ; 等式(true 或者 false)
(+ a b) ; 求和
(- a b) ; 求差
(* a b c) ; 求积
(< a b c) ; true if a < b < c
; 求值步
(+ k (* 2 4)) ; 这里假设 k 求值得到 3
(+ 3 (* 2 4)) ; (* 2 4) 求值得到 8
(+ 3 8) ; (+ 3 8) 求值得到 11
11
如果列表的第一个元素是一个__特殊形式(Special Form)__, 那么其余元素传递给它时不做求值. (总共有 22 的特殊形式.)
(if (= a b c) ; <-- 判断是否 a=b=c
(foo 1) ; <-- 只在 true 的时候求值
(bar 2) ; <-- 只在 false 的时候求值
)
; 定义(define) k 为 3
(def k 3) ; <-- 注意这个 k 不会被求值
; (def 需要 k 这个符号, 而不是它的值)
; 创建一个 greeting 函数
(fn [username] ; <-- 期望参数是 Vector 格式的
(str "Hello " username))
; oops, give the function a name
(def greet (fn [username]
(str "Hello " username)))
(greet "Bob") ; => "Hello Bob"
如果列表的第一个元素是个 Macro, 那么其余元素传给它时不做求值, 但是调用之后的结果是经过求值. 用下面这个图形对它们的区别做一下展示:
求值过程上的区别使得 Macro 可以作为生成代码的函数使用.
比如 defn
这个 Macro 展开成前面例子中遇到的 def
和 fn
:
; 用 defn 这个 Macro 创建一个具名函数
(defn greet [username]
(str "Hello " username))
; defn 这个 Macro 的定义(极度简化版本)
(defmacro defn [name args body]
`(def ~name (fn ~args ~body)))
应用开发者极少需要为他们自己创建 Macros, 但这项功能对于类库开发者来说是不可或缺的, 通过 Macro 他们可以为应用开发者提供语言本身最大的灵活性.
有几个 Macro 字符被用来提供简单的替换, 让语言变得更简洁(不全是 Macro).
; 简单的函数的一种简写
; #(...) => (fn [args] (...))
#(* 3 %) ; => (fn [x] (* 3 x))
#(* 3 (+ %1 %2)) ; => (fn [x y] (* 3 (+ x y)))
要熟练使用 ClojureScript 当然是需要了解更多的语法的. 不过了解了上面这些语法, 你应该可以比较舒服地查看各种例子了, 也可以看出来数据是怎么被求值的, 怎么被到处传递的.
; 在 Console 中打印
(js/console.log "Hello World!")
; 创建局部的绑定(常量)
(let [a (+ 1 2)
b (* 2 3)]
(js/console.log "The value of a is" a)
(js/console.log "The value of b is" b))
; 创建数字的序列
(range 4) ; => (0 1 2 3)
; 生成前 4 个自然数乘以 3 的结果
(map #(* % 3) (range 4)) ;=> (0 3 6 9)
; 序列中的元素个数
(count "Bob") ; => 3
(count [4 5 2 3]) ; => 4
; 从列表中筛选出三个字母组成的名字
(def names ["Bob" "David" "Sue"])
(filter #(= (count %) 3) names) ; => ("Bob" "Sue")
(需要担心圆括号太多而迷失. 所有的现代文本编辑器都可以做到配对的括号的高亮, 甚至自动缩进代码来保证可读性, 就跟其他语言标准的做法一样.)
阅读 Jumping from HTML to ClojureScript 来了解 ClojureScript 语法怎样解决 JavaScript 社区中 JSX 解决的冗长性(verbosity?)和灵活性的问题.
ClojureScript API 手册的语法章节可以找到所有可能的语法形式, 甚至能从源码中找到怎样进行读取和解析.
下面是我在学习 ClojureScript 时候的一些资源和步骤. (大部分介绍 Clojure 的资源对于 ClojureScript 也适用, 因为两者共享了很大的交集.)
- Reading through the ClojureScript Wiki
- Reading the book ClojureScript Up and Running
- Reading the book Clojure Programming
- Doing ClojureScript Koans
- Reading Clojure Style Guide
- Reading Clojure Programming By Example
- Reading Clojure Functional Programming
- Thumbing through Clojure Core API
- Reading ClojureScript - Differences from Clojure - Host Interop for accessing javascript properties like
(.-Infinity js/window)
and functions like(.sqrt js/Math 25)
. - Reading JavaScript to ClojureScript synonyms
- Experimenting in
lein repl
for Clojure REPL. - Experimenting in http://clojurescript.net/ for ClojureScript REPL with a browser context.
- Reading docstrings of functions I encounter with
(doc <funcname>)
in REPL. - Miscellaneous ClojureScript things to know