clojure的语法糖
语法糖很多, 就是奔这个“懒” 来用clj的.
但是,在常见的书里(《Clojure编程》《Clojure编程乐趣2》)都对很多基本语法,用法都介绍不全, 不细。看书看得很累。
比如《Clojure编程》里 第1章介绍了各种基本语法,但是没有介绍for :when,然后在P138 直接用了
(for [dx [- ] dy [- ] :when (not= dx dy)]
[(+ dx x) (+ dy y)])
也没有详细解释。也许不是一个作者写的吧。
对我这种特别笨的人来说,一下就看不懂了,感觉还是有很多坑。
必须对照https://clojuredocs.org/ + 自己尝试。
心得
写在前面。
在已经学过《SICP》和用过scheme和hy的基础上,感觉Clojure确实有自己的特点。仍然必须先让自己进入什么都不会的状态,耐心去学,才能真学进去,不然,小问题就能卡住半天。还不容易找到答案。
1学Clojure就像是学汉字。要从边旁部首学起。
和只有7个关键字scheme不同,Clojure系各种语法糖特别多,而且因为引入的不同的集合#{} {} [] '(),客观上带来了很多scheme里没有的“转型"问题。
如果不从细微出入手,别说读懂句子,就连每个词,每个字,甚至每个部首,都不认识。>_<!
开始学偏旁部首,认字过程 学的非常慢,都是非常细节,功能单一但是非常细腻的小函数,小动词。
然后用这些部首自己去造字组词(DSL),然后再去用DSL组词造句表达。
2 数据结构、类型+远多于其他语言,利弊互现。
scheme里一切皆列表 list() 顶多知道cons 也就差不多了。
数据类型,顶多知道有symbol类型 就已经可以写出个解释器了。
hy/py 也就是py里的set {} list [] map {k:v} 然后tuple() generator () ...大概也就差不多了
但是Clojure的数据结构因为 不可变/可变 的分野,几乎等于类型数量×2。
即使不考虑这一点,其他性能、实现方面的复杂度,工程上和JVM的兼容等等
Clojure放弃了scheme里一切皆括号()的写法, 引入{} [] 。客观上放眼望去,不是无尽的括号,可读性增强。
但是弊端当然就是学习、写码时负载增加。
典型体现在:
1必须认真理解() 和 [] 的差异,包括conj行为差异,遍历性能差异等等等。
2 必须记住(for []) 返回的是个list () 需要别的类型,还要转。
想直接返回1个list 是麻烦的,这样写是不行的
[for [l [1 2 3]] (* l 2)]
因为外层不能是方括号
而在py里 list 表达式根深蒂固。而hy里,我们有(lfor ) 也能直接返回list。个人认为hy的lfor方案很好。
3 不得不依赖 引入各种转型 比如filter 之外,还搞出了filterv filterm 分别对应返回结果是 vector map 的情况。
个人感觉,这里还不如py优雅。
再比如,合并([] [] [])-> [] nested vector成为1个vector
大概只能
(vec (apply concat nested_coll))
虽然把这一行自己写成宏也没什么,但是总感觉怪异,不优雅。
-----------------------------------------分割线---------------------------------------------------------------
下面全都是我记录下在py hy里没有的,我这种菜鸟新手不太习惯的写法。陆续增加。
for和doseq
上面这句里, for可以同时循环dx dy 2个变量 相当于2个for嵌套
后面的:when 保证只有when成立时再执行body
注意 如果执行函数,要用这个,而不是for
(doseq [x [ ]]
(println "aa")
)
这里doseq 换成for 里面的println 是不会被执行的。这里区分有点细,和py的list表达式和 hy区别
not=
可以接受多个参数,来判断连续相等。相当于+ - * / 连加
set 作为谓词
因为集合可以作为函数,所以当然可以作为谓词函数
(if (#{ }, ) "真" "假")
这个集合#{3 2}完全可以定义为1个谓词函数,就像《Clojure编程》P143一样,在body里把集合定义成谓词,然后在外面简单把集合传进来
内部
(if (survive? ) "真" "假")
其实survive?这个看起来是谓词函数的东西,只是个简单的#{2 3}
mapcat
(mapcat f p) 等于(concat (map f p)) 把map的结果连接起来
apply map
从scheme,py pandas里就都有。但总记不住
(apply f [p1 p2 p3])
(map f [p1 p2 p3])
相同点: 都是紧跟1个函数。后面是一串参数。
不同点:map: 是“映射”,所以返回((f p1) (f p2) (f p2))
apply 的f 是接受3个参数的, 返回(f p1 p2 p3) 比如
(apply + [ ])
=>6
(map #(* % ) [ ])
=>( )
apply还有一个重要作用就是“脱括号”的作用。比如 当参数是[[], []] 这种时,想用concat 把nest2个vector连接起来,用
(apply concat [vec1, vec2])
相当于(concat vec1 vec2)
——微吐槽:不如py的 list extend() 或者*解引用 itertools.chain(*[vec1 vec2])
disj
把元素添加进set
user=> (disj #{ }) ; disjoin nothing
#{ } user=> (disj #{ } ) ; disjoin
#{ } user=> (disj #{ } ) ; disjoin non-existent item
#{ } user=> (disj #{ } ) ; disjoin several items at once
#{}
juxt
和map相同点:都是元素级操作
不同点:
map:1个函数,多个参数;
juxt多个函数,1个参数。
((juxt a b c) x) => [(a x) (b x) (c x)]
constantly
返回1个函数,这个函数可以接收任意数量的参数,但永远返回初始给定的返回值
user=> (def boring (constantly ))
#'user/boring user=> (boring ) user=> (boring) user=> (boring "Is anybody home?")
Zipper
来自《FUNCTIONAL PEARL》中的概念。
以不可变的方式遍历层次数据结构(如嵌套的vector XML 等等等)。
参考
http://josf.info/blog/2014/03/21/getting-acquainted-with-clojure-zippers/
http://www.thattommyhall.com/2013/08/23/genetic-programming-in-clojure-with-zippers/
(zipper branch? children make-node root) ;;Creates a new zipper structure. ;;branch? is a fn that, given a node, returns true if can have ;; children, even if it currently doesn't. ;; children is a fn that, given a branch node, returns a seq of its ;; children. ;; make-node is a fn that, given an existing node and a seq of ;; children, returns a new branch node with the supplied children. ;; root is the root node.
concat和into
类似但不同。主要是返回值
into
(into [ ] [ ])
=>[ ]
concat
(concat [ ] [ ])
=>( )
选入进新的collection
user=> (into (sorted-map) [ [:a ] [:c ] [:b ] ] )
{:a , :b , :c }
user=> (into (sorted-map) [ {:a } {:c } {:b } ] )
{:a , :b , :c } ; When maps are the input source, they convert into an unordered sequence
; of key-value pairs, encoded as -vectors
user=> (into [] { , })
[[ ] [ ]]
user=> (into () '(1 2 3))
( ) ; This does not happen for a vector, however, due to the behavior of conj:
user=> (into [ ] '(4 5 6))
[ ]
merge-with
多个map规约为1个map 把每个map的 value 按 f进行规约
(merge-with f & maps)
(merge-with into
{"Lisp" ["Common Lisp" "Clojure"]
"ML" ["Caml" "Objective Caml"]}
{"Lisp" ["Scheme"]
"ML" ["Standard ML"]})
;;=> {"Lisp" ["Common Lisp" "Clojure" "Scheme"], "ML" ["Caml" "Objective Caml" "Standard ML"]}
(merge-with +
{:a :b }
{:a :b :c }
{:a :b :c }
{:a }
{:c :d }) ;;=> {:d , :c , :a , :b }
assoc
可以把多个key value 放进一个map里, 把map作为可变对象
(assoc map key val)
(assoc map key val & kvs) (assoc {} :key1 "value" :key2 "another value") ;;=> {:key2 "another value", :key1 "value"}
conj
把多个element 加入conj到coll。 返回新coll,不可变对象
注意,只保证加入。顺序性各有不同:
map和set没有顺序不谈
[] vector在末尾添加
'() 列表在头部添加
(conj coll x) (conj coll x & xs) ;; notice that conjoining to a vector is done at the end
(conj [ ] )
;;=> [ ] ;; notice conjoining to a list is done at the beginning
(conj '(1 2 3) 4)
;;=> ( ) (conj ["a" "b" "c"] "d")
;;=> ["a" "b" "c" "d"] ;; conjoining multiple items is done in order
(conj [ ] )
;;=> [ ] (conj '(1 2) )
;;=> ( 3 ) (conj [[ ] [ ]] [ ])
;;=> [[ ] [ ] [ ]]
condp
接受1个双参数的pred函数,然后跟1个作为第2个参数,
后面跟的列表,是第1个参数的列表,和值的列表。
(println (condp #(% %) :foo
string? "it's a string"
keyword? "it's a keyword"
symbol? "it's a symbol"
fn? "it's a function"
"something else!")
)
=>it's a keyword
vector和vec
vector在外面加一层方括号[]
vec把外层转换成[]
(vector '(1 2 3))
=>[( )] (vec '(1 2 3))
=>[ ]
超强的let
es6里let是和const并列的;let定义可变变量,const定义不可变变量。
Clojure里切不可望文生义。let远不止是用于 创建临时变量 和es6 ts里 的let感觉完全不一样。
Clojure里 let和def的区别是这样分的
let创建 内部、不可变变量
def创建 namespace、可变变量
let的作用起码有3条:
1 创建内部、不可变变量。
2 解构赋值 destruct
3 赋值有顺序,后面的语句可以调用前面的,所以可以在let中放置顺序执行语句
(let [var1 v1
var2 (f1 var1)
]
(f2 var1 var2)
)
这样,在let 的方括号里 [] 把var1 var2 按顺序赋值好,其中var2 的赋值还用到了刚刚赋值的var1。
最后let的body里,只表现最终返回结果就好了。
可以认为,多用let少用do就对了
(let [数值准备]
(返回的结果)
)
只把最后1次计算 方在body里,或者返回 一个map{} 或者list [], 可以突显返回值。
这种涉及思路,相当于把全部内部变量全都在头部声明、赋值1次。只用1个let 避免其他语言里到处const var好多次。
而且用机制保证了赋值后的不可变性。
最后,让body聚焦于值的返回
确实是很强大,很有力的表达方式,一定要掌握。
fn内部的letfn
和py/hy不同 defn 定义的一律是namespace级别的。所以如果defn内部嵌入1个defn定义,则外部执行2遍,将导致内部defn也更新。(因为def可以覆盖),但本意内部的defn是外部不可见的局部函数。这时就用letfn
简单说,任何涉及fn内部的东西,都要显式用let/letfn来构造。
在Clojure里, 局部/ns 的区别必须显式声明!
for中的:when和:while
(for [x (range ) :when (not= x )] x)
; =>( ) (for [x (range ) :while (not= x )] x)
; => ( )
:when会遍历整个循环,条件不满足的不执行 类似 continue
:while 遇到第一次满足条件的地方,就会退出。类似break
filter
只相当于for中的:when,不能在第1次满足是停下:下面3种写法是等效的
(for [x (range ) :when (not= x )] x)
(filter #(not= % ) (for [x (range )] x))
(filter #(not= % ) (range ))
后两种显然最后更简单。
不同点:
写在 :when和:while 里面的不是函数,是表达式
如何选择:
如果过滤条件简单,就是个简单表达式,那么用for更合适;
如果过滤条件是外部定义的predicator函数,那么用filter合适;
个人倾向,尽量使用filter/filterv/filterm,不显式使用for和循环,尤其coll已经是外部赋值好的时候。 和需要定制返回值类型时
for的好处是,可以在body里对返回值进行定制。
字符串分割
#"" 是正则表达式 如果只用1个分割符,就这样
(str/split "*-D1R2" #"-")
等价于py里
"*-D1R2".split("-")
identity和constantly
都是用于创建简单函数:
user=> (def boring (constantly 10))
#'user/boring user=> (boring ) user=> (boring) user=> (boring "Is anybody home?")
loop recur的坑
注意recur里,各循环变量还是不可变的。
(loop [iter
acc ]
(if (> iter )
(println acc)
(recur (inc iter) (+ acc iter)))) ;; =>
;; sum from to
每次recur (inc i) ( XX i)
(loop [i ]
(let [res1 ((get fns-cmp i) env1 env2)]
(if (or (>= i n) (not= res1))
res1
(recur (inc i))
)
)
)
update-in
(def users [{:name "James" :age } {:name "John" :age }])
#'user/users ;; similar to assoc-in but does not simply replace the item.
;; the specified function is performed on the matching item.
;; here the age of the second (index ) user is incremented.
(update-in users [1 :age] inc)
;;=> [{:name "James", :age } {:name "John", :age }]
查询条件是nest指向1个记录的,所以如果想更新多于1个值,需要assoc
;;You can use update-in in a nested map too, in order to update more than
;;one value: (def m {: {:value , :active false}, : {:value , :active false}}) (update-in m [:] assoc :value 1 :active true)
;;=>{: {:value , :active true}, : {:value , :active false}}
clojure的语法糖的更多相关文章
- 探索C#之6.0语法糖剖析
阅读目录: 自动属性默认初始化 自动只读属性默认初始化 表达式为主体的函数 表达式为主体的属性(赋值) 静态类导入 Null条件运算符 字符串格式化 索引初始化 异常过滤器when catch和fin ...
- C#语法糖大汇总
首先需要声明的是"语法糖"这个词绝非贬义词,它可以给我带来方便,是一种便捷的写法,编译器会帮我们做转换:而且可以提高开发编码的效率,在性能上也不会带来损失.这让java开发人员羡慕 ...
- 看看C# 6.0中那些语法糖都干了些什么(终结篇)
终于写到终结篇了,整个人像在梦游一样,说完这一篇我得继续写我的js系列啦. 一:带索引的对象初始化器 还是按照江湖老规矩,先扒开看看到底是个什么玩意. 1 static void Main(strin ...
- 看看C# 6.0中那些语法糖都干了些什么(中篇)
接着上篇继续扯,其实语法糖也不是什么坏事,第一个就是吃不吃随你,第二个就是最好要知道这些糖在底层都做了些什么,不过有一点 叫眼见为实,这样才能安心的使用,一口气上五楼,不费劲. 一:字符串嵌入值 我想 ...
- 看看C# 6.0中那些语法糖都干了些什么(上篇)
今天没事,就下了个vs2015 preview,前段时间园子里面也在热炒这些新的语法糖,这里我们就来看看到底都会生成些什么样的IL? 一:自动初始化属性 确实这个比之前的版本简化了一下,不过你肯定很好 ...
- C# 6.0新特性---语法糖
转载:http://www.cnblogs.com/TianFang/p/3928172.html 所谓语法糖就是在编译器里写做文章,达到简化代码书写的目的,要慎重使用,省略过多不易理解. NULL检 ...
- C#语法糖,让编程更具乐趣
一.什么是语法糖 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法 ...
- Java语法糖4:内部类
内部类 最后一个语法糖,讲讲内部类,内部类指的就是在一个类的内部再定义一个类. 内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功 ...
- Java语法糖1:可变长度参数以及foreach循环原理
语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...
随机推荐
- python 包的概念
包的概念 包的概念: 在python中包即使模块,是一系列功能的集合体, 为什么要用包? 提高开发效率 如何用包 import ... from ... import ..... 如何认识它就是一包 ...
- Idea 竖选文本、竖向选择、横向纵向选择文本代码
在使用Idea的时候,可能需要在相同类型的文字中增加数据,所以Idea提供一种列式选择方式,提高开发的效率. 如果需要使用,我们可以选中代码,右键单击,在弹出的菜单中选中[Column Selecti ...
- Spark中的术语图解总结
参考:http://www.raincent.com/content-85-11052-1.html 1.Application:Spark应用程序 指的是用户编写的Spark应用程序,包含了Driv ...
- POJ - 2421 Constructing Roads(最小生成树&并查集
There are N villages, which are numbered from 1 to N, and you should build some roads such that ever ...
- 初识JavaScript(二)
初识JavaScript(二) 我从上一篇<初识JavaScript(一)>知道和认识JavaScript的词法结构,也开始慢慢接触到了JavaScript的使用方法,是必须按照JavaS ...
- python-day13(正式学习)
闭包函数 闭包 闭包:闭是封闭(函数内部函数),包是包含(该内部函数对外部作用域而非全局作用域的变量的引用).闭包指的是:函数内部函数对外部作用域而非全局作用域的引用. 额...这里提示一下闭包!=自 ...
- POJ 3249 Test for Job (拓扑排序+DP)
POJ 3249 Test for Job (拓扑排序+DP) <题目链接> 题目大意: 给定一个有向图(图不一定连通),每个点都有点权(可能为负),让你求出从源点走向汇点的路径上的最大点 ...
- luogu P4076 [SDOI2016]墙上的句子
luogu loj 题意看了我半天(逃 (应该是我语文太差了) 题意是要确定每一行和每一列的看单词的顺序,使得同时正着出现和反着出现在里面的单词数量最少,每行和每列的性质是这一行所有单词反过来的单词要 ...
- 剑指offer-7:调整数组顺序使奇数位于偶数前面
一.相对位置可以改变 1.题目 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分. 2.分析 不考虑相对位置,可以类比快排,用左右 ...
- Flask开发系列之Web表单
Flask开发系列之Web表单 简单示例 from flask import Flask, request, render_template app = Flask(__name__) @app.ro ...