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/structSemigroup/getJoinSemigroup: Semigroup/maxSemigroup/getJoinSemigroup: Semigroup/minSemigroup/semigroupAny: boolean/SemigroupAnyOrd/ordNumber: number/Ordimport { 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μ λν΄ μ΄μΌκΈ°νκ² μ΅λλ€.