프로그래밍

#5 state 모나드

암것두몰러유 2022. 4. 10. 01:40

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