Either는 받은 값을 리턴으로 전달 할 뿐입니다.
전 글에 작성된 예제를 가져와 다시 이야기를 이어가도록 하겠습니다.
const dataFunction = () => {
const names = ["kim", "na", "park", "lee"];
const findResult = names.filter((name) => name === "hong")
if(findResult.length) return "exist";
else return "empty";
}
const checker = Either.check(dataFunction()) // 이 값을
((value) => value === "exist" ? true : false) // 이 함수에 넣어 실행한 결과를
((value) => value ? Either.right(value) : Either.left(value)); // 이 기준에 따라 가른다
checker
.catch((value) => console.log("찾는 값이 없음!"))
.map((value) => ({ nameLength: value.length }))
.flatten()
Either는 앞의 함수에서 전달한 값을 사용하고 다음으로 넘겨주기만 합니다. 이런 상황에서 모나드 전체에서 사용할수 있는 상태가 필요할 수도 있습니다. 어떻게 구현하면 좋을까요?
Either.right({value: '...', state: {}}).map((value) => {
return value;
})
이렇게 해도 해결 가능 합니다. 그러나 이 방법은 다른 Either 모나드를 chain 할 때 새로운 Either 모나드의 값에 따라 기존의 EIther 모나드의 값이 유실되거나 변형되는 문제가 생깁니다. 개념적으로 우리가 구분을 했지만 하나의 값일 뿐이기 때문 입니다.
Either.right({value: '...', state: {}}).map((value) => {
return value;
}).
chain(()=> Either.right({value: '...', state: [])); // 이 전의 state는 없어진다.
개념적인 구분 뿐 아니라 코드로도 분리가 되어 있으면 좋을 것 같습니다.
Either에 추가 하면 좋을까?
Either에 추가하면 될런지 아래 내용을 고민해 보았습니다.
- state 변수를 추가하고 map, catch, check 함수에 state를 전달하게 수정 합니다.
- value만 주던 flatten을 state도 같이 주도록 수정 합니다.
- check의 checkFunction에 state를 넘겨줘야 합니다.
- 기존 state와 새로운 state를 어떻게 처리할지는 직접 작성하여 넘겨줘야 합니다.
- left도 map이 동작해야 합니다.
- 원하는 값이 아니더라도 다음 함수에게 상태를 반드시 전달 해야하기 때문입니다.
- catch도 value, state 에 대한 catch로 나뉘어야 할 것 같습니다.
위의 목록대로 코드를 수정하면 left, right의 코드 차이가 없어집니다.
다음 함수에게 상태 값을 전달해줘야 하기 때문에 left, right 둘다 같은 인터페이스를 갖고 있어야 하기 때문입니다. 이렇게 되었을 때 left, right가 서로 다른건 constructor 프로퍼티 입니다.
사실 차이가 없는데 억지로 나눠 놓은 꼴이 됩니다. 그래서 Either에 추가 할 수 없다는 결론을 내렸습니다.
Either 모나드는 어떤 값을 다루는 모나드로 적합한 것 같습니다.
State 모나드
State 모나드는 값과 상태를 갖고 있습니다. 값과 상태와 관련된 함자들을 갖고 있는 모나드 입니다.
const State = (f = null) => {
const _runState = f;
return {
put: (newState) => { // 상태를 set 합니다.
return State(() => {
if(_runState){
const prev = _runState();
return { value: prev.value, state: newState };
}else{
return { value: undefined, state: newState }
}
})
},
modify: (f) => {
return State(() => { // 상태를 수정합니다.
if(_runState){
const prev = _runState();
return { value: prev.value, state: f(prev.value, prev.state) };
}else{
return { value: undefined, state: f()}
}
})
},
apply: (f) => {
return State(()=>{ // 값을 수정합니다.
if(_runState){
const prev = _runState();
return { value: f(prev.value, prev.state), state: prev.state };
}else{
return { value: f(), state: undefined}
}
})
},
eval: () => { // 작성된 코드를 실행합니다.
const result = _runState()
return result;
},
chain: (f)=>{
return State(()=>{
if(_runState){
const prev = _runState();
return f(prev.value, prev.state).eval();
} else{
return f().eval();
}
})
}
}
}
아래의 예제 코드를 보며 설명하도록 하겠습니다.
예제
State().put('Hello World')
.modify((value, state)=> {
/**
value : undefined,
state: 'Hello World'
**/
return ["홀롤롤로"];
})
.apply((value, state)=>{
/**
value : undefined,
state: ['홀롤롤로']
**/
return 1;
})
.chain((value, state)=>{
/**
value : 1,
state: ['홀롤롤로']
**/
return State().put({
arr: state,
length: state.length
})
})
.eval() // {value : undefined (새로운 State가 넘어왔으니까), state: { arr : ['홀롤롤로'], length: 1 }}
State의 모든 함자에서 값과 상태값을 동시에 받을 수 있지만 업데이트 하는것은 함자별로 다 다르게 되었습니다. 위키백과에 State 모나드의 정의가 있던데 정의대로 잘 만들었나 모르겠습니다만. 이것이 State 모나드 입니다.
(이 글은 제 과거 블로그 글을 다시 정리하여 쓴 글입니다. 지식이 일천하여 틀린곳이 있을 수 있습니다.)
'프로그래밍' 카테고리의 다른 글
frontend framework에 대한 생각 (1) | 2022.08.21 |
---|---|
#6 맺으며 (0) | 2022.04.10 |
#4 부수효과 (0) | 2022.04.08 |
#3 maybe 모나드와 either 모나드 (0) | 2022.04.08 |
#2 함수의 합성 (0) | 2022.04.07 |