函数式编程的理念

1. FP 理念

1.1 不变性

  • 没有变量的概念, 只有'值'.
    • 避免改变状态及可变数据.
    • 三部曲: 编写函数, 使用REPL工具测试, 使用.

1.2 声明性风格

  • 代码是描述期望结果的表达式.
    • 将表达式组合起来, 可以同时隐藏执行细节和复杂性.
  • 声明性只提出what to do 而不是解决how to do.
    • 例如, 使用foreach 来遍历集合数据, 其实是在重复how to do(命令式).
    • 提取通用的'思想'为词语(函数名), 已经是what to do 了.
// csharp
return sourceList.Where(item => item %2 == 0);
// or LINQ 
stylereturn from item in sourceList where item % 2 == 0 select item;
// if we have a function(abstract) already. 
return sourceList.Where(NumberIsEven);
  • C#的LINQ借鉴的是FP的高阶函数以及monad,只是和SQL长的比较像而已(可能是为了避免学习成本)。

1.3 类型

  • 每个表达式都有对应的类型, 这确保了表达式组合的正确性.
  • 泛型是一种代码重用技术.
    • 使用类型占位符来将真正的类型延迟到运行时再决定.
    • 类型模板, 在需要时插入真实的类型.
    • 包装: 打包了某种具体类型.
      • 包装类: int?其实是指Nullable<T>对int类型的包装。

1.4 表达式求值

  • 整个程序就是一个大的表达式.
    • 关心的是表达式的副作用, 而不是其返回值.
    • 没有语句的概念.

2. 高阶函数

  • 方便函数的定制.
    • 隐藏具体的执行细节, 将可定制的部分(行为)抽象处理传递给某个高阶函数使用.
    • 类似OO 的模板方法.
  • 提升
    ('a -> 'b) -> M<'a> -> M<'b>
    lift2: ('a -> 'b -> 'c) -> M<'a> -> M<'b> -> M<'c>
    • 允许将一个对值进行处理的函数转换为一个在不同设置中完成相同任务的函数.

3. 柯里化(Currying) 和部分函数应用

  • 柯里化(Currying)
function addBy(value) 
{ 
  return function(n) {  
    // 将value 的进行闭包处理
    return n + value; 
  };
}
var add10 = addBy(10);
var result11 = add10(1);

// orElse
var result11 = addBy(10)(1);
  • 多个参数时:
    let fakeAddFn n1 = fun n2 -> fun n3 -> fun n4 -> n1 + n2 + n3 + n4

4. 递归及优化

  • 尾递归优化:
    • 当在递归调用的时候,不需要做任何工作, 则可以从当前的调用栈直接跳到下一次的调用栈上去.
    • 关键是对累加器的使用.
    • 当需要处理非常庞大的列表时.
public int Factorial(int n) {
    return n <= ? 1 : n * Factorial(n - 1);
}
>> 每次递归都卡在了n*_ 上, 必须等后面的结果返回后,当前函数的调用栈才能返回.
n               (n-1)       ...      3         2       1  // state
--------------------------------------------------------
n*f(n-1) -> (n-1)*f(n-2) -> ... -> 3*f(2) -> 2*f(1) -> 1  // stack in
                                                       |  
n*r      <-  (n-1)*(r-1) <- ... <-   3*2  <-   2*1  <- 1  // stack out

private int FactorialHelper(acc, n) {
    return n <= 1 ? acc : FactorialHelper(acc * n, n - 1);
}
public int Factorial(int n) { return FactorialHelper(1, n); }

init        f(1, n)             // stack in
                |               // stack pop, jump to next
n           f(n, n-1)           // stack in
                |               // stack pop, jump to next
n-1         f(n*(n-1), n-2)     // stack in
                |               // stack pop, jump to next
...         ...                 // stack in
                |               // stack pop, jump to next
2           f((k-1), 1)         // stack in
                |               // stack pop, jump to next
1           k                   // return result
  • fold 自带累加器的化简函数, 抽取了遍历并化简核心的步骤, 仅将需要自定义的部分以参数的形式放出来.
    • fold 的累加器需要指定一个初始值, 而reduce 的初始累加器使用列表的第一个值.

5. 记忆化

  • FP 函数是没有副作用的
    • 也就意味着以相同的参数调用同一函数将会返回相同的结果.
    • 对于一些会被多次调用的函数, 将对应参数的求值结果缓存起来, 以后遇到相同的参数直接返回缓存结果.
  • 通用的记忆化函数
let memorize f =
    let cache = new Dictionary<_, _>()
    fun p ->
        match cache.TryGetValue(p) with
        | true, result -> result
        | _ ->
            let result = f p
            cache.Add(p, result)
            result

6. 惰性求值

  • 立刻求值: 将表达式n % 2 == 0 ? "right" : "wrong"绑定到标识(即变量名)isEven上
  • 将isEven 绑定到某种结构上, 结构知道如何求值,并且是按需求求值.
    • var isEven = new Lazy<string> (() => n % 2 == 0 ? "right" : "wrong");
    • 使用isEven.value 来即可求值并拿到结果.
  • 通过函数,函数表达了某种可以得到值的方式,但是需要调用才能得到.
    • var isEven = (Func<string>)(() => n % 2 == 0 ? "right" : "wrong");
    • 通过函数调用isEven() 来立刻获取值.

7. 延迟

  • 回调函数: 延后了某种行为, 且该行为对上下文有依赖.
  • 手段: 在函数调用结束后自动调用指定的行为(函数).
    • 在进行尾递归优化时, 之前累加器累加的值, 延迟使得可以累加行为.
  • 延迟可以推导出bind: 外层匹配模式.
// binding:
('a -> 'b)    -> 'a -> 'b
// map:
('a -> 'b)    -> M<'a> -> M<'b>
// bind:
('a -> M<'b>) -> M<'a> -> M<'b>

10. Mics

  • 一等用来描述'值', 函数被看做一等公民, 就是函数等价于'值'的概念.
  • 高阶函数: 以一个函数(而不是值)作为参数; 返回一个函数作为结果.
  • 编程方式
    • 命令式编程: 建立在直接操作和检查程序状态之上.
      • 局限于细节.
      • 函数式的思路是将程序拆分并抽象成多个函数再组装回去.
    • 面向对象编程
      • js 是利用现有的对象作为原型来生产特定的实例.
      • 必须有一个语义的自引用(this).
    • 元编程
      • 可以通过编写代码来改变某些代码被解释的方式.
      • this 引用的动态性质可以用来元编程.
  • 集合中心编程.
    • 用100 个函数操作一个数据结构, 比用10 个函数操作10 个数据结构要好.
  • 闭包的两个重点
    • 闭包会捕获一个值(或引用), 并多次返回相同的值.
    • 每个新的闭包都会捕获不一样的值.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容