Skip to main content

Command Palette

Search for a command to run...

Scala로 하는 Side-effect 다루기 기초

Updated
•5 min read•View as Markdown
Scala로 하는 Side-effect 다루기 기초
W

내가 가진 자신감은 무지에 기반했다

Side Effect? Pure Function?

함수형 프로그래밍을 하다보면 Pure Function과 Composition 이라는 용어를 자주 마주치곤 한다. 도대체 이 용어들은 뭘 말하는걸까?

코딩을 하다보면 아래 행위들을 수행하는 코드를 종종 작성하게 되는데 이러한 행위들이 부수효과(Side-effect)라는 것은 이미 잘 알고 있다.

  • 데이터베이스에 데이터를 READ, UPDATE, DELETE 한다.

  • 다른 서비스에 RESTful API 호출을 한다.

  • 현재 들고 있는 데이터를 HDFS에 쓴다.

  • 디버깅을 위해 데이터를 파일이나 콘솔에 로깅한다.

부수효과 자체가 나쁘다는건 아니다. 근데 왜 사람들은 부수효과를 효율적으로 다뤄야 된다는 말을 하고 어떤 사람들은 부수효과를 없애야 한다고 하는걸까? 부수효과를 효율적으로 다뤄야되는게 뭘까? 도대체 부수효과를 어떻게 다뤄야 효율적인걸까?

여기 콘솔에 데이터를 출력하는 코드를 살펴보자. 앞서 말했듯이 이것은 부수효과다.

부수효과가 있는 함수(printSystem)를 그냥 실행하는 것에는 별문제가 없어 보인다.

그러나 부수효과가 프로그램 전체에 영향을 끼치지 않게 하기 위해 부수효과의 예외 상황을 컨트롤 해야 할 수도 있다. 즉 특정 함수 내부에 있는 부수효과의 실행을 외부에서 제어할 수 있게 하는 것인데 이런 경우 그 함수를 호출하여 부수효과를 구성하는 일을 진행해도 부수효과는 실행되지 않고 내가 원할 때 실행할 수 있게 된다.

결국 부수효과를 값으로 취급하는 함수로 탈바꿈 시키면 그 함수는 더 이상 부수효과가 있다고 하지 않게 된다. 예제에 대입하자면 printSystem 함수는 안에 부수효과(println)가 있는 함수이지만 printSystem 내부에 있는 부수효과를 값으로 취급 및 반환하여 부수효과 실행을 외부로 제어권을 넘기면 printSystem 함수는 더이상 부수효과가 있는 함수라고 하지 않는다. (이를 Effectful function 혹은 Pure Function이라고 부른다)

부수효과 실행을 지연 시켜서 부수효과를 프로그래머가 관리할 수 있도록 하는게 함수형 프로그래밍에서 말하는 effectful system 이다.

그럼 printSystem 함수 내부에 있는 부수 효과인 println를 값으로 취급하여 pritnSystem 함수에 부수효과를 없애보자. 여기서 손쉽게 쓸 수 있는게 scala.concurrent.Future 인데 Future로 부수효과를 감싸보자.

여기서 Future(println("Launch missiles"))라는 로직은 동일하니 하나로 생략해보자.

콘솔 출력 결과는 동일할 거라고 생각하지만 println은 한 번만 실행된다.

앞에서 Future를 새로 만들어서 합성하는 경우에는 기대한대로 결과가 나왔지만 코드를 줄이기 위해 Future를 재사용한 경우에는 기대한 것처럼 동작하지 않는다. 왜 그럴까?

일단 Future는 Eager Evaluation 이라서 launch.flatMap 함수 내부에 있는 launch는 Eager evaluation된 결과값으로 치환되어 버린다. 즉 Unit으로 치환되어 버리고 println 은 실행되지 않는 것이다. 결국 리팩토링으로 인해 부수효과가 기대한대로 동작을 하지 않아 부수효과를 제대로 다루지 못한 경우라고 볼 수 있다.

이러한 상황을 피하기 위해 부수효과를 Lazy Evaluation 할 수 있는 값으로 치환해야 위와 같이 리팩토링를 해도 우리가 원하는대로 동작하는 Pure Function을 만들 수 있게 되는 것이다.

Lazy Evaluation되는 Pure Function을 만들어보자

Lazy Evaluation을 하기 위해 스칼라에는 call-by-name 이라는 특징이 있다. (함수값이나 thunk가 여기에 해당된다) Pure Function을 만들기 위해 부수효과를 값으로 취급할 때는 call-by-name을 이용하여 실행을 지연 시키면 된다.

이게 무슨 말인지 모르겠으니 코드로 살펴보자.

여기서 LazyIO가 부수효과를 Lazy Evaluation 할 수 있는 자료형이라고 볼 수 있다. 바로 LazyIO case class 필드에 runEffect와 컴패니언 오브젝트의 io 메소드를 살펴보면 effect 라는 파라미터가 있는데 이 파라미터들은 함수이다.

결국 LazyIO 자료형은 부수효과를 실행하는 함수를 값(runEffect, effect)으로 취급하여 그 실행을 외부로 넘겼다. 또한 map, flatMap 함수를 이용하여 합성까지 할 수 있도록 되어 있다. 이렇게 값으로 취급된 부수효과를 내가 원하는대로 합성하는 Pure Function을 함수형 프로그래밍에 그토록 열광하는 이유라고 본다.

위 코드에서 twoRuns.runEffect() 함수를 호출하게 되면 어떻게 될까? 그 과정을 살펴보면 다음과 같다.

  • 먼저 io, 즉 LazyIO.io(println("Hello, world!"))를 살펴보면 LazyIO 오브젝트에 io 라는 함수의 파라미터(effect: ⇒ A)로 println("Hello, world!") 이라는 thunk를 전달했다.

  • 그 다음 io.flatMap(...)을 살펴보면 _ ⇒ io 라는 함수를 flatMap의 파라미터로 전달했다.

  • LazyIO.flatMap 내부에선 어떤 일이 펼쳐질까?

  • 우선 LazyIO 오브젝트의 io 함수는 thunk 이기 때문에 즉시 실행이 되질 않는다. 따라서 fn(runEffect()).runEffect() 이 부분은 즉시 실행이 되질 않는다.

  • 이제 fn(runEffect()).runEffect()는 LazyIO 오브젝트의 io 함수의 파라미터로 넘겨지게 되는데 넘겨받은 파라미터는 LazyIO 케이스 클래스에 멤버 필드, 즉 함수 값으로 전달된다. (아래 코드를 설명하고 있는 것임)

  • 여기서 fn(runEffect()).runEffect()을 다시 살펴보자.

  • fn(runEffect())의 반환값 자료형을 살펴보면 LazyIO[B] 이다.

  • fn(runEffect())를 다시 쓰면 fn((() ⇒ println("Hello, world!"))()) 가 되며 () ⇒ println("...") 이 부분은 첫 번째 io의 멤버 필드를 의미한다. (괄호가 좀 많아서 자세히 봐야 됨)

  • 그런데 지금 설명하고 있는 영역은 thunk 영역이기 때문에 fn의 파라미터 (() ⇒ println("..."))()은 즉시 실행이 되질 않는다.

  • fn을 주목하자. fn을 풀어서 쓰면 _ ⇒ io를 의미한다. 그런데 여기서는 fn(...) 이기 때문에 LazyIO[Unit] 이라고 볼수 있다.

  • 결국 fn(...).runEffect()는 위에서 말한 LazyIO[Unit].runEffect() 라고 볼 수 있으며 마찬가지로 아직 thunk 영역이기 때문에 runEffect()를 호출한 것처럼 써있지만 아직 실행되지는 않는다.

  • 마지막에 twoRuns의 runEffect()를 호출하게 되면 위 과정에서 합성된 효과들이 한꺼번에 실행이 된다.

위와 같은 과정으로 인해 부수효과는 실행되지 않은채 LazyIO[Unit] 혹은 LazyIO[(Unit, Unit)] 이라는 값으로 취급되며 언제든지 필요할 때 Lazy Evaluation을 할 수 있게 된다.

Next Level: Cats Effect

Scala 생태계에 LazyIO와 같은 컨셉을 갖춘 Cats Effect 라는 라이브러리가 있다. Cats Effect의 IO 라는 클래스가 LazyIO와 비슷하다고 할 수 있는데 Cats Effect를 이용하여 부수효과를 핸들링하는게 직접 구현한거보다 더 많은 기능을 제공하고 있고 Cats 생태계를 이용하기에도 편할 것 같다.

unsafeRunAndForget 이라는 메소드가 좀 거슬리니 Cats Effect에서 제공하는 IO 핸들링 메커니즘을 이용하자.

하나 더 덧붙이자면 이렇게 부수효과를 합성 가능한 값으로 다루는 자료형인 IO를 모나드라고 부른다. 위에 코드에서는 IO 모나드만 반환했는데 LazyIO.runEffect를 호출하듯이 IO 모나드를 실행하는 외부는 어디일까?

위에 코드에는 다 표현되지는 않았지만 twoRuns는 IO[Unit] 타입이다. 이걸 Future로 바꾸는 implicit 함수가 이미 EffectTestSupport 라는 트레이트에 선언되어 있어서 이게 작용하게 된다.

Future로 변환하는 과정에서 unsafeToFuture 하는 메소드를 호출하게 되는데 Future로 변환하려면 어쩔 수 없이 이 메소드를 호출해야 되나보다. 이 다음은 org.scalatest.funsuite.AsyncFunSuite 로 인해 비동기로 검증 작업을 진행하게 된다. (이 부분 부터는 Scalatest 영역이라 설명 생략)

다음 할 일

  • 그렇다면 기존에 scala.concurrent.Future로 구현된 코드에 Cats Effect를 적용하여 부수효과를 효율적으로 핸들링 해야 한다면 어떻게 해야될까? 여기 이에 대한 괜찮은 아티클이 하나 있던데 좀 더 공부해보자. https://www.innoq.com/en/blog/functional-service-in-scala/#fn:2

  • Cats Effect의 IO를 다룰 때 Resource로 감싸서 사용하던데 이 때 Higher Kinded Type을 사용한다. 같이 조사해보자.

참고 자료

More from this blog

liftIO 2022 발표하고 나서 Pierre Ricadat을 만났던 후기

이렇게 될 줄 알았을까? liftIO 2022에서 발표한 게 이런 후폭풍을 몰고 올 줄 난 알았을까? 발표가 끝난 후 며칠이 지나자 라스칼라 코딩단에서 누군가 영어로 내 발표를 잘 봤다는 짤막한 인사와 ZIO, ZPure를 살펴보라고 조언을 해주는 게 눈에 띄었다. 처음엔 친절한 외국인이겠거니 생각했는데 이름을 자세히 살펴보니 Pierre Ricadat 이었다!! 너무 놀라서 되지도 않은 짤막한 영어 몇 마디 나눈 뒤 바로 만나자고 점심 약속을...

Dec 7, 20222 min read
liftIO 2022 발표하고 나서 Pierre Ricadat을 만났던 후기

liftIO 2022 컨퍼런스 발표자 참석 후기와 Lazy Evaluation에 대한 못다 한 답변

올해 liftIO 2022는 발표자로 참석하였다. https://festa.io/events/2876 liftIO 발표자 참석은 작년에 나의 작은 소망이었는데 liftIO가 올해도 개최된다는 소식을 듣고 약간의 흥분과 용기를 머금고 발표자 신청하게 된 것이다. 주위의 응원과 기대 속에 나조차도 내가 무슨 말을 하게 될지 기대가 되었던 발표 준비를 했던 것 같다. 컨퍼런스 주제는 함수형 프로그래밍에 대한 성과였지만 나 잘났다고 혼자만 떠드는 ...

Dec 3, 20223 min read
liftIO 2022 컨퍼런스 발표자 참석 후기와 Lazy Evaluation에 대한 못다 한 답변

Dev Dive 2022 함수형 개발자로 성장하기 Backend Day 참석 후기

그린랩스 CTO 발표 돌이켜 보면 첫 발표는 아래 두 아티클에 대한 요약과 함수형 프로그래밍에 대한 앞으로의 바람에 대해 얘기를 했던 것 같다. Why Functional Programming Matters - John Hughes 1990 WHY Functional Programming Should Be the Future of Software Development 개발자가 다루는 문제에 대한 핵심은 문제를 분해하고 조립할 수 있는 프...

Nov 9, 20225 min read

스프링의 Psa 개념을 이해했는지 확인하기 + 스칼라, Fp 버전으로 바꿔보기

참고로 이 글을 특정 유튜버나 인터넷 강의를 홍보하려고 쓴 글이 아니며 본문에 나오는 유튜브 영상 저자와 저는 전혀 관련이 없음을 밝힙니다. 스프링을 제대로 공부했는가? 확인해보자. 얼마 전 유튜브를 보던 중 '스프링 제대로 공부했는지 5분 안에 확인하는 방법'이라는 제목의 영상이 눈길을 끌었다. 뭘까 궁금해서 봤는데 결론은 스프링과 리팩토링 인터넷 강의를 홍보하는 영상이었다. 영상 내용은 특정 문제를 제시하고 스프링을 제대로 공부했다면 풀 수...

Feb 17, 20226 min read
스프링의 Psa 개념을 이해했는지 확인하기 + 스칼라, Fp 버전으로 바꿔보기

Will Lee

10 posts