Practical Clojure - Functional Programming Techniques

  1. 云栖社区>
  2. 博客>
  3. 正文

Practical Clojure - Functional Programming Techniques

寒凝雪 2017-05-02 15:25:00 浏览971

对于通用FP技术的介绍, 参考FP基础

此处主要描述这些FP技术特性, 在clojure中的实现 

First-Class Functions, 一类公民

Function作为FP中最基本的元素, 是构成所有其他的基石, 所以是一类公民...



It can be created on demand.  
It can be stored in a data structure. 
It can be passed as an argument to a function. 
It can be returned as the value of a function.


There are two aspects to using first-class functions: 
Consuming, as arguments and calling them 
Functions that take other functions as arguments are extremely common. These are known as higher order functions.

Producing, creating and returning them 
Not only can functions take other functions as arguments, but they can construct them and return them as values. 
此特性相当强大, 后面描述的curry, comp, closure都是produce function的方法 
This is one of the main reasons Lisp has historically been associated with artificial intelligence. 
It was thought that functions creating other functions would allow a machine to evolve and define its own behavior. 
Although self-modifying programs never quite lived up to expectations, the ability to define functions on-the-fly is nevertheless extremely powerful and useful for many everyday programming 

代码即数据, 可以运行时修改代码, 这也成为Lisp主要被用于AI的主要原因, 满足程序不停自我进化的需要. 

(defn rangechecker
  "Returns a function that determines if a number is in a provided range."
  [min max]
  (fn [num]
    (and (<= num max)
         (<= min num))))


user=> (def myrange (rangechecker 5 10))

user=> (myrange 7)
user=> (myrange 11)



closure和clojure读音一样, 所以在clojure中很重要?

简单的一句话, 保存context的函数. context一般都是在function之外的数据 
因为引用的value, 其实在调用的时候, 本不应该存在的. 而实际不但存在且仅存在于function内部, 并且这个值还会伴随function的整个生命周期. 是不是很像这个value被close over在function内部... 
As might be gathered from its very name, closures are a central feature in Clojure. But what, exactly, is a closure? And why do they matter so much? 
Briefly stated, closures are first-class functions that contain values as well as code. 
These values arethose in scope at function declaration, preserved along with the function. 
Whenever a function is declared, the values locally bound to symbols it references are stored along with it. 
They are “closed over” (hence the name) and maintained along with the function itself. This means that they are then available for the function’s entire lifespan and the function can be referred to as a closure.

The value of a closed-over value can’t change after the function is created, so it becomes in essence a constant for that function.

One interesting property of closures is that due to their dual nature—both behavior and data—they can fulfill some roles that are assumed by objects in object-oriented languages. 
Just as anonymous classes with one method are used to simulate first-class functions in Java, closures can be viewed as an object with a single method.


(defn times-n [n]
  (let [x n]
    (fn [y] (* y x))))


(def times-four (times-n 4))

(times-four 10)
;=> 40

Currying and Composing Functions

Using partial to Curry Functions

In Clojure, any function can be curried using the partial function. 
partial takes a function as its first argument and any number of additional arguments. 
It returns a function that is similar to the provided function, but with fewer arguments; it uses the additional arguments to partial instead.

partial这个名字比较形象, 部分啥? 部分参数. 第一个参数是function, 后面的参数是需要fix的那部分参数

user=> (def times-pi (partial * 3.14159)) ;*本身需要2个参数, 用partial指定一个而生成新的pi函数

不用partial你直接这样定义也是一样的, 不过用partial更简洁

(defn times-pi
  "Multiplies a number by PI”
  (* 3.14159 n))

Currying vs. Partial Application, 描述Currying和Partial的区别

Using comp to Compose Functions

comp takes any number of parameters: each parameter is a function. 
It returns a function that is the result of calling all of its argument functions, from right to left. Starting with the rightmost, it calls the function and passes the result as the argument to the next function and so on.

user=> (def my-fn (comp – *))

user=> (my-fn 5 3) ;–(5 * 3)


(defn my-fn ;同样comp只是一种简洁的表达方式, 也可以直接写
  "Returns –(x * y)”
  [x y]
  (- (* x y)))


comp看上去和curry没啥关系, 但是comp的每个function都只能有一个参数, 所以经常使用currying技术来减少参数 
Because the functions passed to comp are required to take a single argument, it makes them particularly good candidates for using currying with partial.

user=> (def my-fn (comp (partial * 10) - *))
user=> (my-fn 5 3)


Continuation-passing style, joy 7.3.4

Before wrapping up this chapter, we’re going to take time to talk about a style of programming not necessarily prevalent in Clojure, but moreso in the functional tradition: continuation-passing style
Continuation-passing style (CPS) is a hybrid between recursion and mutual recursion, but with its own set of idioms.

这在FP基础里面也讲到, 但其实在clojure里面并没有被广泛使用. 因为可能不太需要, 因为在Haskell这种pure FP里面, CPS可以用于保证执行顺序 
其实可以认为recursion是一种特殊的CPS, 每次都continue到自身 



+ 关注