yo, what's up?
Product types 允许同时存在两种以上的资料型态在内
举例来说现在我们建立一个特别的型别叫 Clock
,其可以放入两个数值,Hour
与 Period
class Clock {
constructor(hour, period) {
if(!Array.from({length: 12}, (_, i) => i + 1).includes(hour) || !['AM', 'PM'].includes(period)){
throw new Error('format error!')
}
this.hour = hour;
this.period = period;
}
}
大家可以思考一下,我们呼叫 Clock 可能的组合会有多少?
const One_AM = new Clock(1, "AM");
const Two_AM = new Clock(2, "AM");
// ... 以此类推
没错,是 24 种,因为 Hour 有 12 种, Period 则是 2 种,所以 Clock 会有 12 * 2 = 24 种不同的组合。
用在数学世界中,通常会将其表示
C([A, B]) = C(A) * C(B)
C(A): 为 type A
有多少种元素在内。例如 Hour
有 12 种。
而 Product type 运用的时机通常为其组成数值为相互独立的。 像是 hour 改变时, period 并不会受到影响。
Sum types 则每次只能有一组固定的资料型态
用在数学世界中,通常会将其表示
C(A | B) = C(A) + C(B)
例如 TODO List 其状态包含 CRUD, 并且会用 type 去标记现在的状态 (ex CREATE, REMOVE)
const CREATE_TODO = 'CREATE_TODO'
const REMOVE_TODO = 'REMOVE_TODO'
{
type: CREATE_TODO,
text
}
{
type: REMOVE_TODO,
id,
}
而每个 Action 都会有自己的 constructors
const create = (text) => ({
type: CREATE_TODO,
text
})
const remove = (id) => ({
type: REMOVE_TODO,
id
})
这就是 Sum Type 的概念, Action 里面只会有一种动作,不会同时有多个,而每个动作都会用 tag
去标记,这也让我们可以对其进行 recursive,举 LinkedList 为例,
如果用 TS 的 interface 表达
type LinkedList<A> =
| { readonly _tag: 'Nil' }
| { readonly _tag: 'Cons'; readonly head: A; readonly tail: LinkedList<A> }
而 LinkedList<A>
就是 recursion.
在一些 FP 语言中,有pattern matching 这个非常好用的功能。而 JavaScript 则有相关的 proposal 正在进行,但在原生没有这个功能前,我们可以实作出一个类似的 pattern matching 的函式 match
.
继续沿用 LinkedList 作为范例,
const nil = { _tag: 'Nil' };
const cons = (head, tail) => ({
_tag: 'Cons',
head,
tail,
});
const match = (onNil, onCons) => (fa) => {
switch (fa._tag) {
case 'Nil':
return onNil();
case 'Cons':
return onCons(fa.head, fa.tail);
}
};
// 此 LinkedList 是否为空
const isEmpty = match(
() => true,
() => false
);
// 新增 item 在 LinkedList 中
const addFirst = (num) =>
match(
() => cons(num, nil),
(head, _tail) => cons(num, cons(head, _tail))
);
// 取得该 LinkedList 第一个数值
const head = match(
() => undefined,
(head, _tail) => head
);
// 取得该 LinkedList 最後一个数值
const last = match(
() => undefined,
(head, tail) => (tail._tag === 'Nil' ? head : last(tail))
);
// 取得该 LinkedList 长度
const length = match(
() => 0,
(_, tail) => 1 + length(tail)
);
const myList = cons(1, cons(2, cons(3, nil)));
isEmpty(myList) // false
如果两值(状态)相依时,以 react 为例,我们常常会写出类似这样的程序
import React, { useState, useEffect } from 'react';
const App = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null);
const [data, setData] = useState(null);
...
return <>{!loading && !error && data.map(/** rendering */)}</>
}
而这种两值相依的情况就非常适合使用 Sum type,我们就来将上面改写一下,
首先我们先定义其可能的状态,并根据每个状态给定 constructor.
const match = (onInit, onLoading, onError, onSuccess) => (fa) => {
switch (fa._tag) {
case 'INIT':
return onInit();
case 'LOADING':
return onLoading();
case 'ERROR':
return onError(fa.error);
case 'SUCCESS':
return onSuccess(fa.data);
default:
break;
}
};
const STATE = {
INIT: { _tag: 'INIT' },
LOADING: { _tag: 'LOADING' },
ERROR: (error) => ({
_tag: 'ERROR',
error,
}),
SUCCESS: (data) => ({ _tag: 'SUCCESS', data }),
};
接下来进行 fetch 以及 UI render
import React, { useEffect, useState } from 'react';
export default function App() {
const [result, setResult] = useState(STATE.INIT);
useEffect(() => {
const runEffect = () => {
setResult(STATE.LOADING);
fetch('https://jsonplaceholder.typicode.com/todos')
.then((response) => response.json())
.then((data) => setResult(STATE.SUCCESS(data)))
.catch((error) => setResult(STATE.ERROR(error)))
};
runEffect();
}, []);
const renderer = match(
() => <div>initial...</div>,
() => <div>loading...</div>,
(error) => <div>{JSON.stringify(error)}</div>,
(xs) =>
xs.map((x) => (
<code key={x.id}>
<pre>{JSON.stringify(x, null, 2)}</pre>
</code>
))
);
return <>{renderer(result)}</>;
}
Product Type 适合用在两值相互独立的情况, Sum Type 则适合用在两值相依的情况,而 ADT 的概念的应用在处理业务逻辑上。
NEXT: Semigroup
<<: 第 10 天 阶段达成继续奋斗( leetcode 003 )
前言 该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系...
据说设计模式有很多种⋯⋯而MVC是超级常用的一种,在还没有物件导向的概念、或者是值型别、参考型别的概...
前言 在上一章节中,讲述了Linux process之基本原理与机制,以及控制jobs工作的方法,并...
我们来接续昨日Azure Site Recovery(ASR)的进度之前, 我想补充一下地端及云端容...
前言 为了能够更全面的去理解k8s的原理 今天主要从k8s 几个基础的元件开始介绍 Node &am...