Day02 - Pure Function

yo! what's up!

本篇文章会简单地介绍基本的 Functional Programming 概念,这些概念不仅重要,更是贯穿了之後的主题。

Pure Function

给定相同的输入,一定会得到相同的输出。并且不会产生任何 Side Effect.

那我们就来探讨一下这段纯函式(pure function)的定义,

  • 什麽是给定相同输入,一定会得到相同的输出?
  • 什麽是 Side Effect?

给定相同输入,一定会得到相同的输出

举例来说, Math.random

Math.random() // 0.06243529219776711
Math.random() // 0.9436551613058293
Math.random() // 0.29082104009990295

Math.random 有什麽问题呢?可以看到给定同样的输入,却是不同的输出,这就是不纯的函式(impure 函式)。

满足纯函式的要点之一,就是必须是在相同输入下,无论重复呼叫多少次,输出的结果永远是一样的,举 square square为例,只要给定特定的值,就会回传该值的平方。

const square = num => Math.pow(num, 2);

square(2) // 4
square(2) // 4
square(2) // 4

ex1

数学中的函式就像是两个集合的对应关系,且集合内的数皆可以在另外一个集合找到对应的唯一值。而纯函式就是数学中的函式,必须是一对一或是多对一的关系。

Side Effect

Side Effect 就是当呼叫函式时也改变外部物件的状态,我们就会称这个函式有Side Effect.

而什麽是改变外部状态,举以下几个例子

let state = 0;

const counter = () => {
    state += 1;
    return state;
} 

counter(); // 1
counter(); // 2
counter(); // 3
counter(); // 4

上面范例的counter 就是一个有side effect 的函式,因为我们因为呼叫了 counter 时也改变外部的状态。当一个有程序四处散落着 side effect 的函式时,那程序变得非常不可控。

又或是在函式内进行console.log

const doWhat = (x) => {
    console.log(`hahaha, ${x}`) 
    return x;
}

doWhat()

这个范例则是我们在呼叫 doWhat 时,同时也在 console 写入了一些资讯,这也是一个有 Side Effect 的函式。

而会导致Side Effect 的操作,笔者列了几项,

  • 写入 / 读取 / 删除 LocalStorage 的状态
  • DOM 操作
  • 写入 / 读取档案
  • 改变外部的资料结构
  • Http请求

Wait..., 读者们可能心想,在现实开发中,不使用上述这些会产生 Side Effect 的功能,还能进行开发吗? 对,基本上是开发不了任何东西的。所以FP 该不会等於 no code!

不不不

FP 不是不能有Side Effect,而是将这些Side Effect用一些方式包覆起来,将 Effectful program 跟 Pure function 有个明确的界线进行划分。在之後章节我们也会提到FP是如何解决。

回到纯函式的定义

为什麽纯函数这个概念这麽重要? 因为它直接确保 referential transparency 的概念!

首先我们先来看看 referential transparency 的定义

An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior. - wiki

举例来说,

const x = square(2);

上述提到的范例square ,因为它是有着 referential transparency 的特性,所以我们可以将直接用4 取代

const x = 4;

此概念虽然简单,笔者们看了这个范例或许心想 So What?

但 referential transparency 确保了

  • 开发者可以在理解该段程序时,不需要去担心外部状态会如何改变。
  • 可以更有信心地进行重构。

为什麽这些概念这麽重要呢?

提升程序码的可预期性

就如同刚刚上述提到的特性,当程序是由众多纯函数组成时,不用担心我们影响外部的任何状态,也可以确保程序执行过程中,不会产生非预期地结果。

提升可测试性

纯函式也确保了我们在测试时,不用去建造一个真实的环境去测试整个 program, 只需要给定 input 并预期其结果是正确的就好。

可快取

由於纯函式的关系,确保了每个输入对应唯一的输出值,这就代表我们可以进行快取,举最知名的 fibonacci number cache 过後的 fib函式与原本的 fib 的差别就是可以减少重复计算。

const memo = f => {
    const cache = {};
    const memoized = n => {
        if(!cache[n]) {
             cache[n] = f(n);
        }
        return cache[n]
    }
    return memoized;
}

const fib = n => {
    if(n === 0) return 0;
    if(n === 1) return 1;
    return fib(n-2) + fib(n-1)
}

const memoFib = memo(fib)

memoFib(10) // 55
memoFib(10) // cache 後的 55

小结

尽管这些概念都是基本的概念,但其概念却是贯穿着函数式编程,明天将介绍 Currying vs. Partial Application.


<<:  Day 02 - 那个 React Hook

>>:  Day 02 - 环境安装(中) Docker & MySQL

30天轻松学会unity自制游戏-安装资源包

预计三十天内学会制作一个2D游戏,如果还有时间就继续练习一个3D游戏。 开始先做一个2D的卷轴射击游...

Day 16 - UML x Interface — TextField

今天的 TextField 和明天的 FormControl 都是在介绍跟表单有关的介面和元件,而...

[从0到1] C#小乳牛 练成基础程序逻辑 Day 10 - 转角捡到猫 取什麽名字好? 命名规范

路上捡到猫 | 要取什麽名字? | 很急>< 在线等! 🐄点此填写今日份随堂测验 ...

大盘又跌啦!是不是想吃麦当劳阿??

今天的盘又是一个开高走低的情况 最近的盘真的是有够难做,作股票最讨厌的就是遇到这种情况。建议想入场的...

裸机Hyperviser大众化原因

今天来探讨裸机Hyperviser在近几年朝大众化的原因 摆脱固有平台 受够了各大云端运算服务的绑手...