기능적 스타일로 프로그래밍 할 때 응용 프로그램 논리를 통해 하나의 응용 프로그램 상태가 있습니까?


12

어떻게 다음을 모두 가지고있는 시스템을 구축 할 :

  1. 변경 불가능한 객체에 순수한 함수 사용
  2. 더 이상 필요하지 않은 함수 데이터 만 전달하십시오 (즉, 큰 응용 프로그램 상태 객체가 없음).
  3. 함수에 너무 많은 인수를 사용하지 마십시오.
  4. 함수에 매개 변수를 패킹하고 압축을 풀기 위해 단순히 새로운 오브젝트를 구성하지 말고, 너무 많은 매개 변수가 함수에 전달되지 않도록하십시오. 하나의 객체로 함수에 여러 항목을 포장하려는 경우 해당 객체가 일시적으로 생성 된 것이 아니라 해당 데이터의 소유자가되기를 원합니다.

State 모나드는 규칙 # 2를 위반하는 것으로 보이지만 모나드를 통해 직조되어 있기 때문에 명확하지 않습니다.

어떻게 든 렌즈를 사용해야한다는 느낌이 들지만 비 기능 언어에 대해서는 거의 쓰여 있지 않습니다.

배경

연습으로, 기존 응용 프로그램 중 하나를 객체 지향 스타일에서 기능적 스타일로 변환하고 있습니다. 내가하려고하는 첫 번째 일은 응용 프로그램의 내부 코어를 최대한 많이 만드는 것입니다.

제가 들었던 한 가지는 순수한 기능 언어로 "상태"를 관리하는 방법입니다. 이것이 제가 주 모나드에 의해 수행된다고 생각하는 것은 논리적으로 여러분은 순수한 함수를 호출하는 것입니다. 함수를 반환하면 변경된 세계 상태를 반환합니다.

예를 들어, 순전히 기능적인 방식으로 "hello world"를 수행 할 수있는 방법은 일종의 프로그램과 같은 화면 상태를 전달하고 화면에 "hello world"가 인쇄 된 상태를 다시받습니다. 따라서 기술적으로 순수한 기능을 호출하고 부작용이 없습니다.

이를 바탕으로, 나는 내 응용 프로그램을 살펴 보았습니다. 1. 먼저 모든 응용 프로그램 상태를 단일 전역 객체 (GameState)에 넣었습니다. 2. 둘째, GameState를 변경할 수 없게 만들었습니다. 변경할 수 없습니다. 변경이 필요한 경우 새로운 변경을 구성해야합니다. 선택적으로 변경된 하나 이상의 필드를 사용하는 복사 생성자를 추가하여이 작업을 수행했습니다. 3. 각 응용 프로그램에 GameState를 매개 변수로 전달합니다. 함수 내에서 수행 할 작업을 수행 한 후 새 GameState를 생성하여 반환합니다.

순수한 기능적 코어와 외부의 루프를 통해 해당 GameState를 응용 프로그램의 기본 워크 플로 루프에 공급하는 방법.

내 질문:

이제 내 문제는 GameState에 약 15 개의 불변 객체가 있다는 것입니다. 가장 낮은 수준의 많은 기능은 점수 유지와 같은 일부 개체에서만 작동합니다. 점수를 계산하는 함수가 있다고 가정 해 봅시다. 오늘 GameState가이 함수로 전달되어 새로운 점수로 새로운 GameState를 만들어 점수를 수정합니다.

그것에 대해 뭔가 잘못 보인다. 이 함수에는 GameState 전체가 필요하지 않습니다. Score 객체 만 있으면됩니다. 그래서 점수를 전달하고 점수 만 반환하도록 업데이트했습니다.

그것은 말이되는 것처럼 보였으므로 다른 기능으로 더 나아갔습니다. 일부 함수는 GameState에서 2, 3 또는 4 매개 변수를 전달해야하지만 응용 프로그램의 외부 코어까지 패턴을 모두 사용함에 따라 점점 더 많은 응용 프로그램 상태를 전달합니다. 워크 플로 루프의 최상위에서 점수를 계산할 때까지 메서드 등을 호출하는 메서드를 호출하는 메서드를 호출합니다. 즉, 맨 아래의 함수가 점수를 계산하기 때문에 현재 점수가 모든 레이어를 통해 전달됩니다.

이제 수십 개의 매개 변수가있는 함수가 있습니다. 매개 변수 수를 낮추기 위해 해당 매개 변수를 객체에 넣을 수는 있지만 호출시 단순히 전달하지 않도록 객체가 아닌 해당 클래스가 상태 응용 프로그램 상태의 마스터 위치가되도록하고 싶습니다. 여러 매개 변수에 넣은 다음 포장을 풉니 다.

이제 문제가 내 기능이 너무 깊게 중첩되어 있는지 궁금합니다. 이것은 작은 함수를 원하기 때문에 발생합니다. 함수가 커질 때 리팩터링하여 여러 개의 작은 함수로 나눕니다. 그러나 그렇게하면 더 깊은 계층이 생성되고 외부 함수가 해당 객체에서 직접 작동하지 않더라도 내부 함수로 전달 된 모든 것이 외부 함수로 전달되어야합니다.

이 문제를 피하는 방식으로 단순히 GameState를 전달하는 것처럼 보였습니다. 그러나 함수에 필요한 것보다 더 많은 정보를 함수에 전달하는 원래 문제로 돌아 왔습니다.


1
나는 디자인과 전문적인 기능에 대한 전문가는 아니지만, 본질적으로 당신의 게임은 진화하는 상태를 가지고 있기 때문에, 함수형 프로그래밍은 응용 프로그램의 모든 계층에 맞는 패러다임이라고 확신합니까?
Walfrat

Walfrat, 함수형 프로그래밍 전문가와 이야기한다면, 함수형 프로그래밍 패러다임에는 진화하는 상태를 관리하기위한 솔루션이 있다고 말할 것입니다.
Daisha Lynn

귀하의 질문은 나에게만 더 넓게 보입니다. 상태 관리에 관한 것이라면 여기 시작입니다. stackoverflow.com/questions/1020653/…
Walfrat

2
@DaishaLynn 질문을 삭제해야한다고 생각하지 않습니다. upvoted되었고 아무도 닫으려고하지 않으므로이 사이트의 범위를 벗어난 것으로 생각하지 않습니다. 지금까지 답변이 부족한 것은 비교적 틈새 기술이 필요하기 때문일 수 있습니다. 그렇다고해서 결국에는 답을 찾지 못하는 것은 아닙니다.
벤 애런 슨

2
실질적인 언어 지원없이 복잡한 순수 기능 프로그램에서 변경 가능한 상태를 관리하는 것은 큰 고통입니다. Haskell에서는 모나드, 간결한 구문, 매우 좋은 형식 유추로 인해 관리가 가능하지만 여전히 매우 성가시다. C #에서는 상당히 많은 문제가 있다고 생각합니다.
복원 Monica Monica

답변:


2

좋은 해결책이 있는지 확실하지 않습니다. 이것은 대답 일 수도 있고 아닐 수도 있지만, 설명하기에는 너무 깁니다. 나는 비슷한 일을 하고 있었고 다음과 같은 트릭이 도움이되었습니다.

  • GameState계층 적으로 분할 하므로 15 개 대신 3-5 개의 작은 부품을 얻을 수 있습니다.
  • 인터페이스를 구현하면 메소드가 필요한 부분 만 볼 수 있습니다. 실제 유형에 대해 자신에게 거짓말 할 때 다시 캐스팅하지 마십시오.
  • 부품이 인터페이스를 구현하도록하여 전달한 내용을 정밀하게 제어 할 수 있습니다.
  • 매개 변수 객체를 사용하되, 드물게 수행하고 자체 동작으로 실제 객체로 바꾸십시오.
  • 때로는 필요한 것보다 약간 더 많이 전달하는 것이 긴 매개 변수 목록보다 낫습니다.

이제 문제가 내 기능이 너무 깊게 중첩되어 있는지 궁금합니다.

나는 그렇게 생각하지 않습니다. 작은 함수로 리팩토링하는 것이 옳지 만, 더 잘 그룹화 할 수 있습니다. 때로는 가능하지 않으며 때로는 문제를 두 번째 (또는 세 번째) 봐야합니다.

설계를 변경 가능한 설계와 비교하십시오. 다시 쓰기로 인해 악화 된 일이 있습니까? 그렇다면 원래와 같은 방식으로 더 나아질 수 없습니까?


누군가 카레를 사용할 수 있도록 함수가 하나의 매개 변수 만 사용하도록 디자인을 변경하라고 말했습니다. 하나의 함수를 시도 했으므로 DeleteEntity (a, b, c)를 호출하는 대신 DeleteEntity (a) (b) (c)를 호출합니다. 너무 귀여워서 좀 더 작곡하기가 쉽지만 아직은 얻지 못했습니다.
Daisha Lynn

@DaishaLynn Java를 사용하고 있으며 카레를위한 달콤한 구문 설탕이 없으므로 시도해 볼 가치가 없습니다. 나는 우리의 경우에 고차 함수의 가능한 사용법에 대해 회의적이지만, 그것이 당신에게 효과가 있는지 알려주십시오.
maaartinus

2

C #과 대화 할 수는 없지만 Haskell에서는 전체 상태를 전달하게됩니다. 이를 명시 적으로 또는 State 모나드로 수행 할 수 있습니다. 더 많은 정보를 수신하는 함수의 문제를 해결하기 위해 할 수있는 한 가지는 Has 클래스를 사용하는 것입니다. 익숙하지 않은 경우 Haskell 형식 클래스는 C # 인터페이스와 약간 비슷합니다. 상태의 각 요소 E에 대해 E 값을 반환하는 getE 함수가 필요한 형식 클래스 HasE를 정의 할 수 있습니다. 이 모든 유형 클래스의 인스턴스를 만들었습니다. 그런 다음 실제 함수에서 State 모나드를 명시 적으로 요구하는 대신 필요한 요소에 대해 Has 유형 클래스에 속하는 모나드가 필요합니다. 함수가 사용하는 모나드로 할 수있는 작업을 제한합니다. 이 방법에 대한 자세한 내용은 Michael Snoyman 's를 참조하십시오.ReaderT 디자인 패턴에 게시하십시오 .

전달되는 상태를 정의하는 방법에 따라 C #에서 이와 같은 것을 복제 할 수 있습니다. 당신이 같은 것을 가지고 있다면

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

당신은 인터페이스를 정의 할 수 있습니다 IHasMyIntIHasMyString방법으로 GetMyInt하고 GetMyString각각을. 상태 클래스는 다음과 같습니다.

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

그런 다음 메서드에 IHasMyInt, IHasMyString 또는 전체 MyState가 필요할 수 있습니다.

그런 다음 상태 객체를 전달할 수 있도록 함수 정의에 where 제약 조건을 사용할 수 있지만 double이 아니라 string 및 int에만 도달 할 수 있습니다.

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}

그 흥미 롭군요. 그래서 현재 함수를 호출하고 값으로 10 개의 매개 변수를 전달하는 경우 "gameSt'ate"를 10 번 전달하지만 "IHasGameScore", "IHasGameBoard"등과 같은 10 가지 매개 변수 유형에 전달합니다. 함수가 해당 유형의 모든 인터페이스를 구현해야 함을 나타낼 수있는 단일 매개 변수를 전달하는 방법이었습니다. "제네릭 제약"으로 할 수 있을지 궁금합니다. 시도해 보겠습니다.
Daisha Lynn

1
효과가있었습니다. 여기에서 작동합니다 : dotnetfiddle.net/cfmDbs .
Daisha Lynn

1

Redux 또는 Elm에 대해 배우고이 질문을 어떻게 처리하는지 잘 알고 있다고 생각합니다.

기본적으로 전체 상태와 사용자가 수행 한 작업을 수행하고 새 상태를 반환하는 하나의 순수한 함수가 있습니다.

그런 다음이 함수는 다른 순수한 함수를 호출하며 각 함수는 특정 상태를 처리합니다. 동작에 따라 이러한 많은 함수는 원래 상태를 변경하지 않고 되돌릴 수 있습니다.

자세한 내용은 Google Elm Architecture 또는 Redux.js.org를 참조하십시오.


Elm을 모르지만 Redux와 비슷하다고 생각합니다. Redux에서 모든 감속기가 모든 상태 변경을 요구하지는 않습니까? 매우 비효율적으로 들립니다.
Daisha Lynn

저수준 최적화와 관련하여 측정하지 마십시오. 실제로 그것은 충분히 빠릅니다.
Daniel T.

고마워 다니엘,하지만 그것은 나를 위해 작동하지 않습니다. 컨트롤이 컨트롤에 관심이 있는지 여부에 관계없이 데이터가 변경 될 때마다 UI의 모든 구성 요소에 알리지 않는다는 것을 알기 위해 충분한 개발을 수행했습니다.
Daisha Lynn

-2

나는 당신이하려고하는 일은 마치 객체 지향 언어를 순수한 기능적인 것처럼 사용해서는 안되는 방식으로 사용하는 것이라고 생각합니다. OO 언어가 모두 악한 것은 아닙니다. 두 가지 접근 방식의 장점이 있으므로 이제는 OO 스타일을 기능적 스타일과 혼합하고 일부 코드를 기능적으로 만들 수있는 반면 다른 객체는 객체 지향성을 유지하여 모든 재사용 가능성, 상속 또는 Polimophism을 활용할 수 있습니다. 운 좋게도 우리는 더 이상 어느 쪽도 접근 할 수 없어서 왜 당신은 그들 중 하나에 자신을 국한하려고합니까?

귀하의 질문에 대답 : 아니오, 응용 프로그램 논리를 통해 특정 상태를 직조하지 않고 현재 사용 사례에 적합한 것을 사용하고 가장 적절한 방법으로 사용 가능한 기술을 적용하십시오.

C #은 원하는대로 기능적으로 사용할 준비가되지 않았습니다.


3
이 답변이나 그 음색에 감격하지 않았습니다. 난 아무것도 학대하지 않습니다. C #의 한계를보다 기능적인 언어로 사용하려고합니다. 드문 일이 아닙니다. 당신은 철학적으로 그것에 반대하는 것처럼 보입니다. 귀하의 의견은 누구에게도 소용이 없습니다. 어서
Daisha Lynn

@DaishaLynn 당신이 틀렸어, 나는 그것에 반대하지 않고 실제로 그것을 많이 사용하지만 ... 자연스럽고 가능하며 OO 언어를 기능적인 언어로 바꾸려고하지 않는 것은 엉덩이이기 때문에 그렇게하기 위해. 내 대답에 동의 할 필요는 없지만 도구를 올바르게 사용하지 않는다는 사실은 바뀌지 않습니다.
t3chb0t

그렇게하는 것이 엉덩이이기 때문에 나는하지 않습니다. C # 자체는 기능적 스타일을 사용하는쪽으로 나아가고 있습니다. Anders Hejlsberg 자신도 그렇게 지적했습니다. 나는 당신이 언어의 주류 사용에만 관심이 있다는 것을 이해하고, 왜 그리고 언제 적절한 지 이해합니다. 난 당신 같은 사람이 왜이 스레드에 있는지 모르겠어요. 어떻게 돕고 있습니까?
Daisha Lynn

@DaishaLynn 당신이 당신의 질문이나 접근을 비판하는 답변에 대처할 수 없다면 당신은 아마도 여기에 질문하지 말아야 할 것입니다. 진실을 듣고 싶어하지만 오히려지지적인 의견을 얻으십시오.
t3chb0t

서로 좀 더 동정하십시오. 언어를 훼손하지 않고 비판을 할 수 있습니다. 기능적인 스타일로 C #을 프로그래밍하려고 시도하는 것은 확실히 "남용"또는 엣지 케이스가 아닙니다. 많은 C # 개발자가 다른 언어를 배우기 위해 사용하는 일반적인 기술입니다.
zumalifeguard
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.