문제

나는 최근에 질문을 했다 함수형 프로그래밍에 대해 더 많은 질문을 촉발하는 (좋은!) 답변을 받았습니다(때때로 학습의 경우인 것 같습니다).다음은 몇 가지 예입니다.

  1. 한 답변은 불변 데이터 구조의 장점을 언급했습니다.각 스레드는 자체 복사본을 가질 수 있습니다.나에게 이것은 누군가가 체크아웃한 코드를 다른 사람이 수정할 수 없도록 잠그는 대신 모든 사람이 자신의 복사본을 체크아웃할 수 있는 버전 제어 시스템(비유를 사용하기 위해)처럼 들립니다.좋은 것 같아요.그러나 VCS에는 두 사람이 동일한 내용을 변경한 경우 변경 사항을 "병합"한다는 개념이 있습니다.이 문제는 멀티스레드 시나리오에서 확실히 발생할 수 있는 것 같습니다...그렇다면 스레드가 최신 데이터를 보는 것이 중요한 경우 "병합"은 어떻게 수행됩니까?

  2. 이 답변 객체에 대한 루프에서 작업이 수행되는 경우와 이전 객체를 업데이트하는 대신 매번 새 객체를 사용할 수 있는 방법에 대해 이야기했습니다.그러나 bankAccount GUI 뱅킹 시스템과 같은 비루프 시나리오에서 업데이트되고 있습니다.운영자는 "이자율 변경" 버튼을 클릭합니다. 그러면 (예를 들어 C#에서) 다음과 같은 이벤트가 발생합니다. bankAccount.InterestRate = newRateFromUser.나는 여기서 밀도가 높은 것처럼 느껴지지만 내 예가 의미가 있기를 바랍니다.객체를 업데이트할 수 있는 방법이 있어야 합니다. 그렇죠?몇 가지 다른 사항은 새 데이터에 따라 달라질 수 있습니다.

어쨌든 패러다임 전환에 대해 머리를 숙이는 데 도움을 주시면 감사하겠습니다.코딩에 대한 간단한 절차적 명령형 접근 방식을 배경으로 OOP를 배울 때 내 두뇌가 비슷한 "어리석은 단계"를 겪었던 것을 기억합니다.

도움이 되었습니까?

해결책

1 부에 대한 답변 : 불변의 객체 자체는 두 스레드의 업데이트 결과를 결합 할 수 있도록 "병합"과 같은 것을 지원하지 않습니다. 이에 대한 두 가지 주요 전략이 있습니다 : 비관론과 낙관적. 비관적이라면 두 스레드가 동시에 동일한 데이터를 업데이트하고 싶을 가능성이 높다고 가정합니다. 따라서 첫 번째 스레드가 끝날 때까지 두 번째 스레드가 얼어 붙을 수 있도록 잠금을 사용합니다. 거의 발생하지 않는다는 낙관적이라면 두 스레드가 자체 논리적 데이터 사본에서 작동하게합니다. 첫 번째는 새 버전을 먼저 공급하고 다른 하나는 처음부터 다시 시작해야합니다. 이제 첫 번째 스레드 변경 결과에서 시작합니다. 이 고가의 재시작은 때때로 발생하므로 잠금 부족으로 인해 모든 것이 더 잘 수행됩니다 (이것은 충돌이 거의 발생하지 않는 경우 낙관론이 잘 배치 된 경우에만 해당됩니다).

2 부 : 순수한 기능적 상태 불필요한 언어는 그 문제를 실제로 제거하지 않습니다. 순수한 Haskell 프로그램조차도 상태를 가질 수 있습니다. 차이점은 Stateful Code의 반환 유형이 다르다는 것입니다. 상태를 조작하는 함수는 해당 상태를 나타내는 객체에서 작동하는 일련의 연산으로 표현됩니다. 터무니없는 예에서는 컴퓨터의 파일 시스템을 고려하십시오. 프로그램이 파일의 내용을 수정할 때마다 (단일 바이트조차도) 전체 파일 시스템의 새로운 "버전"을 생성했습니다. 그리고 확장하여 전체 우주의 새로운 버전. 그러나 지금은 파일 시스템에 중점을 두겠습니다. 파일 시스템을 검사하는 프로그램의 다른 부분은 이제 수정 된 바이트의 영향을받을 수 있습니다. Haskell은 파일 시스템에서 작동하는 기능은 파일 시스템의 버전을 나타내는 객체를 효과적으로 전달해야한다고 말합니다. 그런 다음 수동으로 다루기가 지루하기 때문에 요구 사항을 내부로 바꾸고 기능이 IO를 수행 할 수 있으려면 일종의 컨테이너 객체를 반환해야한다고 말합니다. 컨테이너 내부에는 함수가 반환하려는 값이 있습니다. 그러나 컨테이너는이 기능에 부작용이 있거나 부작용을 볼 수 있다는 증거 역할을합니다. 이는 Haskell의 유형 시스템이 측면 효과와 "순수한"기능을 구별 할 수 있음을 의미합니다. 따라서 코드의 상태를 실제로 제거하지 않고 포함하고 관리하는 데 도움이됩니다.

다른 팁

.NET의 문자열 클래스 (불변의 객체)에 대해 생각해보십시오. 문자열에서 메소드를 호출하면 새 사본이 나타납니다.

String s1 = "there";
String s2 = s1.Insert(0, "hello ");

Console.Writeline("string 1: " + s1);
Console.Writeline("string 2: " + s2);

이것은 출력됩니다 :

문자열 1 : 거기

문자열 2 : 안녕하세요

이 동작을 기본적으로 동일한 방법 서명 인 StringBuilder와 비교하십시오.

StringBuilder sb  = new StringBuilder("there");
StringBuilder sb2 = sb.Insert(0, "hi ");

Console.WriteLine("sb 1: " + sb.ToString());
Console.WriteLine("sb 2: " + sb2.ToString());

StringBuilder는 변이 가능하기 때문에 두 변수 모두 동일한 객체를 가리 킵니다. 출력은 다음과 같습니다.

SB 1 : 안녕하세요

SB 2 : 안녕하세요

따라서 문자열을 만든 후에는 끈을 변경할 수 없습니다. S1은 항상 시간이 끝날 때까지 (또는 쓰레기가 수집 될 때까지) 항상 "거기"됩니다. 그것은 항상 각 캐릭터를 밟고 항상 '거기에 인쇄한다'는 것을 알고 값을 인쇄 할 수 있기 때문에 스레딩에서 중요합니다. StringBuilder가 만들어진 후 인쇄를 시작하면 처음 두 문자를 인쇄하고 'TH'를 인쇄 할 수 있습니다. 이제 다른 스레드가 ad inserts 'hi'를 따라 온다고 상상해보십시오. 값은 이제 다릅니다! 세 번째 캐릭터를 인쇄하면 'Hi'의 공간입니다. 그래서 당신은 인쇄합니다 : 'th there'.

#2에 관해서는...

다른 몇 가지 사항은 새로운 데이터에 따라 다를 수 있습니다.

이것이 바로 순수주의자들이 "효과"라고 ​​부르는 것입니다.동일한 변경 가능한 개체에 대한 여러 개체 참조의 개념은 변경 가능한 상태의 본질이자 문제의 핵심입니다.OOP에는 BankAccount 유형의 객체 "a"가 있을 수 있으며, 다른 위치에서 a.Balance 등을 읽는 경우 타임스 다른 값이 표시될 수 있습니다.대조적으로, 순수 FP에서 "a"가 BankAccount 유형을 갖는 경우 이는 불변이며 시간에 관계없이 동일한 값을 갖습니다.

그러나 BankAccount는 아마도 시간에 따라 상태가 변하는 모델링하려는 객체일 것이므로 FP에서는 해당 정보를 유형으로 인코딩합니다.따라서 "a"는 "IO BankAccount" 유형을 가질 수도 있고 본질적으로 "a"를 "이전 세계 상태"(또는 이전 은행 이자율 상태)를 입력으로 사용하는 함수로 만드는 다른 모나딕 유형을 가질 수도 있습니다. , 또는 무엇이든), 세계의 새로운 상태를 반환합니다.이자율을 업데이트하는 것은 효과를 나타내는 유형을 사용하는 또 다른 작업입니다(예:다른 IO 작업), 따라서 새로운 '세계'를 반환하고 이자율(세계 상태)에 의존할 수 있는 모든 것은 해당 세계를 입력으로 가져와야 한다는 것을 아는 유형의 데이터가 됩니다.

결과적으로 "a.Balance" 또는 기타 등등을 호출할 수 있는 유일한 방법은 정적 유형 덕분에 '지금까지 우리를 있게 한 세계 역사'가 다음 지점까지 적절하게 연결되도록 강제하는 코드를 사용하는 것입니다. 호출, 그리고 입력된 세계 역사가 무엇이든 우리가 a.Balance에서 얻을 결과에 영향을 미칩니다.

다음 내용을 읽어보세요. 상태 모나드 '공유 변경 가능 상태'를 순수하게 모델링하는 방법을 이해하는 데 유용할 수 있습니다.

  1. 불변의 데이터 구조는 VC와 다릅니다. 불변의 데이터 구조를 읽기 전용 파일로 생각하십시오. 읽기 전용이라면 누가 주어진 시간에 파일의 어느 부분을 읽고 있는지는 중요하지 않으며, 모든 사람은 올바른 정보를 읽습니다.

  2. 그 대답은 이야기하고 있습니다 http://en.wikipedia.org/wiki/monad_(functional_programming)

MVCC (다중 동시성 제어)

당신이 언급하는 문제에 대한 해결책은 Rich Hickey가 그의 비디오 프레젠테이션.

요컨대: 클라이언트를 참조하여 데이터를 직접 전달하는 대신 간접에 하나 더 레벨을 추가하고 데이터에 대한 참조에 대한 참조를 전달합니다. (글쎄, 실제로, 당신은 적어도 하나 이상의 간접 수준을 갖고 싶습니다. 그러나 "배열"과 같이 데이터 구조가 매우 간단하다고 가정 해 봅시다.)
데이터는 불변이 아니기 때문에 데이터를 변경할 때마다 변경된 부분의 사본을 만듭니다 (배열의 경우 다른 배열을 만들어야합니다!) 또한 모든 "변경된"데이터에 대한 다른 참조를 만듭니다.
따라서 첫 번째 버전의 배열을 사용한 모든 클라이언트의 경우 첫 번째 버전에 대한 참조를 사용합니다. 두 번째 버전에 액세스하려는 모든 클라이언트는 두 번째 참조를 사용합니다.
"배열"데이터 구조는 데이터를 분할 할 수없고 모든 것을 복사해야하기 때문에이 방법에 대해서는 그다지 흥미롭지 않습니다. 그러나 나무와 같은보다 정교한 데이터 구조의 경우 데이터 구조의 일부 부분을 "공유"할 수 있으므로 매번 모든 것을 복사 할 수 없습니다.

자세한 내용은이 백서를 살펴보십시오. "순전히 기능적인 데이터 구조" Chris Okasaki에 의해.

"불변"은 정확히 그 의미를 의미합니다.

기능 프로그램이 업데이트하는 방식은 새로운 것을 전달하는 것입니다. 기존 값은 변경되지 않습니다. 새 값을 구축하고 대신 전달합니다. 종종 새로운 가치는 기존의 가치를 공유합니다. 이 기술의 좋은 예는 단점 셀로 구성된 목록이며 지퍼.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top