Clojure 学习笔记梳理

上周在公司前端会分享了 Clojure, 顺带把几天的笔记也整理到一起
具体函数跟其他语言相似, 语法有区别, 建议看文档, 不贴了

总览

Clojure 是一门基于 JVM 的通用编程语言, 像脚本语言, 但也是编译型语言.
同时吸收了大量 Lisp 的特性, 支持函数式编程, S 表达式, Macro 等等.

Clojure is a dynamic programming language that targets the Java Virtual Machine (and the CLR, and JavaScript). It is designed to be a general-purpose language, combining the approachability and interactive development of a scripting language with an efficient and robust infrastructure for multithreaded programming.
Clojure is a compiled language - it compiles directly to JVM bytecode, yet remains completely dynamic. Every feature supported by Clojure is supported at runtime. Clojure provides easy access to the Java frameworks, with optional type hints and type inference, to ensure that calls to Java can avoid reflection.

官网 http://clojure.org/
GitHub 排名 http://githut.info/
社区 https://groups.google.com/forum/#!forum/clojure
论坛 http://clojure-china.org/
QQ: 130107204
书籍 http://clojure.org/books
Cheatsheet http://clojure.org/cheatsheet
文档 https://clojuredocs.org/
入门 http://learnxinyminutes.com/docs/zh-cn/clojure-cn/
教程 http://www.moxleystratton.com/blog/2008/05/01/clojure-tutorial-for-the-non-lisp-programmer/
教程 http://www.braveclojure.com/

特性

  • Lisp 语法, 函数式编程, Macro

  • Persistent Data Structure

  • 强类型, 但是无需写类型声明

  • JavaScript 平台有 ClojureScript, 另外有 .Net 平台 ClojureCLR

(ns 
  ^{:author "Stuart Halloway",
    :doc "Non-core data functions."}
  clojure.data
  (:require [clojure.set :as set]))

(declare diff)

(defn- atom-diff
  "Internal helper for diff."
  [a b]
  (if (= a b) [nil nil a] [a b nil]))

Clojars

https://clojars.org/

Clojure 社区的模块通过 Clojars 管理, 不过注意 Clojars 不是一个命令行工具
主要通过 Leiningen 这个项目管理工具来操作, 也有其他的方案
初次使用, 需要注册 Clojars 账号, 设置 ssh, gpg 等密钥, 略复杂
建议看第三方的教程, 同时结合官方的 Wiki
http://bendb.com/how-to-deploy-to-clojars/
https://github.com/ato/clojars-web/wiki/About

注意 Clojars 上发布的 package 是不能删除的, 可以搜索到 StackOverflow 上的说明
Clojars 上的包应该是 jar 包的形式打包的, 跟 Java 大概类似
由于 Clojure 当中可以调用 Java 代码, 估计 Maven 的包也能用

Leiningen

Clojure 以 jar 包的形式发布, 可以下载 jar 包, 用 java 命令启动 REPL:
http://clojure.org/downloads
http://clojure.org/repl_and_main

java -cp clojure.jar clojure.main

其中 -cp 是指定 classpathclojure.jar 这个包, 运行 -main 函数
但是进行一个项目的话, 需要专门的工具管理, 叫做 Leiningen, 简称 lein
http://leiningen.org/
可以通过脚本安装, Mac 上推荐用 Homebrew 安装

brew install leiningen

lein 可以管理依赖, 启动 REPL 环境, 运行测试, 部署到 Clojars 等等, 看教程
https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md

lein install 安装的依赖, 可以在 ~/.m2/ 目录下找到
运行 lein deploy clojars 可以发布 jar 包到 Clojars 上
当然, 账号密钥需要配置好, gpg 登录配置有点烦, 看下文档
https://github.com/technomancy/leiningen/blob/master/doc/DEPLOY.md#gpg

REPL

Clojure 冷启动非常慢, 开发大部分都在 REPL 当中进行
编写一段代码, 在 REPL 当中重新加载, 然后加载代码到 REPL 当中调用函数测试:

➤➤ lein repl
nREPL server started on port 55414 on host 127.0.0.1 - nrepl://127.0.0.1:55414
REPL-y 0.3.5, nREPL 0.2.6
Clojure 1.6.0
....
user=> (require '[cirru.parser.core :as cirru])
nil
user=> (use 'cirru.parser.core :reload)
nil
user=> (cirru/pare "a" "")
[["a"]]

lein test 命令可以执行 test/ 目录当中的测试代码, 令启动也很慢
所以也可以从 REPL 当中加载文件, 然后直接调用函数测试

(require '[clojure.test :refer [run-tests]])
(require 'your-ns.example-test)
(run-tests 'your-ns.example-test)

http://stackoverflow.com/a/24337705/883571

lein 的主要配置文件是 project.clj, 定义了包的信息和依赖等等
具体看教程上的说明, 注意包名是有规则的, 实际上是 group/package
在填写的时候, 一般如果省略 group 部分, 会自动以 package 替代成 package/package

namespace

Clojure 跟 Java 一样是基于命名空间来加载模块的, 具体看文章的说明
https://blog.8thlight.com/colin-jones/2010/12/05/clojure-libs-and-namespaces-require-use-import-and-ns.html

开发环境当中, src/test/ 是被首先加入到 classpath
文件定义的 namespace 也基于 classpath 查找引用的文件
查看可以用 lein classpath, 也可以直接在 REPL 当中执行:

(println (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))

http://pupeno.com/2008/11/26/printing-the-class-path-in-clojure/

尾递归

Clojure 处理了尾递归, 但不会自动对尾递归进行优化
通常通过 recur 进行尾递归:
https://clojuredocs.org/clojure.core/recur

(loop [i 0]  
  (when (< i 5)    
    (println i)    
    (recur (inc i)); loop i will take this value
))

稍微复杂一些的地方就需要用到 trampoline 函数了, 用法如下:
http://jakemccrary.com/blog/2010/12/06/trampolining-through-mutual-recursion/

(defn my-even? [n]
  (if (zero? n)
    true
    #(my-odd? (dec (Math/abs n)))))

(defn my-odd? [n]
  (if (zero? n)
    false
    #(my-even? (dec (Math/abs n)))))

(trampoline my-even? 1000000)

AST

为了处理 Cirru Sepal 相关部分, 我收集了一些 Clojure AST 相关的资料
AST 是有 Clojure 的 Map 表示的, 但是复杂度相比语法树高了好多
而且 AST 比起 S 表达式也复杂了很多, 所以并不适合生成语法的时候用
另外 ClojureScript 的 AST 似乎比 Clojure 简化了一些, 至少结构上有区别

ClojureTV: Timothy Baldridge - Data All The ASTs http://www.youtube.com/watch?v=KhRQmT22SSg
Clojure AST Quickref http://mkremins.github.io/clojure-ast-ref/
tools.analyzer AST Quickref http://clojure.github.io/tools.analyzer/spec/quickref.html
ClojureScript Next(AST) http://swannodette.github.io/2015/07/29/clojurescript-17/

Demo https://github.com/clojure/tools.analyzer#example-usage

clojure.tools.analyzer.jvm> (require '[clojure.tools.analyzer.ast :as ast])
nil
clojure.tools.analyzer.jvm> (ast/children (analyze '(do 1 2 :foo)))
[{:op   :const,
  :id   0,
  :type :number,
  :val  1,
  :form 1,
  ...}
 {:op   :const,
  :id   1,
  :type :number,
  :val  2,
  :form 2,
  ...}
 {:op   :const,
  :id   3,
  :type :keyword,
  :val  :foo,
  :form :foo,
  ...}]

如果说把 S 表达式作为语法树的话, 也能生成对应的代码(从 AST 反而没找到...)

(clojure.pprint/write '(do (println "Hello") (println "Goodbye")))

(clojure.pprint/write '(do (println "Hello") (println "Goodbye"))
                      :dispatch clojure.pprint/code-dispatch)

同时 pprint 默认打印到终端当中, 通过调用 with-out-str 函数获取字符串:
http://stackoverflow.com/a/18044014/883571

ClojureScript

ClojureScript 之前文章梳理过了, 目前两个 OS X 上的实现是 planckcljs-repl
http://planck.fikesfarm.com/
https://www.npmjs.com/package/cljs-repl
正在完成自举, 冷启动速度非常显著, 几乎是按下回车就启动好的
跟目前整个 Leiningen, Clojars, npm 生态集成的进展, 我还没有弄清楚
考虑 React 在 ClojureScript 社区热度非常高, 后面我会再发文章梳理

文件操作

Clojure 的文件操作似乎很多调用 Java 的, 看网上简单的例子:
http://www.unexpected-vortices.com/clojure/cds/cookbook/files-and-directories.html
另外监视文件系统更新, 我试了 dirwatch(反应慢) 和 hawk, 推荐 hawk:
https://github.com/wkf/hawk

你可能感兴趣的:(clojurescript,clojure)