热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Clojure学习笔记

Clojure官方网站:http:clojure.orgIntelliJ插件地址?https:cursiveclojure.comClojure是在JVM上重新实现的L


Clojure官方网站:http://clojure.org/

IntelliJ 插件地址?https://cursiveclojure.com/

Clojure是在JVM上重新实现的Lisp。

Clojure中的并发工具包和数据结构就是一项新技术。并发抽象层让程序员可以写出更加安全的多线程代码。它和Clojure的序列抽象层(对集合和数据结构上的不同看法)相结合,为开发人员提供了非常强大的工具箱。

Clojure认为值才是真正重要的概念。值可以是数字、字符串、向量、映射、集合,或其他任何东西。一旦创建,Clojure的值就不能再变了,因为它们是不可变的。

Clojure处理状态和内存的方式是它在名字和值之间创建了一个关联关系。这就是绑定,通过特殊形式(def)建立。Clojure中的特殊形式相当于Java的关键字。

(def)的句法是:

(def<名称> <值>)

在REPL中可以输入Clojure代码,也可以执行Clojure函数。它是个交互式环境,而且在前面得出的计算结果不会被丢掉。可以用它做探索式编程。

Clojure中没有可变状态,但有可以改变绑定值的符号。Clojure不是让“内存盒子”中的内容改变,而是让符号绑定到不同的不可变值上。在程序的生命期内,var可以指向不同的值。

可变状态和不同绑定两者之间的区别很微妙,但这个概念很重要,一定要掌握。要记住,可变状态是指盒子中的内容变了,而重新绑定是指在不同时间指向不同的盒子。

“定义函数”宏(defn)。宏是类Lisp语言的关键概念之一,其核心思想是内置结构和普通代码之间的区别应该尽可能小。



  • (defn<符号><值?>):把符号绑到值上(如果有的话)。如果有必要创建与符号对应的var。

  • (fn<名称>?[<参数>*]<表达式>*):返回带有特定参数的函数值,并把它们应用到表达式上。通常跟(def)相结合,变成形式(defn)

  • (if?):如果test的计算结果为true,计算then并产出其结果。否则计算else并产出其结果,当然,前提是else存在。

  • (let[<绑定>*]<表达式>*):给局部名称分配别名值,并隐式定义一个作用域。使得在let作用域内的所有表达式都能获得该别名。

  • (do<表达式>*):按顺序计算表达式的值,并产出最后一个结果

  • (quote<形式>):照原样返回形式(不经计算)。它只能接受一个形式参数,其他的参数全都会被忽略。


  • (var<符号>):返回与符号对应的var(返回一个Clojure JVM对象,不是值)



(quote)以一种特殊的方式处理它的参数。具体来说就是它不会计算参数,所以第一个参数不是函数值也没有问题。

Clojure的向量(vector)跟数组类似,实际上,基本上可以把Clojure列表等同于Java的LinkedList,向量等同于ArrayList。向量可以用方括号表示。

(vec)形式以一个列表为参数,并用这个列表创建向量,而(vector)形式以多个独立符号为参数,并返回包含它们的向量。

函数(nth)有两个参数:集合和索引。它跟Java中的List接口的get()方法类似。可以用在向量和列表上,也可以用在Java集合甚至字符串(字符的集合)上。

Clojure也支持映射(map,相当于Java的HashMap),定义很简单:

{key 1 value1 key2 "value2"}

关于关键字,请记住下面这些知识点:



  • Clojure的关键字是只有一个参数的函数,其参数必须是映射。

  • 在映射上调用这个函数会返回映射里与该关键字函数对应的值。

  • 关键字的使用遵循语法对称性规则,即(my-map:key)和(:key my-map)都是合法的。

  • 关键字作为值使用时返回自身

  • 关键字在使用之前无需声明或def。

  • Clojure中的函数也是值,因此可以放在映射里当键用。

  • 可以用逗号(但没必要)来分隔键值对,因为Clojure会把他们当做空格处理

  • 除了关键字,其他符号也能用在映射里做键,但关键字太好用了们所以我们要特别提出来,你应该把它用在自己的代码中。



除了映射字面值,Clojure还有个(map)函数,它不像(list),(map)函数不会产生映射。而是对集合中的元素轮番应用其中的函数,并用返回的新值建立一个新集合。

Clojure也支持集(set),跟Java的HashSet很像。它的缩写形式是:

#{"apple" "pair" "peach"}

Clojure没有Java里那种意义的操作符,只能用函数:

(add 3 4)

也可以这样写

(+ 3 4)

Clojure的相等形式(相当于Java里的equals()和==)状况稍微有点复杂。Clojure有两个跟相等相关的形式:(=)和(identical?)。注意它们的名字,这全都是因为Clojure不为操作符保留字符。另外,(=)也是等号,而不是赋值符号。

(+)是clojure.core命名空间下的函数,能够接受0到任意数目的参数,假如没有参数,则返回0。

Clojure中有两个值表示逻辑假:false和nil。其他全是逻辑真。

对于VM来说,Clojure函数是实现了clojure.lang.IFn的对象。

Schwartzian转换的基本思想是基于向量中的元素的某些属性對元素进行排序。排序所依据的属性值是通过在元素上调用键控函数确定的。

读取器宏

‘ ? ? 引号,展开为(quote),产生不进行计算的形式

; ? ? 注释,标记知道行尾的注释,就像Java里的//

\ ? ?字符,产生一个字面字符

@ ?解引用,展开为(deref),接受var对象并返回对象中的值(跟(var)形式的操作相反)。在事务内存上下文中海油其他含义。

^ ? 元数据,将一个元数据映射到附加对象上。

` ? ? 语法引用,经常用在宏定义中的引号形式,不太适合初学者

# ? ?派发,有几种不同的子形式

#‘ ? 展开为(var)

#{} ?创建一个集字面值

#() ?创建匿名函数字面值,用在哪些使用(fn)太啰嗦的地方

#_ ? 跳过下一个形式。可以用#_(....多行...)来创建多行注释

#"<模式>" ?创建一个正则表达式(作为java.util.regex.Pattern对象)

在自身的环境内”封装“一些值的函数称为闭包。

序列是Clojure的创新,实际上,用Clojure编程主要就是要思考怎么用序列解决特定的问题。

Clojure与Java中的集合与迭代器相对应的核心概念是序列(sequence),或者简称seq。它基本上是把两个Java类的一些特性集成到一个概念里。这样做的动机有三个:



  • 更强健的迭代器,特别是对于多路算法而言。

  • 不可变能力,可以安全地在函数间传递序列。

  • 实现了懒序列的可能性。


(seq ) ? ? ? 返回一个序列,作为所操作集合的”视图“


(frist ) ? ? ? 返回集合的第一个元素,如有必要,先在其上调用(seq)。如果集合为nil,则返回nil

(rest ) ? ? ? ?返回从集合中去掉第一个元素后的到的新序列。如果集合为nil,则返回nil

(seq? ) ? ? ? ? ?如果o是一个序列,则返回true(也就是实现了ISeq)

(cons ) ?在集合前面增加新元素,并返回由此得到的序列

(conj ) ? 返回将新元素加到合适的一端(向量的尾端和列表的头)的新集合

(every? ) 如果(pred-fn)对集合中的每个元素都返回逻辑真,则返回true

?

列表是自身的序列,而向量不是。因此从理论上来来说,不能在向量上调用(rest)。而实际上是可以的,因为(rest)在操作向量之前先在其上调用了(seq)。这是序列结构中普遍存在的属性:很多序列函数都会接受比序列更通用的对象,并在开始之前先调用(seq)。

Clojure函数有一个强大的特性,它天生就具备参数数量可变的能力,有时称为函数的变元(arity)。参数数量可变的函数称为变参函数(variadic)。

(defn const-fun-arity1

? ? ? ([] 0)

? ? ? ([x] 1)

? ? ? ([x & more] "more"))中的&表明这是该函数的变参版本。

(defn lenStr [y] (.length (.toString y)))中用到了形式(.toString)和(.length),这都是Java方法,它们是在Clojure对象上调用的。符号开始部分的句号,表示运行时应该在下一个参数上调用该名称的方法,底层是用(.)宏实现的。

所有用(def)或它的变体定义的Clojure值都被放在clojure.lang.Var实例中,它可以承载任何java.lang.Object,所以任何可以在java.lang.Object调用的方法都可以在Clojure值上调用。另外一些跟Java交互的形式是用来调用静态方法的。

(System/getProperty "java.vm.version")

Clojure调用的本质

Clojure中的函数调用实际上是JVM的方法调用。JVM不能保证像Lisp语言(特别是Scheme)通常做的那样优化掉尾递归。JVM上一些其他的Lisp方言觉得它们需要真正的尾递归,因此不准备把Lisp函数调用跟JVM方法调用完全等同起来。而Clojure完全以JVM为平台,甚至不惜违背通常的Lisp实践。

如果你想创建一个新的Java对象并在Clojure中操作它,用(new)形式就可以轻松做到。它还有个备选的缩写形式,在类名之后跟一个句号,可以归结为(.)宏的另一个用法。

Clojure有一个强大的宏(proxy),可以用它来创建扩展Java类(或实现接口)的Clojure对象。

(proxy)的一般形式是:

(proxy [<超类/接口>] [ <命名函数的实现>+])

第一个向量参数是这个代理类应该实现的接口。如果这个代理还要扩展Java类(如果可以的话,当然,只能扩展一个Java类),这个类名必须是向量中的第一个元素。

第二个向量参数包含传给超类构造方法的参数。这个向量经常是空的,并且如果(proxy)形式只是实现Java接口的话,那它肯定是空的。

这两个参数之后是一个或多个表示单个方法实现的形式,按接口的要求或超类指定的实现。

Clojure的类型系统跟Java高度一致。Clojure数据结构全是真正的Java集合,都实现了对应接口的所有必须部分。因为接口的可选部分一般都跟修改数据结构有关,而Clojure数据结构不可变,所以一般都没实现。

import clojure.lang.ISeq;
import clojure.lang.StringSeq;
/**
* Created with IntelliJ IDEA.
* User: billlee
* Date: 2015/1/13
* Time: 16:04
* To change this template use File | Settings | File Templates.
*/
public class ClojureDemo {
public static void main(String[] args) {
ISeq seq= StringSeq.create("football");
while(seq!=null)
{
Object first=seq.first();
System.out.println("Seq:"+seq+" ;first:"+first);
seq=seq.next();
}
}
}
?


Clojure的指导思想是默认把线程彼此隔开,这种实现并发安全的办法由来已久。假定”没有共享资源“的基线和采用不可变值使Clojure避开了很多Java所面临的问题,从而可以专注于为并发编程安全地共享状态的方法。

实际上,Clojure用不同的方法实现了不同的并发模型:未来式(future)、并行调用(pcall)、引用形式(ref)和代理(agent)。

第一个也是最明显的一个状态分享办法就是不分享。实际上,我们一直使用的Clojure结构var本质上是不可以共享的。如果两个不同线程继承了名字相同的var,并在线程里重新绑定了它,那绑定只在这些线程内部可见,绝不可能被其他线程共享。

有个简单的函数是(pcalls),可以接受数量可变的零参函数,让它们并发执行。它们在运行时管理的线程池上执行,并返回一个懒序列结果。试图访问序列中的任何还没完成的元素会导致访问线程被阻塞。

ref是Clojure在线程间共享状态的办法。它们基于运行时提供的一个模型,在这个模型中,状态的改变要能被多个线程见到。该模型在符号和值之间引入了一个额外的中间层。也就是说,符号绑定到值得引用上,而不是绑定到值上。这个系统基本上是事务化的,并且由Clojure运行时进行协调。

代理是Clojure中异步的、面向对象消息的并发原语。Clojure代理不是共享状态,而是属于另外一个线程的一点儿状态,但它会从另外一个线程中接收消息(以函数的形式)。

应用到代理上的函数在代理的线程上运行。这个线程是由Clojure运行时管理的,在一个程序员通常无法访问的线程池里。运行时还会保证代理中那些可以被外界看到的值是孤立的和原子的。这就是说用户代码只会见到状态修改之前或者之后的代理值。

涉及到的部分练习的代码:

(ns com.clojure.ClojureDemo)
(def hello (fn [] "Hello world"))
(hello)
;使用java中的toString()结合length()获取字符串长度
(defn lenStr [y] (.length (.toString y)))
(defn schwartz [x f]
(map #(nth %1 0)
(sort-by #(nth %1 1)
(map #(let [w %1] (list w (f w)))x))))
(schwartz ["sads" "21" "ssssewe" "22323" "223" "s"] lenStr)
‘(1 2 3 4 5)
(quote (1 23 45 32 545))
(vector 1 2 3)
(vec ‘(1 2 3 4 5))
[1 2 3 4 5 4 5 2]
["ssa" 2 "dsadsa" "321" 221]
(nth ‘(1 2 3 "433" "rewr" "e33") 4)
(def foo {"aaa" "111" "bbb" 22222})
(foo "aaa")
(foo "bbb")
(def martijn {:name "Martijn Verburg",:city "London",:area "Highbury"})
(:name martijn)
(:city martijn)
(def ben {:name "ben Evans",:city "London",:area "Holloway"})
(def authors [ben martijn])
(map ( fn [y] (:name y)))
(map ( fn [y] (:name y)) authors)
(+ 3 4)
(defn add [x y] (+ x y))
(+ 2 3 4 56)
(def list-int ‘(1 2 3 4))
(def vect-int (vec list-int))
(identical? list-int vect-int)
(defn const-fun1 [y] 1)
(defn iden-fun [y] y)
(defn list-maker-fun [x f]
(map (fn [z] (let [w z]
(list w (f w))))x))
(list-maker-fun ["a"] const-fun1)
(list-maker-fun ["a" "b"] const-fun1)
(list-maker-fun [3 4 65] iden-fun)
(schwartz [33 452 53 42 555] iden-fun)
(defn like-for [counter]
(loop [ctr counter]
(println ctr)
(if ( (recur (inc ctr))
ctr)))
(like-for 100)
(like-for 60)
(like-for 6)
(like-for 1)
(defn like-for [counter]
(loop [ctr counter]
(println ctr)
(if ( (recur (inc ctr))
ctr)))
(like-for 99)
(like-for 1)
(defn adder [constToAdd] #(+ constToAdd %1))
(def plus2 (adder 2))
(plus2 222)
(def plus100 (adder 100))
(plus100 22)
(rest ‘(1 2 3))
(first ‘(1 2 43 54))
(rest [1 23 45])
(seq ())
(seq [])
(seq ‘(1 2 3 4))
(seq [22 1 "re" "fdsw" "fdsf" "fdsffd"])
(cons 1 [2 34 43])
(defn next-big-n [n] (let [new-val (+ 1 n)]
(lazy-seq (
cons new-val (next-big-n new-val)
))))
(defn natural-k [k]
(concat [k] (next-big-n k)))
(take 10 (natural-k 3))
(defn const-fun-arity1
([] 1)
([x] 1)
([x & more] 1))
(const-fun-arity1 1)
(const-fun-arity1 1 2)
(const-fun-arity1 1 2 3)
(defn const-fun-arity1
([] 0)
([x] 1)
([x & more] "more"))
(const-fun-arity1)
(const-fun-arity1 1 )
(const-fun-arity1 1 2 3)
(System/getProperty "java.vm.version")
(import ‘(java.util.concurrent CountDownLatch LinkedBlockingQueue))
(def cdl (new CountDownLatch 2))
(def lbq (LinkedBlockingQueue.))
(.getClass "test")
(.getClass 2.3)
(.getClass [12 34 534])
(.getClass ‘(1 2 3 4))
(.getClass (fn [] "Hello world"))
(import ‘(java.util.concurrent Executors LinkedBlockingQueue TimeUnit))
(def stpe (Executors/newScheduledThreadPool 2))
(def lbq (LinkedBlockingQueue.))
(def msgRdr (proxy [Runnable] []
(run [] (.toString (.poll lbq)))))
(def rdrHndl (.scheduleAtFixedRate stpe msgRdr 10 10 TimeUnit/MILLISECONDS))
(import ‘(java.util ArrayList LinkedList))
(.getClass (.iterator (ArrayList.)))
(.getClass (.iterator (LinkedList.)))
(def simple-future (future (do (println "hello world Line0")
(Thread/sleep 10000)
(println "we are the world Line1")
(Thread/sleep 10000)
(println "do the best Line 2"))))
(defn wait-with-for [limit]
(let [counter 1]
(loop [ctr counter]
(Thread/sleep 500)
(println (str "Ctr=" ctr))
(if ( (recur (inc ctr))
ctr))))
(defn wait-1 [] (wait-with-for 1))
(defn wait-2 [] (wait-with-for 2))
(defn wait-3 [] (wait-with-for 3))
(def wait-seq (pcalls wait-1 wait-2 wait-3))
(first wait-seq)
(first (next wait-seq))
(defn make-new-acc [account-name opening-balance]
{:name account-name :bal opening-balance})
(defn loop-and-debit [account]
(loop [acc account]
(let [balance (:bal acc) my-name (:name acc)]
(Thread/sleep 1)
(if (> balance 0)
(recur (make-new-acc my-name (dec balance)))
acc))))
(loop-and-debit (make-new-acc "Ben" 600))
(defn make-new-acc [account-name opening-balance]
(ref {:name account-name :bal opening-balance}))
(defn alter-acc [acc new-name new-balance]
(assoc acc :bal new-balance :name new-name))
(defn loop-and-debit [account]
(loop [acc account]
(let [balance (:bal @acc)
my-name (:name @acc)]
(Thread/sleep 1)
(if (> balance 0)
(recur (dosync (alter acc alter-acc my-name (dec balance)) acc))
acc)
)))
(def my-acc (make-new-acc "Ben" 500))
(defn my-loop [] (let [the-acc my-acc]
(loop-and-debit the-acc)))
(pcalls my-loop my-loop my-loop my-loop my-loop)
(defn wait-and-log [coll str-to-add]
(do (Thread/sleep 10000)
(let [my-coll (conj coll str-to-add)]
(Thread/sleep 10000)
(conj my-coll str-to-add))))
(def str-coll (agent []))
(send str-coll wait-and-log "some str")
@str-coll
?


?

?


推荐阅读
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
author-avatar
彼岸花2011的冬天_290
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有