April 25, 2021
λ³Έ ν¬μ€νΈλ fp-ts 곡μ λ¬Έμμ Learning Resourcesμ μλ Getting Startedμμ μκ°νλ λ¬Έμλ€μ λ²μνλ©° νμ΅ν λ¬Έμμ λλ€. μλ³Έ λ¬Έμλ λ§ν¬μμ νμΈν μ μμΌλ©° μμ±ν μ½λλ€μ μ¬κΈ°μμ νμΈν μ μμ΅λλ€.
Semigroup
μ ν¨μν νλ‘κ·Έλλ°μ κ·Όλ³Έμ μΈ μΆμνμ΄λ―λ‘ μ΄ κΈμ λ΄μ©μ΄ νμλ³΄λ€ κΈΈμ΄μ§ κ²μ
λλ€.
Semigroup
μ A
κ° λΉμ΄ μμ§ μμ μ§ν©μ΄κ³ *
κ° A
μ λν μ΄μ§ μ°κ΄ μ°μ°μΈ μ (A, *)
μ
λλ€. μ¦, A
μ λ μμλ₯Ό μ
λ ₯μΌλ‘ λ°κ³ A
μ μμλ₯Ό μΆλ ₯μΌλ‘ λ°ννλ ν¨μμ
λλ€.
*: (x: A, Y: A) => A
κ²°ν© λ²μΉμ μλμ λμμ΄ λͺ¨λ A
μ λν x
, y
, z
μ λν΄ μ μ§λ¨μ μλ―Ένλ€.
(x * y) * z = x * (y * z)
κ²°ν© λ²μΉμ λ¨μν ννμμ κ΄νΈλ‘ λ¬Άλ κ²μ λν΄ κ±±μ ν νμκ° μμΌλ©° x * y * z
λ₯Ό μΈ μ μλ€λ κ²μ μλ―Έν©λλ€.
Semigroup
μ λ³λ ¬ν κ°λ₯ν μ°μ°μ λ³Έμ§μ ν¬μ°©ν©λλ€.
Semigroup
μ μμλ μλμ κ°μ΄ λ§μ΄ μμ΅λλ€.
(number, *)
: μ¬κΈ°μμ *
μ°μ°μ μΌλ°μ μΈ μ«μμ κ³±μ
λλ€.(string, +)
: μ¬κΈ°μμ +
μ°μ°μ μΌλ°μ μΈ λ¬Έμμ΄ μ°κ²°μ
λλ€.(boolean, &&)
: μ¬κΈ°μμ &&
μ°μ°μ μΌλ°μ μΈ λ
Όλ¦¬κ³±μ
λλ€.μ΄ μΈμλ λ§μ μμκ° μμ΅λλ€.
fp-ts
μμ fp-ts/lib/Semigroup
λͺ¨λμ ν¬ν¨λ νμ
ν΄λμ€ Semigroup
μ TypeScriptμ interface
λ‘ κ΅¬νλ©λλ€. μ¬κΈ°μ μμ
*
λ concat
μΌλ‘ λͺ
λͺ
λ©λλ€.
interface Semigroup<A> {
concat: (x: A, y: A) => A;
}
Semigroup
μ μλμ κ·μΉμ΄ μ μ§λμ΄μΌ ν©λλ€.
A
μ λͺ¨λ x
, y
, z
μ λνμ¬ concat(concat(x, y), z) = concat(x, concat(y, z))
λ₯Ό λ§μ‘±νλ€.concat
μ΄λΌλ μ΄λ¦μ λ°°μ΄μ λν΄ νΉν μλ―Έκ° μμ§λ§, μΈμ€ν΄μ€λ₯Ό ꡬννλ λ§₯λ½ λ° νμ
A
μ λ°λΌ Semigroup
μ°μ°μ λ€λ₯Έ μλ―Έλ‘ ν΄μλ μ μμ΅λλ€.
μ΄ μΈμλ λ€λ₯Έ λ§μ μλ―Έλ‘ ν΄μλ μ μμ΅λλ€.
μλμ semigroupProduct
μΈμ€ν΄μ€κ° (number, *)
μ ꡬννλ λ°©λ²μ
λλ€.
/** number νμ
μ κ³±μ
`Semigroup` */
export const semigroupProduct: Semigroup<number> = {
concat: (x, y) => x * y,
};
λμΌν νμ
μ λν΄ μλ‘ λ€λ₯Έ Semigroup
μΈμ€ν΄μ€λ₯Ό μ μν μ μμ΅λλ€. μλλ semigroupProductSum
μΈμ€ν΄μ€λ‘ (number, +)
μ ꡬνμ
λλ€. μ¬κΈ°μ +
λ μΌλ°μ μΈ number
νμ
μ λνκΈ° μ°μ°μ
λλ€.
/** number νμ
μ λ§μ
`Semigroup` */
const semigroupSum: Semigroup<number> = {
concat: (x, y) => x + y,
};
λ€λ₯Έ μμλ‘ string
νμ
μ μ¬μ©ν μ μμ΅λλ€.
const semigroupString: Semigroup<string> = {
concat: (x, y) => x + y,
};
νμ
A
κ° μ£Όμ΄μ‘μ λ A
μμ μ°κ΄ μ°μ°μ μ°Ύμ μ μμΌλ©΄ μ΄λ»κ² ν μ μμκΉμ? μλμ ꡬμ±μ μ¬μ©νμ¬ λͺ¨λ νμ
μ λν΄ (μ¬μν) Semigroup
μΈμ€ν΄μ€λ₯Ό λ§λ€ μ μμ΅λλ€.
/** νμ 첫 λ²μ§Έ μΈμλ₯Ό λ°ννλ€. */
function getFirstSemigroup<A = never>(): Semigroup<A> {
return { concat: (x, y) => x };
}
/** νμ λ λ²μ§Έ μΈμλ₯Ό λ°ννλ€. */
function getLastSemigroup<A = never>(): Semigroup<A> {
return { concat: (x, y) => y };
}
λ λ€λ₯Έ κΈ°μ μ A
μ μμ Semigroupμ΄λΌκ³ νλ Array<A>
(*)μ λν Semigroup
μΈμ€ν΄μ€λ₯Ό μ μνλ κ²μ
λλ€.
function getArraySemigroup<A = never>(): Semigroup<Array<A>> {
return { concat: (x, y) => x.concat(y) };
}
κ·Έλ¦¬κ³ A
μ μμλ₯Ό Array<A>
μ λ¨μΌ μμμ 맀νν©λλ€.
function of<A>(a: A): Array<A> {
return [a];
}
(*)λ μλ°ν λ§νλ©΄ A
μ λΉμ΄ μμ§ μμ λ°°μ΄μ λν Semigroup
μΈμ€ν΄μ€μ
λλ€.
μ°Έκ³ :
concat
μ λ°°μ΄μ λ©μλλ‘,Semigroup
μ°μ°μ μ΄λ¦μ λν μ΄κΈ° μ νμ μ€λͺ ν©λλ€.
A
μ μμ Semigroup
μ μμκ° A
μμμ λΉμ΄μμ§ μμ μ ν μνμ€μΌ μ μλ Semigroup
μ
λλ€.
Ord
λ‘ νμμν€κΈ°νμ
A
μ λν Semigroup
μΈμ€ν΄μ€λ₯Ό λ§λλ λ λ€λ₯Έ λ°©λ²μ΄ μμ΅λλ€. A
μ λν Ord
μΈμ€ν΄μ€κ° μ΄λ―Έμλ κ²½μ° μ΄λ₯Ό Semigroup
μΌλ‘ βλ³νβν μ μμ΅λλ€.
μλ μ½λλ μ€μ λ‘ κ°λ₯ν λ Semigroup
μ
λλ€.
μλ¬Έμμλ
getMeetSemigroup
,getJoinSemigroup
,ordNumber
λ₯Ό μ¬μ©νλΌκ³ μμ±λμ΄ μμ§λ§, μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μμΌλ©°min
,max
,Ord
λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
import { Ord } from 'fp-ts/lib/number';
import { max, min } from 'fp-ts/lib/Semigroup';
/** 2κ°μ κ° μ€ μμ κ°μ λ°ννλ€. */
const semigroupMin: Semigroup<number> = min(Ord);
/** 2κ°μ κ° μ€ ν° κ°μ λ°ννλ€. */
const semigroupMax: Semigroup<number> = max(Ord);
μμ±ν semigroupMin
, semigroupMax
μΈν°νμ΄μ€λ μλμ κ°μ΄ ν
μ€νΈν μ μμ΅λλ€.
semigroupMin
μΈν°νμ΄μ€λ₯Ό ν
μ€νΈνλ μ½λdescribe('Semigroup μΈν°νμ΄μ€λ₯Ό ꡬνν semigroupMin μΈμ€ν΄μ€ ν
μ€νΈ', () => {
it('semigroupMin μΈμ€ν΄μ€ concat ν¨μ ν
μ€νΈ', () => {
expect(semigroupMin.concat(2, 1)).toBe(1);
});
});
semigroupMax
μΈν°νμ΄μ€λ₯Ό ν
μ€νΈνλ μ½λdescribe('Semigroup μΈν°νμ΄μ€λ₯Ό ꡬνν semigroupMax μΈμ€ν΄μ€ ν
μ€νΈ', () => {
it('semigroupMax μΈμ€ν΄μ€ concat ν¨μ ν
μ€νΈ', () => {
expect(semigroupMax.concat(2, 1)).toBe(2);
});
});
semigroupMin
μΈν°νμ΄μ€μ concat
ν¨μλ μΈμλ‘ λ°μ λ κ°μ κ° μ€ μμ κ°μ λ°ννλμ§ νμΈνλ©° semigroupMax
μΈν°νμ΄μ concat
ν¨μλ μΈμλ‘ λ°μ λ κ°μ κ° μ€ ν° κ°μ λ°ννλμ§ νμΈν©λλ€.
μ’ λ 볡μ‘ν νμ
μ λν Semigroup
μΈμ€ν΄μ€λ₯Ό μμ±ν΄ λ³΄κ² μ΅λλ€.
type Point = {
x: number;
y: number;
};
const semigroupPoint: Semigroup<Point> = {
concat: (p1, p2) => ({
x: semigroupSum.concat(p1.x, p2.x),
y: semigroupSum.concat(p1.y, p2.y),
}),
};
μ μ½λμ λλΆλΆ μμ£Ό μ¬μ©νλ ꡬ문μ
λλ€. μ’μ μμμ κ° νλμ λν΄ Semigroup
μΈμ€ν΄μ€λ₯Ό μ 곡 ν μ μλ€λ©΄ Point
μ κ°μ ꡬ쑰체μ λν΄ Semigroup
μΈμ€ν΄μ€λ₯Ό λ§λ€ μ μλ€λ κ²μ
λλ€.
μ€μ λ‘ fp-ts/lib/Semigroup
λͺ¨λμ getStructSemigroup
μ½€λΉλ€μ΄ν°λ₯Ό μ§μν©λλ€.
μλ¬Έμμλ
getStructSemigroup
λ₯Ό μ¬μ©νλΌκ³ μμ±λμ΄ μμ§λ§, μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μμΌλ©°struct
λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
import { struct } from 'fp-ts/lib/Semigroup';
const semigroupPoint: Semigroup<Point> = struct({
x: semigroupSum,
y: semigroupSum,
});
κ³μν΄μ λ°©κΈ μ μ λ μΈμ€ν΄μ€λ‘ struct
λ₯Ό μ¬μ©ν μ μμ΅λλ€.
type Vector = {
from: Point;
to: Point;
};
const semigroupVector: Semigroup<Vector> = struct({
from: semigroupPoint,
to: semigroupPoint,
});
struct
μ fp-ts
μμ μ 곡νλ μ μΌν μ½€λΉλ€μ΄ν°κ° μλλλ€. μ¬κΈ°μ ν¨μμ λν Semigroup
μΈμ€ν΄μ€λ₯Ό νμμν¬ μ μλ μ½€λΉλ€μ΄ν°κ° μμ΅λλ€. S
μ λν Semigroup
μΈμ€ν΄μ€κ° μ£Όμ΄μ§λ©΄, λͺ¨λ A
μ λν΄ μκ·Έλμ² (a: A) => S
μ ν΄λΉνλ Semigroup
μΈμ€ν΄μ€λ₯Ό λμΆν μ μλ€.
μλ¬Έμμλ
getFunctionSemigroup
,semigroupAll
λ₯Ό μ¬μ©νλΌκ³ μμ±λμ΄ μμ§λ§, μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μμΌλ©°getSemigroup
,SemigroupAll
λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
import type { Semigroup } from 'fp-ts/lib/Semigroup';
import type { Point } from './semigroupPoint';
import { getSemigroup } from 'fp-ts/lib/function';
import { SemigroupAll } from 'fp-ts/lib/boolean';
/** `semigroupAll`μ κ²°ν© λ boolean Semigroupμ
λλ€. */
export const semigroupPredicate: Semigroup<
(p: Point) => boolean
> = getSemigroup(SemigroupAll)<Point>();
μ΄μ Points
μμ λ predicate ν¨μλ₯Ό βλ³ν©βν μ μμ΅λλ€.
const isPositiveX = (p: Point): boolean => p.x >= 0;
const isPositiveY = (p: Point): boolean => p.y >= 0;
const isPositiveXY = semigroupPredicate.concat(isPositiveX, isPositiveY);
μμ±ν isPositiveXY
ν¨μλ μλμ κ°μ΄ ν
μ€νΈν μ μμ΅λλ€.
describe('semigroupPredicate μΈμ€ν΄μ€λ₯Ό μ΄μ©ν΄ λ§λ isPositiveXY ν
μ€νΈ', () => {
it('isPositiveXY ν¨μ ν
μ€νΈ', () => {
expect(isPositiveXY({ x: 1, y: 1 })).toBeTruthy();
expect(isPositiveXY({ x: 1, y: -1 })).toBeFalsy();
expect(isPositiveXY({ x: -1, y: 1 })).toBeFalsy();
expect(isPositiveXY({ x: -1, y: -1 })).toBeFalsy();
});
});
SemigroupAll
μ μ΄μ©νκΈ° λλ¬Έμ semigroupPredicate
μΈμ€ν΄μ€μ concat
ν¨μμ μ λ¬λ λ ν¨μ λͺ¨λ true
λ₯Ό λ°νν΄μΌ isPositiveXY
ν¨μκ° true
λ₯Ό λ°νν©λλ€. x
, y
λͺ¨λ 0 μ΄μμ κ°μ΄ μ λ¬λμμ κ²½μ° true
κ° λ°νλμλμ§ νμΈν©λλ€.
μ μμ λ°λΌ concat
μ A
μ λ μμμμλ§ μλν©λλ€. λ λ§μ μμλ₯Ό μ°κ²°νλ €λ©΄ μ΄λ»κ² ν μ μμκΉμ?
fold
ν¨μλ Semigroup
μΈμ€ν΄μ€, μ΄κΉκ° λ° μμ λ°°μ΄μ μ¬μ©ν©λλ€.
μλ¬Έμμλ
fold
,semigroupSum
,semigroupProduct
λ₯Ό μ¬μ©νλΌκ³ μμ±λμ΄ μμ§λ§, μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μμΌλ©°concatAll
,SemigroupSum
,SemigroupProduct
λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
import { SemigroupSum, SemigroupProduct } from 'fp-ts/lib/number';
import { concatAll } from 'fp-ts/lib/Semigroup';
const sum = concatAll(SemigroupSum);
const product = concatAll(SemigroupProduct);
μμ±ν sum
ν¨μμ product
ν¨μλ μλμ κ°μ΄ ν
μ€νΈν μ μμ΅λλ€.
sum
ν¨μλ₯Ό ν
μ€νΈνλ μ½λdescribe('concatAll, SemigroupSumλ₯Ό μ¬μ©ν sum ν¨μ ν
μ€νΈ', () => {
it('sumν¨μ ν
μ€νΈ', () => {
expect(sum(0)([1, 2, 3, 4])).toBe(10);
expect(sum(10)([1, 2, 3, 4])).toBe(20);
});
});
product
ν¨μλ₯Ό ν
μ€νΈνλ μ½λdescribe('concatAll, SemigroupProductλ₯Ό μ¬μ©ν product ν¨μ ν
μ€νΈ', () => {
it('productν¨μ ν
μ€νΈ', () => {
expect(product(1)([1, 2, 3, 4])).toBe(24);
expect(product(10)([1, 2, 3, 4])).toBe(240);
});
});
μλ¬Έμ fold
ν¨μλμ λ€λ₯΄κ² concatAll
ν¨μλ μ΄κΉκ°μ μΈμλ‘ λ°κ³ concat
μ μ¬μ©ν λ°°μ΄μ μ λ¬λ°μ κ°μ λ°ννλ ν¨μλ₯Ό λ°ννλ€. λ°λΌμ sum(0)([1, 2, 3, 4])
, product(1)([1, 2, 3, 4])
μ κ°μ΄ ν¨μ νΈμΆ μ°μ°μλ₯Ό λ λ² μ¬μ©ν΄ ν
μ€νΈν μ μλ€.
Option<A>
λ κ°λ₯Ό βλ³ν©βνλ €λ©΄ μ΄λ»κ² ν μ μμκΉμ? λ€ κ°μ§ κ²½μ°κ° μμ΅λλ€.
x | y | concat(x, y) |
---|---|---|
none | none | none |
some(a) | none | none |
none | some(a) | none |
some(a) | some(b) | ? |
λ§μ§λ§ νλμ λ¬Έμ κ° μμ΅λλ€. λ κ°μ A
νμ
some
κ°μ²΄λ₯Ό βλ³ν©βνλ €λ©΄ 무μΈκ°κ° νμν©λλ€.
λ κ°μ A
λ₯Ό βλ³ν©βνλ κ²μ΄ Semigroup
μ΄ νλ μΌμ
λλ€! A
μ λν Semigroup
μΈμ€ν΄μ€λ₯Ό μꡬν λ€μ Option<A>
μ λν Semigroup
μΈμ€ν΄μ€λ₯Ό νμ ν μ μμ΅λλ€. μ΄κ²μ΄ getApplySemigroup
μ½€λΉλ€μ΄ν°κ° μλνλ λ°©μμ
λλ€.
μλ¬Έμμλ
Option/getApplySemigroup
,semigroupSum
, λ₯Ό μ¬μ©νλΌκ³ μμ±λμ΄ μμ§λ§, μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μμΌλ©°Apply/getApplySemigroup
,SemigroupSum
λ₯Ό μ¬μ©νλ©΄ λ©λλ€.
import { SemigroupSum } from 'fp-ts/lib/number';
import { getApplySemigroup } from 'fp-ts/lib/Apply';
import { Apply } from 'fp-ts/lib/Option';
const S = getApplySemigroup(Apply)(SemigroupSum);
μμ±ν Option
νμ
μ μ§μνλ Semigroup
μ μλμ κ°μ΄ ν
μ€νΈν μ μμ΅λλ€.
describe('Optionνμ
μ μ§μνλ appliedSemigroup μΈμ€ν΄μ€ ν
μ€νΈ', () => {
let result;
it('appliedSemigroup ν
μ€νΈ (some + none)', () => {
result = appliedSemigroup.concat(some(1), none);
expect(result).toBe(none);
expect(isNone(result)).toBeTruthy();
});
it('appliedSemigroup ν
μ€νΈ (some + some)', () => {
result = appliedSemigroup.concat(some(1), some(2));
expect(result).toMatchObject(some(3));
expect(isSome(result)).toBeTruthy();
});
});
some
κ°μ²΄μ none
κ°μ²΄λ₯Ό λν κ²½μ° none
μ λ°ννλμ§ νμΈνκ³ some
κ°μ²΄μ some
κ°μ²΄λ₯Ό λν κ²½μ° λ some
κ°μ²΄μ value
κ° λν΄μ§ some
κ°μ²΄κ° λ°νλλμ§ νμΈν©λλ€.
Semigroup
μ΄ μ¬λ¬ λ°μ΄ν°λ₯Ό νλλ‘ βμ°κ²°β, βλ³ν©βλλ βκ²°ν©βνκ³ μΆμ λ λμμ΄λλ κ²μ 보μμ΅λλ€.
λ§μ§λ§ μμ (Fantas, Eel, Specification 4 : Semigroupμμ μμ λ¨)λ‘ λͺ¨λ λ§λ¬΄λ¦¬νκ² μ΅λλ€.
μλμ κ°μ κ³ κ° μ 보λ₯Ό μ μ₯νλ μμ€ν μ ꡬμΆνλ€κ³ κ°μ ν΄ λ³΄κ² μ΅λλ€.
interface Customer {
name: string;
favouriteThings: Array<string>;
registeredAt: number;
lastUpdatedAt: number;
hasMadePurchase: boolean;
}
μ΄λ€ μ΄μ λ‘λ κ°μ μ¬λμ λν μ€λ³΅ κΈ°λ‘μ΄ μκΈΈ μ μμ΅λλ€. μ°λ¦¬μκ² νμν κ²μ Semigroup
μ΄ νλ λ³ν© μ λ΅μ
λλ€.
μλ¬Έμμ μ¬μ©νλ ν¨ν€μ§ μ€ μ΅μ λ²μ μ fp-tsμμλ deprecated λμ΄ μλ κ²μ΄ λ§μ μλμ λͺ©λ‘μΌλ‘ μμ±νκ² μ΅λλ€.
Semigroup/getStructSemigroup
: Semigroup/struct
Semigroup/getJoinSemigroup
: Semigroup/max
Semigroup/getJoinSemigroup
: Semigroup/min
Semigroup/semigroupAny
: boolean/SemigroupAny
Ord/ordNumber
: number/Ord
import { Semigroup, struct, max, min } from 'fp-ts/lib/Semigroup';
import { getMonoid } from 'fp-ts/lib/Array';
import { Ord } from 'fp-ts/lib/number';
import { contramap } from 'fp-ts/lib/Ord';
import { SemigroupAny } from 'fp-ts/lib/boolean';
const semigroupCustomer: Semigroup<Customer> = struct({
// λ κΈ΄ μ΄λ¦μ μ μ§νλ€.
name: max(contramap((s: string) => s.length)(Ord)),
// νλͺ©μ μΆμ νλ€.
// getMonoidλ Semigroupμ μν `Array<string>`μ λ°ννλ€.
favouriteThings: getMonoid<string>(),
// κ°μ₯ μ΄μ μ λ μ§λ₯Ό μ μ§νλ€.
registeredAt: min(Ord),
// κ°μ₯ μ΅κ·Όμ λ μ§λ₯Ό μ μ§νλ€.
lastUpdatedAt: max(Ord),
// λΆλ¦¬λ boolean Semigroup
hasMadePurchase: SemigroupAny,
});
μμ±ν semigroupCustomer
μΈν°νμ΄μ€λ μλμ κ°μ΄ ν
μ€νΈν μ μμ΅λλ€.
describe('Semigroup μΈν°νμ΄μ€λ₯Ό ꡬνν semigroupCustomer μΈμ€ν΄μ€ ν
μ€νΈ', () => {
it('semigroupCustomer μΈμ€ν΄μ€ concat ν¨μ ν
μ€νΈ', () => {
expect(
semigroupCustomer.concat(
{
name: 'Giulio',
favouriteThings: ['math', 'climbing'],
registeredAt: new Date(2018, 1, 20).getTime(),
lastUpdatedAt: new Date(2018, 2, 18).getTime(),
hasMadePurchase: false,
},
{
name: 'Giulio Canti',
favouriteThings: ['functional programming'],
registeredAt: new Date(2018, 1, 22).getTime(),
lastUpdatedAt: new Date(2018, 2, 9).getTime(),
hasMadePurchase: true,
}
)
).toMatchObject({
name: 'Giulio Canti',
favouriteThings: ['math', 'climbing', 'functional programming'],
registeredAt: new Date(2018, 1, 20).getTime(),
lastUpdatedAt: new Date(2018, 2, 18).getTime(),
hasMadePurchase: true,
});
});
});
semigroupCustomer
μΈν°νμ΄μ€μ concat
ν¨μλ μ λ¬λ λ κ°μ Customer
νμ
κ°μ²΄λ₯Ό λ³ν©νλ€. name
μμ±μ λ μ€ λ κΈ΄ κ²μΌλ‘ μ μ§νκ³ , favouriteThings
μμ±μ λ μμ±μ ν©μΉλ€. registeredAt
μμ±μ λ μ€ λ μ΄μ μ μκ°μ μ μ§νλ©° lastUpdatedAt
μμ±μ μ΅κ·Ό μκ°μ μ μ§νκ³ hasMadePurchase
μμ±μ true
κ° μμΌλ©΄ true
λ‘ μ μ§ν©λλ€.
λ°λΌμ μ£Όμ΄μ§ 쑰건μ λ§κ² Customer
νμ
κ°μ²΄κ° λ³ν©λμλμ§ νμΈν μ μλ€.
getMonoid
ν¨μλ Array<string>
μ λν Semigroupμ
λ°νν©λλ€. μ€μ λ‘ monidλ Semigroup
μ΄μμ κ²μ λ°νν©λλ€.
κ·Έλμ monoidλ 무μμΌκΉμ? λ€μ ν¬μ€νΈμμλ Monoidsμ λν΄ μ΄μΌκΈ°νκ² μ΅λλ€.