Skip to content

Latest commit

 

History

History
224 lines (159 loc) · 7.9 KB

README.zh.md

File metadata and controls

224 lines (159 loc) · 7.9 KB

cljs

如果你注释想试试, 可以看看这个交互式教程.

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. 字面量数据求值之后得到自身, 显然地:

    1.23                 ; => 1.23
    "foo"                ; => "foo"
    [:bar 3.14 "hello"]  ; => [:bar 3.14 "hello"]
  2. __符号__求值之后得到绑定在上面的数值:

    foo                  ; => 3
  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, 那么其余元素传给它时不做求值, 但是调用之后的结果是经过求值. 用下面这个图形对它们的区别做一下展示:

calls

求值过程上的区别使得 Macro 可以作为生成代码的函数使用. 比如 defn 这个 Macro 展开成前面例子中遇到的 deffn:

; 用 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")

(需要担心圆括号太多而迷失. 所有的现代文本编辑器都可以做到配对的括号的高亮, 甚至自动缩进代码来保证可读性, 就跟其他语言标准的做法一样.)

HTML 模板语法中的 ClojureScript 对比 JSX

阅读 Jumping from HTML to ClojureScript 来了解 ClojureScript 语法怎样解决 JavaScript 社区中 JSX 解决的冗长性(verbosity?)和灵活性的问题.

完整的手册

ClojureScript API 手册的语法章节可以找到所有可能的语法形式, 甚至能从源码中找到怎样进行读取和解析.

实用资源

下面是我在学习 ClojureScript 时候的一些资源和步骤. (大部分介绍 Clojure 的资源对于 ClojureScript 也适用, 因为两者共享了很大的交集.)

  1. Reading through the ClojureScript Wiki
  2. Reading the book ClojureScript Up and Running
  3. Reading the book Clojure Programming
  4. Doing ClojureScript Koans
  5. Reading Clojure Style Guide
  6. Reading Clojure Programming By Example
  7. Reading Clojure Functional Programming
  8. Thumbing through Clojure Core API
  9. Reading ClojureScript - Differences from Clojure - Host Interop for accessing javascript properties like (.-Infinity js/window) and functions like (.sqrt js/Math 25).
  10. Reading JavaScript to ClojureScript synonyms
  11. Experimenting in lein repl for Clojure REPL.
  12. Experimenting in http://clojurescript.net/ for ClojureScript REPL with a browser context.
  13. Reading docstrings of functions I encounter with (doc <funcname>) in REPL.
  14. Miscellaneous ClojureScript things to know