Effect

Posted on September 3, 2022

미완성 문서이지만 공개합니다. 틀린 부분이라 생각되면 꼭 댓글 부탁드립니다.

하스켈(또는 함수형)을 공부하면서 가장 시작 단계에서 만나게 되고, 그 후로도 계속 중요한 역할을 하는데, 정작 Effect가 뭔지 다루는 글이 드뭅니다. 입문자들이 이해할 수 있는 글을 아직 본 적이 없습니다. 이렇게 기초적이고 중요한 개념의 구체적 정의를 찾기 힘들다는 게 이상하단 생각이 듭니다. 아마도 정규 교육을 받을 때 보는 텍스트(교재)들에는 분명 있을텐데, 너무 쉬워서 그런가 웹에서 가볍게 설명하는 텍스트는 없습니다.

주의) 이 글은 충분한 자료(인터넷에 공개된 자료)를 찾지 못해, 일부는 제 의견과 상상으로 채운 글이니, 댓글로 여러 의견들이 오갔으면 좋겠습니다.

어디서 힌트를 얻어야 할지 난감할 때, @Ailrun님의 자세한 설명이 많은 도움이 되었습니다. 감사드립니다. 혹 틀린 내용이 있다면, 분명 제가 잘 못 이해하고 기록한 것입니다.

영단어 뜻

Effect가 뭘까요? 함수형을 공부하다 보면, 절차형(명령형)은 별 신경쓰지 않고 해결하던 문제들을 Effect개념을 써서 복잡하게 해결하는 모습을 자주 만나게 됩니다. 인터넷 검색을 해도 쉽게 답이 나오지 않습니다. 다들 당연히 알고 있고, 저만 모르는 용어인가 싶습니다.

일단 원래 단어가 가지고 있는 뜻을 보겠습니다.

영단어 뜻 :

Effect
- something that inevitably follows an antecedent (such as a cause or agent)
- to cause to come into being
- a change that results when something is done or happens :an event, condition, or state of affairs that is produced by a cause

Merriam-Webster

우리말로는 영향, 결과, 효과등으로 번역합니다. 제가 주목하는 뜻은 먼저antecedent 일어난 어떤 결과가 다음에 영향을 미친다입니다.

세상사를 함수의 합성Composition으로 표현한다

세상 일을 정확히 원자적atomic으로 나누어서 보긴 어렵겠지만, 모델링을 위해 어떤 단위로 구분했다고 보겠습니다. 그럼 현재 일어난 일이, 다음 일에 영향을 미치며 세상이 굴러간다고 볼 수 있습니다. 세상에서 일어나는 일을 모두 함수로 표현하려면 어떻게 할까요? 수학 함수는 매개 변수로 받은 입력에 항상 일정한 출력을 내뱉습니다. 함수형에서 pure 함수라 부르는 것들도 마찬가지입니다. pure 함수로만 체이닝(연속된 합성)을 해서 세상사를 표현하려면, 이전 함수가 만들어 낸 결과 중 다음 함수가 필요한 정보는 모조리 다음 함수의 입력으로 전달 받아야 합니다.

잠시, 명령형(또는 절차형)에서 다음 작업들을 매개 변수를 통하지 않고, 어떻게 처리하는지 생각해 봅시다.

환경 값이나, 키보드로 타이핑한 값들은 특별히 함수의 입출력으로 받지않고 해결했습니다.

하지만, 순수 함수가 외부와 소통하는 방법은 오직 입출력 뿐입니다. 연결된 순수 함수에서 이전 함수가 만들어 낸 무언가가 있는데, 다음 함수가 필요한 정보가 있다면, 함수 입출력에 드러내지 않을 수 없습니다.

세상사가 다 Effect다 (넓은 의미)

함수 연결에서 이전 함수의 결과를 다음 함수가 받아다 쓰면 Effect라 볼 수 있습니다. 영단어 뜻으로 본다면 세상 모든 일이, 함수의 합성이 모두 Effect의 연속입니다.

분명 side란 말을 붙였으면, main 혹은 primary가 있다는 얘기입니다. Wikipedia.org에서 side effect 설명을 보면

if it has any observable effect other than its primary effect of returning a value to the invoker of the operation.

side effect를 설명하기 위해 primary effect란 말을 쓰고 있습니다.

함수 실행 결과는 항상 다음 번에 일어날 일에 영향Effect을 주니, 항상 Effect는 존재하며, 그 중 primary effect가 아닌 걸 side effect라 불렀다는 말입니다. primary effect는 함수 입출력에 드러나는 걸 말합니다. 아래 섹션에서 더 이어 가겠습니다.

프로그래밍에서의 Effect (좁은 의미)

하지만, 함수형 프로그래밍에서는 특정한 경우만 Effect라 부릅니다. 넓의 의미 Effect 중 입출력에 드러나지 않는 것들만 Effect라 부릅니다. 다음 작업에 영향을 주려면, 그냥 다음 작업이 이전 작업의 출력을 입력으로 받으면 될텐데, 어떤 건 입출력으로 다루고, 어떤 건 Effect로 다룰까요?

Effect와 Context의 차이

Context 어원: text는 해석이 필요한 모든 정보를 가리키며, 글자는 text중 하나입니다.(통상 글을 텍스트로 알고 있지만, 그림, 영상, 소리, 건축… 뭐든 해석이 필요한 대상은 모두 text가 될 수 있습니다) con text는 text와 함께con 있는 text 해석을 도와주는 정보입니다.

좁은 의미론 프로그램에서 함수 실행을 위해 필요한 정보들 (입력값, caller, 결과 타입..등)을 context라 합니다.

일반적인 단어 뜻으로 보면 effect도 context의 하나이고, 좁은 의미로 보면 내포된 정보는 effect이고 context는 외연에 있는 정보입니다. (외연은 함수 서명을 의미하고, 내포는 그 외의 모든 것입니다.)

항상 어떤 대상, 동작이 Effect이거나 Context인 것이 아니라, 어떻게 다루냐에 따라 Effect 또는 Context로 부릅니다.

※ 하지만, 전공자분들과 대화하면 일반 단어 뜻으로 받아 들이지 않는 경우가 많습니다. 당장 함수를 실행하기 위해 필요한 정보들만 모아둔 것을 Context라 부릅니다.

Computational Effect

사전적으로는 컴퓨터가 계산하는 작업을 Computation으로 부릅니다. 튜링 머신으로 reduce되는 어떤 것이든 Computation이라 부릅니다. 1+1이란 식을 그대로 바라보면 Computation이라 부르지 않지만, 이 식을 컴퓨터에 넣어 결과를 가져오기 위해, 컴퓨터에 준비한 1+1은 Computation이라 부릅니다. Computation으로 인해 생기는 정보를 Computational Effect로 부릅니다. 검증 필요

Effectful 함수

effect를 만들어내는 함수, effect가 있는 타입을 다루는 함수

Effect 대수algebra

두 번의 computation으로 생기는 두 개의 effect를 합칠수 있는 연산 정의가 가능해야먄 모나드로 만들 수 있습니다. effect algebra가 가능한 effect만 모나드로 만들 수 있습니다.

왜 모나드가 Effect를 표현하는데 딱일까?

카테고리 이론에서는 펑터로 한 번 트랜스폼한 것과, 두 번 트랜스폼한 게 다른 정도를 같은 류로 만들어 주는 걸 모나드라 하는데, 이 모나드가 Effect를 처리하기에 가장 좋은 패턴일까요?

  1. 입출력을 통하지 않은 정보를 필요로 하는 작업
  2. 순수 함수로 표현하면, 타입에 자리 하나를 만들고, 관련 함수들이 이 타입들을 주고 받게 만든다.

1번을 Effect라 부르고, 2번 패턴으로 타입을 써서 구현하기 위해 Effect를 합치는 join이 필요합니다. 2번 모양으로 만들어 놓으면, 개념상으론 입출력에 없는 값에 접근하여 어떤 작업을 하는 것 같지만, 실제 구현은, 모나드로 안보이게 할 자리(타입으로 표현)를 만들어 매개 변수가 아닌 것에 접근 하는 것처럼 만듭니다. Monad가 아닌 Effect가 없다는 말은, 하스켈에서 Effect 표현은 모두 Monad로 된다는 말이라고 생각했었습니다. 그런데, 좀 더 추상화해서 생각하면 세상사를 pure함수로 표현한다면 모든 Effect는 join으로 계속 정보Effect를 합치며 유지하는 모나드와 같은 모양으로 볼 수 있다고 생각을 넓힐 수 있습니다.

유지니오 모기의 Effect 분류

Notion of computation and monads - Eugenio Moggi

위에 나열한 것들이 모두 내포의 예시입니다.

※ 유지니오 모기 교수가 effect를 해결하는데 모나드를 쓰자고 처음 얘기하고, 필립 와들러 교수가 하스켈에서 모나드를 써서 effect를 해결한 구현이 haskell198 표준에 채택되었다고 합니다.

Side-effect는 Effect중의 하나

어떤 논문은 Side-effect를 Computational Effect 전반을 가리키는 용어로 쓰고, 어떤 논문들은 Effect 중 mutation을 가리키는 용어로만 씁니다. 오래된 논문일수록 전자처럼 쓰고, 요즘은 후자로 자리잡아 가고 있다고 합니다.
Polymorphic effect systems (Online: 1988) - J. M. Lucassen, D. K. Gifford 여기서는 전자처럼 쓰이고 있습니다. 논문들을 읽을 때 알아서 잘 구별해서 읽어야 합니다.

추측
필요한 정보들을 모두 입출력을 통해 주고 받게 하면 Effect 없이 Main Computation으로 같은 목표를 달성할 수 있습니다. 이 전 용어를 쓴다면(넓은 의미에서는 모두 Effect), Main Computation을 Primary Effect라 부르고, 입출력에 드러나지 않게 처리하는 걸 Side-effect로 불렀습니다.

하스켈에서 유명한 Effect들

Maybe

Maybe Effect가 해결하는 작업은 “실패할 수도 있어”입니다. 무엇이 외연이고, 무엇이 내포일까요? 실패하지 않는 계산 g :: Int -> Int는 외연으로 동작이 모두 결정 됩니다. 이 연산이 실패할 수도 있다는 건, g만으로, 즉 외연만으로 표현이 안됩니다. 섬에 홀로 덩그러니 두면 뭔지 알 필요가 없습니다. 다른 것과 관계를 맺을 때 Effect가 필요하게 됩니다. 다른 것과 관계를 맺을 때, 즉 compose할 때 실패 여부를 판단하는 가드를 세우면 됩니다. 바인드가 바로 이 가드 역할을 하고, 바인드가 실패 여부를 처리하도록 한 구현을 Maybe라 이름 붙였습니다.

Reader

Reader Effect가 해결하는 작업은 “외부 스코프에 넣어 둔 환경값에 접근하는 작업”입니다. f :: env -> arg -> res, g :: env -> arg -> res 함수에 env를 넣어주는 작업이 외연에 드러나게 하지 않고, Wrapping 함수가 먼저 env를 한 번만 받아 fg에 각 각 넣어주도록 구현한 걸 Reader라 이름 붙였습니다.

State

State Effect는 “변하는 상태를 참조”를 해결합니다. Wrapping함수(Combinator, 하스켈 모나드에선 bind)가 상태를 매개 변수로 주고 받는 걸 담당하고, 안에 있는 함수는 이 값이 어디에서 왔는지 알 필요가 없습니다. 이렇게 구현한 걸 State라 이름 붙였습니다.

IO

State와 방식은 같고, 상태로 Realworld를 넣어주게 만들었습니다. 세상의 변화를 모두 가지고 있다는 설정이지만, 사실 실제 컴파일하면 Realworld몇(아마도 1바이트? 바이너리에선 필요 없는 정보로 0바이트로 표현되는, 컴파일 타임에만 필요한 표식같은 존재입니다. 하스켈 런타임에 프리미티브하게 Realworld에 관한 동작이 들어가 있다고 보시면 됩니다.

하스켈에선 Effect를 어떻게 표현할까?

하스켈의 함수는 모두 순수 함수입니다. Effect 동작을 할 수 없는 순수 함수는 어떻게 할까요?
당연히 다른 함수 하나를 갖다 대서, 순수하지 않은 부분을 맡기면 됩니다. 순수 함수를 두고, 그 순수 함수가 자체적으로 해결 못하는, 즉 Effect는 당연하게도 다른 고차 함수(컴비네이터)의 도움을 받아 해결합니다. 함수에 값을 넣어 줄 때, 그냥 넣어주지 않고, Effect에 맞는 작업을 컴비네이터와 타입 으로 해결합니다. 이 게 모나드가 하는 일입니다.

현재 Effect 정의로 생각하는 것

명시적으로(매개 변수를 통해서) 받지 않고, 결과로 내보내지 않는, 계산computation(혹은 evaluation)에 필요한 (내포intensional) 정보가 있는 작업을 effect가 있는 작업이라 합니다.

아래는 Effect 정의나 구체적 설명이 있는 텍스트를 찾는다는 말에, @Ailrun님이 인용해 주신 문장입니다.

… they(computaional effect) describe intensional information about what takes place during evaluation of the program unlike what was the case above …
Type and Effect Systems - Flemming Nielson & Hanne Riis Nielson
함수를 평가할 때 발생하는 내포적인 것들을 effect라 한다.

세상에 일어나는 일을 Pure 함수로 표현하려고 합니다. Pure 함수는 입력이 같으면 출력이 같아야 합니다. 하지만, 세상사는 그렇지 않습니다. Pure 함수를 체이닝 하는 동안(세상사를 표현하는 동안) 입력값이 달라지지 않았는데도 출력값이 달라야 할 때가 있습니다.

내포intensional와 외연extensional이란 용어가 익숙하지 않으면, 여기서만은 다음 처럼 바꿔 읽어도 됩니다.

함수의 외연extensional은 함수의 입력과 출력을 말하며, 함수를 평가할 때 이 외연 이외의 것들을 모두 내포intensional(적)라 말한다.

내포의 예시를 몇 개 보면

등이 있습니다. (내포는 자체적으로 나열하며 정의하기엔 범위가 정해져 있지 않습니다. 앞으로 무슨 일을 하든, 입력, 출력 이외의 것은 모두 내포입니다.)

한 문장으로 내리는 결론

함수 입출력에 드러나지 않지만, 현재 계산에 필요하거나 다음 연결되는 함수가 필요로 하는 정보, 동작은 모두 Effect입니다.

문장속에서 뜻

다음 처럼 질문을 했습니다.

lionhairdino: 비결정성이 Effect라는 말보다, 비결정성을 Effect로 구현했다라는 말이 더 맞지 않나요?
@Ailrun: 비결정성은 Effect고…

Reader 모나드를 예를 들면, “외부 스코프 변수에 환경값을 넣어 놓고 접근하는 작업(이하 환경값 접근 작업이라 부르겠습니다.)”을 Effect로 해결할 수도 있고, 매개 변수만으로 해결할 수도 있다고 말하고 싶었습니다. 그래서 전 목표 작업 자체를 Reader로 부르고,
Reader를 Effect로 구현할 수도 있고, 아닐 수도 있다”라고 얘기했습니다. 이 건 틀린 말입니다.
“환경값에 접근 하는 작업”을 Effect로 구현한 걸 Reader라 부릅니다. Reader 자체가 Effect의 이름입니다. 비결정성, mutation 등의 말 자체가 Effect입니다.

Effect로 구현하지 않아도, 즉 Reader를 쓰지 않아도 매개 변수로 모두 전달하게 만들면 “환경값에 접근하는 작업”을 해결할 수 있습니다. “환경값에 접근하는 작업” 자체가 Effect인 건 아닙니다.

Reader의 Effect는 무엇인가, 환경값에 접근하는 작업이다” 라고 얘기하는 경우를 자주 만나는데, 엄밀히 얘기하면
Reader는 환경값에 접근하는 작업을 Effect로 구현한 것에 붙인 이름입니다” 입니다.

Reader가 하는 일을 매개 변수로 구현한다면”
State는 상태를 유지하는 작업(mutation effect)인데, 이를 매개 변수로만 구현하면(할 수 있다면) 상태는 사라집니다. 상태란 낱말에 이미 mutation 뜻이 들어가 있습니다. 고정적으로 있는, 즉 변경되지 않는 값은 상태라 부르지 않고, 상수라 부릅니다.”
List는 non-deterministic effect인데, 이게 해결하는 문제를 매개 변수로만 구현하면(할 수 있다면) non-deterministic effect는 사라집니다.”
이런 식으로 말해야 합니다.

참고

A Generic Type-and-Effect System - Daniel Marino, Todd Millstein
The Marriage of Effects and Monads - Philip Wadler
Integrating Functional and Imperative Programming - David K. Gifford and John M. Lucassen 1

※ 아래는 볼 필요는 없지만, 혹시 동일한 상상을 하고 계신 분들을 위해, 조금씩 부족한 상상이었음을 알리드리기 위해 남겨 놓습니다. Effect를 공부하면서 다음 순서로 생각을 바꿔 갔습니다. 모두 부족한 추측이었습니다.

1일차: 지금 당장 결정할 수 없는 정보
2일차: 원인이 되어 다음 액션에 영향을 주는 부차적인 것
3일차: 매개 변수를 통하지 않는 정보로 하는 작업
4일차: 언제든, 실행중에도 값이 바뀔지도 모르는 메모리를 할당, 읽기, 쓰기


  1. Integrating Functional and Imperative Programming - David K. Gifford and John M. Lucassen
    fluent 언어에서 Effect는 effect classes 용어로 설명합니다. fluent 언어에서 모든 표현식은 Effect를 가지고 있습니다. 표현식의 type은 표현식을 계산한 값이 무엇인지 의미하고, 표현식의 effect classes는 표현식이 어떻게 계산될 수 있는지를 설명합니다.
    effect classes는 “관찰자OBSERVER”와 “함수FUNCTION”를 가지고 있습니다.
    표현식은 Effect를 보는 “관찰자Observer”이고, Effect를 만들어cause 낼 순 없습니다.
    fluent 언어에서는 return이 없는 procedures는 effect가 있고(executed solely for their side-effects), function은 effect가 없이 실행됩니다.
    이 논문은 절차형imperative과 함수형을 한 언어에서 어떻게 섞는가에 관한 논문입니다.↩︎

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com