函数式编程范式的理解

2015年06月29日

函数式语言设计模式

最近看了一个视频,是ScottWlaschin在伦敦进行一个关于函数式编程模式的演讲 这个演讲给了我很多启发,所以顺便结合自己的理解,做一下终结。

Functional Patterns 函数式语言的设计模式

这里有几句关于几种编程范式的话,觉得挺有意思的:

Do I need to preserve state? (e.g: some entity in a game) -> OOP.

Am I transforming some input into some kind of output? -> functional. (most of the code).

Something algorithmicky? -> procedural.

再配上ppt上面的图,好搞笑,哈哈 image

函数式语言的设计模式的主要几个原则

  • Functions 函数

  • Types 类型

  • Composition 组合

核心的FP设计

这里其实说的是对函数式语言的一些直观的理解,可以从这几个角度去理解,什么 是函数式范式与面向对象相比,为了同样的工程目标,用了完全不同的概念。

Steal from mathematics

函数式语言里面的确很多数学的概念,映射,尾递归,变换,不可变变量,高阶 函数等用数学的角度去处理数据以前在学习高等代数的时候,一个很重要的概念 就是映射,矩阵就是一个映射𝒜。而映射可以作用于不同的集合。

  • Input and output values already exit
  • A function is not a calculation , just a mapping
  • Input and output values are unchanged(immutable不可变的变量)

从上面这几点可以看出,函数式语言里面的函数的概念其实可以理解为一个映射 𝒜/f(x)的意思而不是运算的集合。

image

比较有趣的是这一个,Functions can work on functions 元素是函数的集合之间 也是可以映射的,也就是输入函数,然后输出是函数,这里可以明白为什么函数式 语言函数是可以作为参数,返回值了。

  • Purity 什么是纯函数?
Pure functions are easy to reason about 

customer.SetName(newName); //setName之后customer状态是不是被改变了?
var name = customer.GetName();

let newCustomer = setCustomerName(aCustomer,newName)
let name,newCustomer = getCustomerName(aCustomer) 
//在函数式语言里面,没有状态所以更加pure?

Pure functions are easy to refactor

let x = doSomething()
let y = doSomethingElse(X)
return y + 1
  • 另外一些如果函数是pure的好处
  • Laziness 延迟求值 only evaluate when I need the output
  • Cacheable results same answer every time – “memoization” 缓存函数结果
  • No order dependencies I can evaluate them in any order I like 执行的时候,不需要根据函数定义的顺序,不用跟c语言那样
  • Parallelizable “embarrassingly parallel” 可以拥抱并发

上面说的各种也就是函数没有状态没有副作用,而且变量不可变 ==> 就代表了 purity 这里我想起erlang里面,其实很多时候函数都不是纯函数,其实也是有side effects的

什么是函数副作用side effects

Types are not classes

函数式语言里面的类型跟类还是有很大区别,虽然类本质上也是一堆数据(函数也可以看成是数据)

什么是类型? 类型应该是数据的定义 类型区分了数据Data以及数据操作的行为 Behaviour

Functions are things

let z = 1; let add x y = x + y; 变量的定义跟函数的定义都是用let关键字

数学里面其实可以认为一切都是函数,f(x) = C,其实我们通常说的常量,在数学概念的理解,应该叫 常函数。

函数可以做参数,可以做返回值,可以在函数内定义,就是说一般 变量能出现的地方,函数也能出现。

Composition everywhere

函数式语言没有继承,接口的概念,基本都是利用组合去实现继承的功能。

  • 函数组合
  • 类型的组合

领域模型的设计-类型的设计

    Types represent constraints on input and output 
    类型代表输入输出的约束,也是程序的文档

    type Suit = Club | Diamond | Spade | Heart
    type String50 = // non-null, not more than 50 chars 
    type EmailAddress = // non-null, must contain ‘@’ 
    type StringTransformer = string -> string
    type GetCustomer = CustomerId -> Customer option

    type PaymentMethod =
    | Cash
    | Cheque of ChequeNumber
    | Card of CardType * CardNumber
    OO version:
    interface IPaymentMethod {..} 
    class Cash : IPaymentMethod {..} 
    class Cheque : IPaymentMethod {..} 
    class Card : IPaymentMethod {..}
    class Evil : IPaymentMethod {..}

image

image

函数就是一个输入集合,输出集合之间的变换 从图左上角看到,外部数据与系统内部的数据类型是怎么转换的 可以更加清晰的理解系统的逻辑,系统逻辑是由数据驱动的,系统的逻辑就是数据流的表现

Function are parameters 函数作为参数

参数化所有的东西

public static int Product(int n) {
    int product = 1;//初始化
    for (int i = 1; i <= n; i++)//遍历
    {
    product *= i; 
    }
    return product;
}
public static int Sum(int n)
{
    int sum = 0;//初始化
    for (int i = 1; i <= n; i++)//遍历
    {
    sum += i; 
    }
    return sum; 
}

Sum  Product 里面 for 这个遍历操作是一样的,可以抽象出来

如果用F#是这么写
let product n =
let initialValue = 1
let action productSoFar x = productSoFar * x
[1..n] |> List.fold action initialValue

let sum n =
let initialValue = 0
let action sumSoFar x = sumSoFar+x
[1..n] |> List.fold action initialValue

let printList anAction aList = for i in aList do
anAction i
// val printList :
//   ('a -> unit) -> seq<'a> -> unit

接口?函数类型就是接口

  • 策略模式
    Object-oriented strategy pattern:
    class MyClass
    {
        public MyClass(IBunchOfStuff strategy) {..}
    int DoSomethingWithStuff(int x)
    {
    return _strategy.DoSomething(x) }
    }

F#:
let DoSomethingWithStuff strategy x = strategy x
  • 装饰模式
    Functional equivalent of decorator pattern
    let add1 x = x + 1 // int -> int
    let logged f x =
    printfn "input is %A" x
    let result = f x
    printfn "output is %A" result
    result
    let add1Decorated = // int -> int 
    logged add1
    
    [1..5] |> List.map add1
    [1..5] |> List.map add1Decorated
  • 依赖注入?IOC?

Chainning Functions

  • Error handling ,Async
  • Monads

Dealing with warpped data

  • Lifting ,Functor
  • Validation with Applicatives

Agrregating data and operations

  • Monoids