나는 사람들이 (이 사이트에서도) 일상적으로 기능적 프로그래밍 패러다임을 칭찬하면서 모든 것이 불변의 것을 갖는 것이 얼마나 좋은지를 강조하고 있습니다. 특히 사람들은 C #, Java 또는 C ++와 같은 전통적인 명령형 OO 언어에서도 Haskell과 같은 순수한 기능 언어뿐만 아니라 프로그래머 에게이 기능을 강요하는 방식 으로이 접근법을 제안합니다.
나는 변이성과 부작용을 발견하기 때문에 이해하기 어렵다는 것을 알았습니다 ... 편리합니다. 그러나 사람들이 현재 부작용을 비난하고 가능한 한 어디에서나 부작용을 제거하는 것이 좋은 방법이라고 생각할 때 유능한 프로그래머가 되려면 패러다임에 대한 이해를 돕기 위해 장을 시작해야한다고 생각합니다 ... 따라서 내 Q.
기능적 패러다임에서 문제를 발견 할 때 한 장소는 여러 장소에서 객체를 자연스럽게 참조하는 것입니다. 두 가지 예를 들어 설명하겠습니다.
첫 번째 예는 여가 시간에 만들려고하는 C # 게임 입니다. 턴 기반 웹 게임으로 두 플레이어 모두 4 명의 몬스터로 구성된 팀을 보유하고 있으며 팀에서 몬스터를 전장으로 보낼 수 있습니다.이 몬스터는 상대 플레이어가 보낸 몬스터를 향하게됩니다. 플레이어는 또한 전장에서 몬스터를 리콜하고 팀의 다른 몬스터로 대체 할 수 있습니다 (포켓몬과 유사).
이 설정에서, 하나의 몬스터는 최소한 두 곳 (플레이어 팀과 전장)에서 자연스럽게 참조 할 수 있습니다.
이제 한 몬스터가 맞고 체력이 20 감소하는 상황을 생각해 봅시다. 명령형 패러다임의 괄호 안에서 나는이 괴물의 health
필드를 수정 하여이 변화를 반영합니다. 이것은 내가 지금하고있는 일입니다. 그러나 이것은 Monster
클래스를 변경 가능 하게 만들고 관련 함수 (방법)를 불순 하게 만듭니다 . 현재로서는 나쁜 습관으로 간주됩니다.
미래의 어떤 시점에서 실제로 게임을 끝내기를 희망하기 위해이 게임의 코드를 이상적이지 않은 상태로 유지할 수있는 권한을 부여했지만 어떻게해야하는지 알고 이해하고 싶습니다. 제대로 작성되었습니다. 따라서 : 이것이 디자인 결함이라면 어떻게 고쳐야합니까?
기능적 스타일에서, 나는 그것을 이해하면서, 대신이 Monster
객체 의 사본을 만들어서이 하나의 필드를 제외하고는 이전의 것과 동일하게 유지합니다. 메소드 suffer_hit
는 이전 오브젝트를 수정하는 대신이 새 오브젝트를 리턴합니다. 그런 다음 Battlefield
이 몬스터를 제외한 모든 필드를 동일하게 유지하면서 객체를 복사합니다 .
이것은 적어도 두 가지 어려움이 있습니다.
- 계층은 just-
Battlefield
> 의 단순화 된 예보다 훨씬 더 깊을 수 있습니다Monster
. 나는 하나를 제외한 모든 필드를 복사 하고이 계층 구조까지 새로운 객체를 반환해야합니다. 이것은 기능 프로그래밍이 상용구를 줄이려고하기 때문에 특히 성가신 것으로 보이는 상용구 코드입니다. - 그러나 훨씬 더 심각한 문제는 데이터가 동기화되지 않는다는 것 입니다. 이 필드의 활성 몬스터는 체력이 감소 된 것을 볼 수 있습니다. 그러나 제어 플레이어에서 언급 한 동일한 몬스터
Team
는 그렇지 않습니다. 내가 대신 필수적 스타일을 수용하면, 모든 데이터 수정 코드의 나는 정말 편리하게 찾을 이와 같은 이러한 경우 다른 모든 장소에서 즉시 볼 것 - 하지만 난 것들을 받고 있어요 방법이있다 정확하게 사람들이 무슨 말을 명령형 스타일로 잘못되었습니다!- 이제
Team
각 공격 후로 이동하여이 문제를 처리 할 수 있습니다 . 이것은 추가 작업입니다. 그러나 몬스터가 나중에 더 많은 곳에서 갑자기 참조 될 수 있다면 어떨까요? 예를 들어, 몬스터가 반드시 필드에 있지 않은 다른 몬스터에 집중할 수 있는 능력이 있다면 어떨까요? 공격 할 때마다 집중된 몬스터로 즉시 여행해야한다는 것을 반드시 기억해야합니까? 이것은 코드가 더 복잡 해짐에 따라 폭발 할 시한 폭탄 인 것 같습니다. 그래서 나는 그것이 해결책이 아니라고 생각합니다.
- 이제
더 나은 솔루션에 대한 아이디어는 동일한 문제에 부딪쳤을 때 두 번째 예에서 비롯됩니다. 학계에서는 Haskell에서 자체 디자인 언어의 통역사를 작성하라는 지시를 받았습니다. (이 또한 FP가 무엇인지 이해하기 시작한 방법이기도합니다). 클로저를 구현할 때 문제가 나타났습니다. 다시 한 번 같은 범위는 지금 여러 곳에서 참조 할 수있다 :이 범위를 보유하고있는 변수를 통해 및 중첩 된 범위의 상위 범위로! 분명히이 범위를 가리키는 참조를 통해이 범위를 변경 한 경우이 변경은 다른 모든 참조를 통해서도 볼 수 있어야합니다.
내가 제공 한 해결책은 각 범위에 ID를 할당하고 State
모나드 에있는 모든 범위의 중앙 사전을 보유하는 것이 었습니다 . 이제 변수는 범위 자체가 아닌 바인딩 된 범위의 ID 만 보유하고 중첩 된 범위는 상위 범위의 ID도 보유합니다.
내 몬스터 싸움 게임에서 같은 접근법을 시도 할 수있을 것 같다. 필드와 팀은 몬스터를 참조하지 않는다. 대신 중앙 몬스터 사전에 저장된 몬스터의 ID를 보유합니다.
그러나이 문제에 대한 해결책으로 주저없이 그것을 받아들이지 못하게하는이 접근법의 문제를 다시 한 번 볼 수 있습니다.
다시 한 번 상용구 코드 소스입니다. 1 개의 라이너를 반드시 3 개의 라이너로 만듭니다 : 이전에 단일 필드를 한 줄로 수정 한 것은 이제 (a) 중앙 사전에서 객체 검색 (b) 변경 (c) 새 객체 저장 중앙 사전에. 또한 참조 대신 객체 ID와 중앙 사전을 보유하면 복잡성이 증가합니다. FP는 복잡성과 상용구 코드 를 줄이기 위해 광고되기 때문에 이것이 잘못하고 있음을 암시합니다.
또한 훨씬 더 심각한 것처럼 보이는 두 번째 문제에 대해 작성하려고했습니다.이 방법은 메모리 누수를 유발 합니다. 도달 할 수없는 객체는 일반적으로 가비지 수집됩니다. 그러나 도달 가능한 오브젝트가이 특정 ID를 참조하지 않더라도 중앙 사전에 보유 된 오브젝트는 가비지 콜렉션 될 수 없습니다. 이론적으로 신중한 프로그래밍은 메모리 누수를 피할 수 있지만 (더 이상 필요하지 않은 경우 각 사전을 중앙 사전에서 수동으로 제거하도록주의를 기울일 수 있음) 오류가 발생하기 쉬우 며 FP는 다시 한 번 프로그램의 정확성을 높이기 위해 광고됩니다. 올바른 방법이 아닙니다.
그러나 나는 시간이 지남에 오히려 그것이 해결 된 문제인 것처럼 보였다. Java는 WeakHashMap
이 문제를 해결하는 데 사용할 수있는 기능 을 제공 합니다. C #은 비슷한 기능을 제공합니다.- ConditionalWeakTable
문서에 따르면 컴파일러에서 사용하도록되어 있습니다. 그리고 Haskell에는 System.Mem.Weak이 있습니다.
이러한 사전을 저장하면이 문제에 대한 올바른 기능적 솔루션입니까, 아니면 내가 볼 수없는 간단한 것이 있습니까? 그러한 사전의 수가 쉽게 늘어나고 나빠질 수 있다고 생각합니다. 따라서 이러한 사전이 변경 불가능한 경우 사전이 모나드로 유지되므로 모나드 계산을 지원하는 언어에서 많은 매개 변수 전달 또는 모나드 계산을 의미 할 수 있습니다. 이 사전 솔루션은 거의 모든 코드를 State
모나드 내부에 넣습니다 . 이것이 올바른 솔루션인지 다시 한 번 의심하게 만듭니다.)
몇 가지 고려를 한 후에 나는 하나 이상의 질문을 추가 할 것이라고 생각합니다. 그러한 사전을 구성하여 무엇을 얻고 있습니까? 많은 전문가들에 따르면 명령형 프로그래밍의 문제점은 일부 객체의 변경 사항이 다른 코드 조각으로 전파된다는 것입니다. 이 문제를 해결하기 위해 객체는 불변이어야합니다. 정확히 이해하면 객체의 변경 사항을 다른 곳에서 볼 수 없어야합니다. 그러나 이제는 오래된 데이터에서 작동하는 다른 코드 조각에 대해 걱정하고 있으므로 중앙 사전을 발명하여 일부 코드 조각의 변경 사항이 다시 한 번 다른 코드 조각으로 전파됩니다! 그러므로 우리는 모든 단점을 가지고 명령형 스타일로 돌아 가지 않습니까?
Team
)는 전투 결과와 몬스터 상태를 (전투 번호, 몬스터 엔티티 ID) 튜플로 검색 할 수 있습니다.