`
songry
  • 浏览: 84531 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Clojure-JVM上的函数式编程语言(4)程序流控制 作者: R. Mark Volkmann

阅读更多

 原帖地址:http://java.ociweb.com/mark/clojure/article.html#ConditionalProcessing

 作者:R. Mark Volkmann

 译者:RoySong

 

程序流控制

条件判断

    特殊form if会检验一个条件,然后根据检验结果来决定执行两个表达式中的哪一个。

它的语法是(if condition then-expr else-expr ),其中的else部分( else-expr )是可选的。

如果then部分或者else部分需要不止一个表达式,则采用特殊form do来将它们包装成一个表达式。

例子如下:

(import '(java.util Calendar GregorianCalendar))
(let [gc (GregorianCalendar.)
      day-of-week (.get gc Calendar/DAY_OF_WEEK)
      is-weekend (or (= day-of-week Calendar/SATURDAY) (= day-of-week Calendar/SUNDAY))]
  (if is-weekend
    (println "play")
    (do (println "work")
        (println "sleep"))))

 

    when和 when-not在只需要一种分支的情况下可以替代if,它们的方法体中可以包含任意数目的表达式,而不用do来包装。

例子如下:

(when is-weekend (println "play"))
(when-not is-weekend (println "work") (println "sleep"))

 

    if-let宏将一个值绑定到某个符号上,然后根据这个值的逻辑真或者假(在 "Predicates "这一节中解释

来决定执行哪个表达式。下面的代码会打印出在队列中等待的第一个用户的名字,如果队列中没有等待的用户,

则打印出"no waiting"。

(defn process-next [waiting-line]
  (if-let [name (first waiting-line)]
    (println name "is next")
    (println "no waiting")))

(process-next '("Jeremy" "Amanda" "Tami")) ; -> Jeremy is next
(process-next '()) ; -> no waiting
 

    when-let宏跟if-let宏很类似,它们的不同和if跟when的不同方式一样。

when-let不支持else部分,并且when部分可以包含任意数目的表达式。例子如下:

(defn summarize
  "prints the first item in a collection
  followed by a period for each remaining item"
  [coll]
  ; Execute the when-let body only if the collection isn't empty.
  (when-let [head (first coll)]
    (print head)
    ; Below, dec subtracts one (decrements) from
    ; the number of items in the collection.
    (dotimes [_ (dec (count coll))] (print \.))
    (println)))

(summarize ["Moe" "Larry" "Curly"]) ; -> Moe..
(summarize []) ; -> no output

 

    condp宏跟其他编程语言的case声明类似。它的参数包含一个双参数断言(通常采用=或者 instance? ),

一个表达式作为第二个参数,后面紧跟任意数量成对的值/结果表达式。这些成对值/结果中的值会被依次传入

断言中,如果断言执行结果为真,则返回对应表达式执行的结果;如果断言执行结果为假,则继续下一个

值/结果的断言验证。最后还可以定义一个可选的参数,在如果所有的值/结果断言验证都为假的情况下,

来返回一个值。如果没定义这个可选参数,而所有的断言验证都为假,就会抛出一个

IllegalArgumentException

 

    下面的例子展示了如果用户输入数字1,2,3时就会打印出对应的名字,其他情况,则会打印出

"unexpected value"。在这之后,会检查本地绑定"value"的类型,如果是number类型,就会打印出这个

数字的2倍值;如果是String类型,则会打印出这个字符串长度的2倍值。

(print "Enter a number: ") (flush) ; stays in a buffer otherwise
(let [reader (java.io.BufferedReader. *in*) ; stdin
      line (.readLine reader)
      value (try
              (Integer/parseInt line)
              (catch NumberFormatException e line))] ; use string value if not integer
  (println
    (condp = value
      1 "one"
      2 "two"
      3 "three"
      (str "unexpected value, \"" value \")))
  (println
    (condp instance? value
      Number (* value 2)
      String (* (count value) 2))))
 

    cond宏接收任意数量的成对断言/结果表达式,它会依次对每个断言求值,直到某个断言的值为真,

则返回断言对应的结果。如果没有任何断言的值为真,则会抛出 IllegalArgumentException

通常最后一个断言会简单地写为true,来对应生下的所有情况。

 

    下面的例子是由用户输入水温,程序会打印出水是冰的、烫的或者其他。

(print "Enter water temperature in Celsius: ") (flush)
(let [reader (java.io.BufferedReader. *in*)
      line (.readLine reader)
      temperature (try
        (Float/parseFloat line)
        (catch NumberFormatException e line))] ; use string value if not float
  (println
    (cond
      (instance? String temperature) "invalid temperature"
      (<= temperature 0) "freezing"
      (>= temperature 100) "boiling"
      true "neither")))

 迭代

    有很多种方式来进行“循环”或者对集合中的元素进行迭代。

 

    dotimes宏会将方法体中的表达式重复执行指定的次数,指定给本地绑定的执行序列值从0开始一直到次数减一。

如果这个本地绑定(比如下例中的card-number )是不需要的,则可以采用下划线做为占位符。例子如下:

(dotimes [card-number 3]
  (println "deal card number" (inc card-number))) ; adds one to card-number
 

    注意采用了inc函数,所以输出值由0,1,2变成了1,2,3.上面代码的输出如下:

deal card number 1
deal card number 2
deal card number 3
 

    while宏会在它的判断表达式( test expression)为true时一直执行方法体中的表达式。

下面的例子会在指定线程保持运行的过程中一直执行while主体中的内容:

(defn my-fn [ms]
  (println "entered my-fn")
  (Thread/sleep ms)
  (println "leaving my-fn"))

(let [thread (Thread. #(my-fn 1))]
  (.start thread)
  (println "started thread")
  (while (.isAlive thread)
    (print ".")
    (flush))
  (println "thread stopped"))
 

    这段程序的输入大致如下:

started thread
.....entered my-fn.
.............leaving my-fn.
thread stopped

 列表解析

    for和doseq宏能够执行列表解析。它们支持遍历多重集合(最右的集合最快),还可以选择:when和:while表达式来进行过滤。for宏以一个单独的表达式做为执行主体,返回一个延迟序列结果。 doseq宏以一系列任意数量的表达式做为主体,依次执行以获得它们的副作用,然后返回nil。

 

    下面的例子是遍历一张表格打印出遍历到的单元格,先逐行遍历,在每行中又逐列遍历。中间跳过了B这一列且只遍历小于3的行。注意其中的dorun宏,被用来强制对for返回的延迟序列求值,其详细用法将在"Sequences "这一节描述。

(def cols "ABCD")
(def rows (range 1 4)) ; purposely larger than needed to demonstrate :while

(println "for demo")
(dorun
  (for [col cols :when (not= col \B)
        row rows :while (< row 3)]
    (println (str col row))))

(println "\ndoseq demo")
(doseq [col cols :when (not= col \B)
        row rows :while (< row 3)]
  (println (str col row)))
 

    上面代码产生以下结果:

for demo
A1
A2
C1
C2
D1
D2

doseq demo
A1
A2
C1
C2
D1
D2

 

    特殊form loop,就像名字所展示的那样,支持循环(loop)。

它以及和它合作的特殊form recur都将在下节中进行说明。

 

递归

    递归是指当一个函数直接或者间接通过其他函数调用自身的情况。

递归中止的条件通常是某个集合元素变空或者某个数字已经达到了指定的值。

前一种情况通常是持续采用next函数不断返回除去了头元素的集合来实现,

而后一种情况则通常是采用dec函数 不断对某个数字持续相减来实现的。

 

    在递归时,如果调用栈层次太深,有可能出现运行时内存溢出的情况。

某些程序语言会采用"tail call optimization " (TCO)的方式来处理这个问题,

java不支持,但是Clojure支持。

在Clojure中避免内存溢出的一种方式是采用loop和recur特殊form,

另外一种是采用 trampoline 函数。

 

    loop /recur的约定组合看起来象是在循环中调用递归,但并不消费栈空间。

特殊form loop和特殊form let相似的地方就在于,它们都是建立本地绑定,

但loop同时会建立一个递归节点以供recur来进行调用。loop在创建本地绑定

时会为它指定一个初始值。接下来的recur调用完成后会将控制权交还给loop,

并为本地绑定指定一个新的值。被传递给recur的参数数量必须和loop建立的

绑定数量相同,同样,recur只能出现在loop调用的结尾处。

(defn factorial-1 [number]
  "computes the factorial of a positive integer
   in a way that doesn't consume stack space"
  (loop [n number factorial 1]
    (if (zero? n)
      factorial
      (recur (dec n) (* factorial n)))))

(println (time (factorial-1 5))) ; -> "Elapsed time: 0.071 msecs"\n120
 

   defn宏,就跟loop特殊form一样,也建立了一个递归点。

recur特殊form也可以放到函数的末尾处来为函数传递一个新的值来进行递归调用。

 

    实现阶乘的另外一种方法是采用reduce函数,它在之前的 "Collections "章节已经描述过了。

它看起来更加函数化,更少指令式编程的样式。不幸的是,在当前例子中,它会更低效。

注意range函数接收了一个包含的低层绑定( lower bound)和一个未包含的高层绑定( upper bound)。

(defn factorial-2 [number] (reduce * (range 2 (inc number))))

(println (time (factorial-2 5))) ; -> "Elapsed time: 0.335 msecs"\n120
 

    采用apply替换掉reduce也能获得相同的结果,但那会花销更长的时间。

这说明了在选择函数时了解它们特征的重要性。

 

    recur特殊form并不适用于a函数调用b函数,b函数再调用a函数这种 相互递归 的情况。

而这篇文章没提到的 trampoline 函数更适合相互递归一些。

 

条件判断

    Clojure提供了很多函数来检验某个条件,执行判断。它们的返回值都可以解读为true或者false。

false和nil值都会被解读为false。true和其他任何值,包含0,都会被解读为true。

条件判断函数通常采用?结尾。

 

    反射调用包含了对象的信息,不仅有值,还包括类型等。有很多条件判断函数都是执行的反射。

检验单一对象类型的判断函数有:class? , coll? , decimal? , delay? , float? , fn? , instance? ,

integer? , isa? , keyword? , list? , macro? , map? , number? , seq? , set? , string?和 vector? 。

而某些执行了反射的非判断函数有: ancestors , bases , class , ns-publics和 parents。

 

    检验值之间关系的判断函数有: < , <= , = , not= , == , > , >= , compare , distinct?和 identical?。

 

    检验逻辑关系的判断函数有:and , or , not , true? , false?和 nil?。

 

    检验序列的判断函数大部分在之前我们都遇到过了,包括:empty? , not-empty , every? ,

not-every? , some和 not-any?。

 

    检验数字的判断函数有: even? , neg? , odd? , pos?和 zero?。

 

分享到:
评论

相关推荐

    函数式编程语言:Clojure.zip

    史上最全编程语言全套教程,共99门...函数式编程语言 壳编程语言 常见编程语言 并行编程语言 数据分析编程语言 数据库查询语言 系统编程语言 脚本编程语言 逻辑编程语言 面向对象编程语言 等所有常见的变成语言系列教程

    Clojure入门教程- Clojure – Functional Programming for the JVM中文版

    - **Clojure**是一种运行在Java虚拟机(JVM)上的动态编程语言,它继承了Lisp家族的强大功能,同时结合了现代编程语言的优点,如面向对象编程和函数式编程。 #### 二、Clojure的特性 - **函数式编程**: Clojure强调...

    clojure-utils, 各种小型但方便的clojure实用程序函数库.zip

    clojure-utils, 各种小型但方便的clojure实用程序函数库 各种小型但方便的Clojure实用程序函数库特别关注:Clojure.java - 用于从Java调用Clojure的实用工具函数的Java类arrays.clj - 操作Java数组core.clj - 应该在...

    Scala与Clojure函数式编程模式:Java虚拟机高效编程1

    Scala和Clojure都是现代函数式编程语言,它们都运行在Java虚拟机(JVM)上,能够利用Java的生态系统和资源。Scala是一种多范式语言,结合了面向对象和函数式编程的特点,提供了强大的类型系统和模式匹配功能。...

    Clojure脚本:函数式编程的现代演绎

    Clojure是一种运行在Java虚拟机(JVM)上的动态函数式编程语言,它属于Lisp家族。Clojure的设计哲学强调了函数作为一等公民的地位,以及数据的不可变性,这些特性使得Clojure在处理并发编程和数据密集型应用时表现...

    来源clojure-toolbox.com___下载.zip

    1. **Clojure编程语言**:Clojure是一种基于Lisp的函数式编程语言,运行在Java虚拟机(JVM)上。它强调 immutability(不可变性)、concurrency(并发性)和functional programming(函数式编程)特性。 2. **函数...

    clojure-1.5.1.jar

    clojure-1.5.1.jar

    Scala与Clojure函数式编程

    虽然Java本身不是一种函数式编程语言,但是JVM强大的生态系统为运行函数式编程语言提供了坚实的基础。 本书的作者Michael Bevilacqua-Linn通过自己的经验,结合了多个编程实践者的点评,将关于Scala和Clojure中函数...

    clojure-sha-3-源码.rar

    Clojure是一种基于Lisp的现代函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java平台的强大功能。SHA-3,全称为Secure Hash Algorithm 3,是美国国家标准与技术研究所(NIST)发布的一种密码散列函数标准...

    CS1807-U201814745-朱槐志函数式编程1

    Lisp是一种古老的函数式编程语言,其主要特点是使用S-表达式(Symbolic Expression)表示数据和程序。Lisp的数据结构核心是列表,而列表是通过递归定义的,这使得在Lisp中实现递归算法变得十分自然。它的宏系统允许...

    Clojure入门介绍: Clojure - Functional Programming for the JVM

    Clojure入门介绍: Clojure - Functional Programming for the JVM

    clojure-1.6.0-RC3.zip

    Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java生态系统的优势。这个压缩包很可能是Clojure 1.6.0-RC3的源代码发布,开发者可以下载、编译和测试以确保其稳定性和兼容性。 ...

    clojure-basics-源码.rar

    Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java的生态系统。这个“clojure-basics-源码.rar”压缩包很可能是包含了一些基本Clojure编程概念的示例代码或者教程。虽然没有具体...

    clojure-must-watch-源码.rar

    Clojure是一种功能强大的Lisp方言,它在Java虚拟机(JVM)上运行,并且具有丰富的函数式编程特性。这个压缩包"clojure-must-watch-源码.rar"似乎包含了与Clojure相关的源代码,可能是为了帮助学习者深入理解Clojure...

    clojure1.3.0及资料

    Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java的生态系统。Clojure的设计目标是提供一种静态类型的、并发的、内存安全的语言,同时保持Lisp的简洁性和灵活性。在这个压缩包...

    clojure-1.5.0.zip

    该版本为稳定版,将zip文件解压,放到某个指定目录,cd进入这个目录,执行以下命令即可java -cp clojure-1.5.0.jar clojure.main。...当今最主流的运算平台JVM,把函数式编程语言引入JVM也是新方向。

    函数式编程另类指南.pdf

    - **Lisp**及其衍生语言(如Scheme和Clojure):历史悠久的函数式编程语言,支持宏定义等高级特性。 - **Scala**:结合了面向对象和函数式编程特性的多范式语言。 - **Erlang**:强调并发处理和分布式系统的函数式...

    programming-clojure-3rd

    《Programming Clojure 第三版》是一本深入探讨Clojure编程语言的专业书籍,旨在帮助开发者全面理解和掌握这门基于Lisp的现代函数式编程语言。Clojure是由Rich Hickey设计的,它运行在Java虚拟机(JVM)上,同时也...

    并行编程语言:Clojure.zip

    史上最全编程语言全套教程,共99门...函数式编程语言 壳编程语言 常见编程语言 并行编程语言 数据分析编程语言 数据库查询语言 系统编程语言 脚本编程语言 逻辑编程语言 面向对象编程语言 等所有常见的变成语言系列教程

    基于Java平台的多语言混合编程.pdf

    4. Scala:基于JVM的静态类型语言,支持面向对象编程和函数式编程。 5. Clojure:基于JVM的动态类型语言,支持函数式编程和基于宏的 metabrogramming。 Java平台的多语言混合编程的未来发展趋势是: 1. 软件项目的...

Global site tag (gtag.js) - Google Analytics