- 发布于
使用 TypeScript 进行函数式编程
AI 辅助翻译自英文阅读英文原文
- 作者

- 姓名
- Garfield Zhu
- @_AlohaYo_
@Author: Garfield Zhu
作为一种声明式编程范式,函数式编程借助数学理论和类型系统,以纯函数模块化地构建程序。
TypeScript 既支持函数式范式,又拥有强大的类型系统,非常适合练习函数式编程。本文介绍并演示一些在 TypeScript 中实用的函数式编程片段。
相关库:
使用 Deno 运行时,例如:
deno run memoize.ts
保持不可变
函数式编程的最佳实践是在可能的地方使用不可变变量。
在 TypeScript 中,尽量使用 const 声明变量,并为类型、接口属性及函数参数使用 readonly。除非必要,避免使用 let 或 var(实际上总能找到避免可变变量的方法)。
示例:
declare const a : number
const b = a > 0 ? 'positive' : 'negativeOrZero' // $ExpectedType: 'positive' | 'negativeOrZero'
let b = a > 0 ? 'postive' : 'negativeOrZero' // $ExpectedType: string
纯函数
可缓存
可移植 / 自描述
可测试
合理
支持并发
柯里化
TypeScript 并非天生支持柯里化。诸如 [Rambda](https://github.com/ramda/ramda) 的 Node 包提供了很好的实现,但由于我们使用的是带 TypeScript 的 Deno,直接使用它并不方便,在 TS 中自行实现也不够优雅。
柯里化的分步示例:如何掌握高级 TypeScript 模式
此外,我们可以期待提议中的 Variadic Kinds 特性,它将让定义带类型的 curry 函数更加容易。
幸运的是,Denofun 提供了一组实用的函数式编程工具,其中包括 curry。
示例:
import curry from "https://deno.land/x/denofun/lib/curry.ts";
const greet = (name: string, age: number) => `hello, I'm ${name} and I'm ${age} years old`;
const curriedGreet = curry(greet);
curriedGreet("Tomasz")(26)
练习:使用 curry
组合(Compose)
结合律
const associative = compose(f, compose(g, h)) == compose(compose(f, g), h) // 两者都满足: // associative(x) = f(g(h(x)))无点风格(Point-free) "永远不要显式写出你的数据"
// not pointfree, for data `word` is mentioned const snakeCase = (word: string) => word.toLowerCase().replace(/\s+/ig, '_') // pointfree const pointfreeSnakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);调试 无点风格丢弃了所有参数,因此错误位置可能很难查找,例如:
const dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' ')) dasherize('The world is a vampire') // TypeError: Cannot read property 'apply' of undefined我们可以使用柯里化的
trace函数记录参数来调试:const trace = curry(function(tag, x){ console.log(tag, x) return x }) const dasherize = compose(join('-'), toLower, trace("after split"), split(' '), replace(/\s{2,}/ig, ' ')); // after split [ 'The', 'world', 'is', 'a', 'vampire' ] // 正确写法: const dasherize = compose(join('-'), map(toLower), split(' '), replace(/\s{2,}/ig, ' ')) dasherize('The world is a vampire');
此外还有类似的 pipe 函数,同样用于组合函数。compose 从右到左调用函数参数,而 pipe 从左到右调用。
练习:使用 compose
函子(Functor)
函子是实现
map并遵守某些规律的类型。
通常我们使用包含 of 函数的 pointed 函数来实例化函子。
Container.of(3)
// Container(3)
Container.of(3).map(x => x + 1)
// Container(4)
应用函子(Applicative Functor)
“应用函子”是一种实现了 ap 函数的“pointed 函子”。
ap 函数应将一个函子的值应用到另一个函子的值上。
也就是说:F.of(x).map(f) == F.of(f).ap(F.of(x))
Container.of(add(2)).ap(Container.of(3))
// Container(5)
Container.of(2).map(add).ap(Container.of(3))
使用应用函子可以像调用普通函数一样使用函子中的柯里化函数:
Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3))
// Maybe(5)
Task.of(add).ap(Task.of(2)).ap(Task.of(3));
// Task(5)
参考
Mostly adequate guide to FP (in javascript)