• Stream 이란 간단히 얘기해 데이터의 흐름 입니다. ioStream은 자바에서는 굉장히 많이 사용되는 개념으로, 아래처럼 read()등을 사용하여 단위 정보를 읽어올 수 있는데요,
      public static void main(String args[]){
          try{
              FileInputStream in = new FileInputStream("D://somewhere");
              byte[] input = new byte[5];
    
              for(int i=0; i<input.length; i++){
                  int b = in.read();
                  if(b == -1)
                      break;
                  // rest of the logic...
              }
          }
      }
    

    (아아 자바여..) ReasonML에서 Stream을 사용한 예를 통해 감을 잡아보겠습니다.

  • 여기를 보면 next를 하면 자료형이 바로 나오고 peek을 하면 option(‘a)로 감싸져서 나오네요. Stream.from(f)는 f에 의해 생성된 요소를 Stream안에 넣고 Stream을 반환합니다.

  • 아래의 코드는 1부터 순차적으로 수를 읽다가, limit이 100이 되었을 때 멈추는 동작을 합니다. 링크 의 Day06.re를 참고했습니다.
let init = ref(1);
let limitFilter = s => {
  let rec go = () => {
    let v = Stream.next(s);
    Js.log2("v=>",v);
    switch (v^ >= 100) {
      | false => go()
      | true => {
        Js.log("Reached 100!") 
      }
    };
  };
  go();
};

let numberStream = input => {
  let state = input;
  let next = _ => {
    state :=  state^ + 1;
    Some(state);
  };
  Stream.from(next);
};

let answer = numberStream(init)->limitFilter->Js.log;
....
v=> { contents: 96 }
v=> { contents: 97 }
v=> { contents: 98 }
v=> { contents: 99 }
v=> { contents: 100 }
Reached 100!
  • 한번 흐름을 따라가보겠습니다.
  • let answer = 로 시작하는 pipe operator들을 보면 (1) init으로 시작된 Stream을 (2) limitFilter로 걸러서 (3) Js.log로 출력하라 라고 한 것을 알 수 있습니다.
  • 여기서 중요한 점은 Stream을 생성하는 로직과 Stream의 값을 필터링하는 로직이 분리되어 있다는 것입니다.
    let numberStream = input => {
    let state = input;
    let next = _ => {
        state :=  state^ + 1;
        Some(state);
    };
    Stream.from(next);
    };
  • Stream.from은 Stream이 어떻게 다음(next) 값을 얻어내는지를 정의하는 부분입니다. ReasonML의 모든 let 변수들은 immutable하기 때문에(‘변’수는 아니지만) ref를 사용해서 update할 수 있도록 했습니다. 단순히 input된 값에서 1을 더해서 넣고 있습니다.
    let limitFilter = s => {
    let rec go = () => {
        let v = Stream.next(s);
        Js.log2("v=>",v);
        switch (v^ >= 100) {
        | false => go()
        | true => {
            Js.log("Reached 100!") 
        }
        };
    };
    go();
    };
  • limitFilter는 안에서 go() 라는 재귀함수를 호출하며 내부에서 Stream.next(s)가 뱉어낸 값이 100 이상인지를 검사합니다. 아니라면 ( false ) 계속해서 찾고, 맞다면 로그를 출력하도록 했습니다. 근데 위의 어느부분에서 다음 수를 생성하고 있는 것일까요?
let v = Stream.next(s);

이 부분입니다. Stream.from(next)에서 다음 값에 대한 정의가 있었기 때문에 Stream.next(s)는 이전 상태에서 발전한 다음 값을 리턴하게 됩니다. ^^

  • 이번 ReasonML에서 Stream을 사용하는 모습은 마치 ES6의 generator를 떠올리게 합니다. 아래는 실제 generator JavaScript코드 입니다.
    function * naturalNumbers() {
    let num = 1;
        while (true) {
            yield num;
            num = num + 1
        }
    }
    const numbers = naturalNumbers();
    console.log(numbers.next().value)
    console.log(numbers.next().value)
  • 매우 흡사하죠? (근데 훨씬 간단하죠?) 그럼 오늘은 여기까지 하겠습니다.