Side Effect를 다뤄야 할 때, 지연평가를 주의하자

easbui·2023년 7월 5일
0

Clojure를 학습하는 겸, Toy Project로 콘솔 입출력을 통한 오목게임을 구현하고 있다.

문제는 바둑판(게임 보드라 하는 것이 더 정확할 수도 있겠다)을 출력하는 함수를 작성하는 과정에서 발생하였다.

먼저 문제가 발생한 코드이다.

(defn print-board [black-checked white-checked boundary]
  (println "[BOARD]")
  (println (apply str "#" (range boundary)))
  (for [r (range boundary)] ;; r은 row num, c는 col num
    (println (apply str r (map (fn [c] (cond
                                         (contains? black-checked (conj `() c r)) "■"
                                         (contains? white-checked (conj `() c r)) "□"
                                         :else "*")) (range boundary))))))

위 함수는 바둑판을 아래와 같은 형식으로 출력 하는 것을 목표로 하고 있다.

[BOARD]
#012345
0******
1******
2******

그러나 정작 함수의 출력은
#012345 까지 밖에 출력되지 않았다. 아래의 for 함수부분이 무시된 것이다.

원인은 Lazy Evaluation에 있었다. print-board 함수를 평가하면 콘솔 출력은 결국 Side Effect이기 때문에, 결과 값은 (nil nil nil ... ) 과 같은 별 의미없는 결과가 되는데, 이후 프로그램에서 이 결과 값을 사용하는 일은 없다.

따라서 지연 평가를 사용하는 Clojure REPL가 이 의미없는 결과를 평가할 일은 없게 되고, 평가되지 않으니 결국 Side Effect 또한 발생하지 않는다.

결국 Lazy Evaluation을 피하기 위해선, 명시적으로 Evaluation을 진행해야 하는데 doall 혹은 doseq를 통해 그것이 가능하다. 그중 doseq는 for 함수와 시그니처가 같기 때문에 위 코드에서 for를 doseq로 치환해주면 의도했던 대로 출력이 된다.

함수형 프로그래밍 패러다임에선 Side Effect가 없는 순수함수를 추구한다. 그러나, 순수함수로만 이루어진 애플리케이션 프로그램은 거의 불가능에 가깝다. 대부분의 애플리케이션 프로그램은 시스템이나 네트워크, 사용자와 교류(?)하게 되는데 이들은 모두 '상태'를 가정하기 때문이다. 따라서, Clojure를 비롯한 함수형 프로그래밍에선 부수효과를 얼마나 철저하게 관리하는가가 중요해지는 것이다.

참고

킹갓오버플로우 - Why isn't my code printing like it should?

profile
개발자 - 프로그램을 개발새발짜는 사람

0개의 댓글