본문 바로가기

프로그래밍 기술 노트/Functional Study

[Expression Problem] 표현 문제란?

 

프로그래밍에서 Expression Problem 란 기능을 확장할때 발생하는 문제이다.
표현문제라는 것을 들어본적이 없어도, 분명 경험해본 문제일것이며, 예시를 보면 "아~ 이런적이 있지" 라고 생각할것이다.

어떻게 기능을 확장 할 것인가?

OOP 와 FP 의 패러다임을 막론하고 발생하는 문제로, 정확히는 OOP 식 접근법과 FP 식 접근법에서 서로 다른 "표현 문제" 를 마주하게 된다.

OOP 식 접근법

먼저 OOP 식 접근법을 살펴보자

interface Character {
    fun hit()
    fun skill()
}

class Healer : Character {
    val hp: Int = 100
    override fun hit() { println("Healer Hit") }
    override fun skill() { println("Healer Skill") }
}

class Knight : Character {
    val hp: Int = 100
    override fun hit() { println("Knight Hit") }
    override fun skill() { println("Knight Skill") }
}

이 소프트웨어는 크게 2가지 방향으로 확장 될 가능성이 있다.
1. Tanker 라는 새 캐릭터가 추가 되었다.
2. Defence 라는 함수가 추가되었다.
1번의 Tanker 라는 캐릭터가 추가되는것은 아주 쉽다.

class Tanker : Character {
    val hp: Int = 100
    override fun hit() { println("Tanker Hit") }
    override fun skill() { println("Tanker Skill") }
}

반면에 2번 Defence 함수를 추가하려면,
모든 Character 에 Defence 함수를 추가해주어야 한다.

추가하세욧!

FP 식 접근법

FP 식 접근법은 함수내에서 디스패칭을 주로 이용한다. 여기서는 When 패턴매칭으로 구현해보았다.

sealed class Character {
    data class Healer(val hp:Int): Character()
    data class Knight(val hp:Int): Character()
}

fun hit(character: Character){
    when(character){
        is Character.Healer -> println("Healer Hit")
        is Character.Knight -> println("Knight Hit")
    }
}

fun skill(character: Character){
    when(character){
        is Character.Healer -> println("Healer skill")
        is Character.Knight -> println("Knight skill")
    }
}

fun defence(character: Character){
    when(character){
        is Character.Healer -> println("Healer defence")
        is Character.Knight -> println("Knight defence")
    }
}

다시 다음과 같은 상황을 가정해 보자

  1. Tanker 라는 새 캐릭터가 추가 되었다.
  2. Defence 라는 함수가 추가되었다.

FP 식 접근법에서 2번은 쉽다.

fun defence(character: Character){
    when(character){
        is Character.Healer -> println("Healer defence")
        is Character.Knight -> println("Knight defence")
    }
}

반면 1번 Tanker 를 만들기위해서는, 모든 함수의 when에 Tanker branch 가 추가 되어야 한다.

추가 하세욧!


이렇게 OOP 식 접근법/ FP 식 접근법에서 확장을 할때, 확장이 어려워지(표현 문제)는 순간이 존재하며, 각각의 패러다임은 표현문제의 절반밖에 해결해주지 않는다.


이러한 Expression Problem 문제는 패러다임과 상관없이, 확장에 대한 어려움을 발생시킨다.
패러다임과 상관없이 발생가능한 문제로, 다양한 언어에서 이를 해결하기 위한 방법이, "언어적 차원 지원" 이 존재한다.

대표적인 해결 방법들을 나열하자면 다음과 같다

  1. Open Class / Open method
  2. Type class
  3. 다중 디스패치 (Multiple dispatch) / Visitor
  4. Object algebras / Tagless Final

 

[Expression Problem 시리즈 작성시 참고한 자료들]
The Expression Problem and its solutions - Eli Bendersky's website (thegreenplace.net)
More thoughts on the Expression Problem in Haskell - Eli Bendersky's website (thegreenplace.net)
Max.Computer - Solving the Expression Problem in Clojure
Expression problem - Wikipedia
TypeClass 와 Interface의 차이점 With Kotlin | by Lazysoul | Medium
How to avoid the Visitor pattern in C# - DEV Community

Joy of clojure (책)

 

728x90