변경 가능한 상태 없이 어떻게 유용한 작업을 수행할 수 있습니까?

StackOverflow https://stackoverflow.com/questions/1020653

  •  06-07-2019
  •  | 
  •  

문제

최근에 함수형 프로그래밍에 관한 많은 내용을 읽었고 대부분을 이해할 수 있지만 머리를 감쌀 수 없는 한 가지는 상태 비저장 코딩입니다.변경 가능한 상태를 제거하여 프로그래밍을 단순화하는 것은 대시보드를 제거하여 자동차를 "단순화"하는 것과 같습니다.완성된 제품은 더 간단할 수 있지만 최종 사용자와 상호 작용할 수 있도록 행운을 빕니다.

내가 생각할 수 있는 거의 모든 사용자 애플리케이션은 상태를 핵심 개념으로 포함합니다.문서(또는 SO 게시물)를 작성하면 새로운 입력이 있을 때마다 상태가 변경됩니다.또는 비디오 게임을 플레이하는 경우 끊임없이 움직이는 경향이 있는 모든 캐릭터의 위치부터 시작하여 수많은 상태 변수가 있습니다.값 변경을 추적하지 않고 어떻게 유용한 작업을 수행할 수 있습니까?

이 문제를 논의하는 내용을 찾을 때마다 제가 갖고 있지 않은 무거운 FP 배경을 가정하는 정말 기술적인 기능적 언어로 작성되었습니다.명령형 코딩에 대해 훌륭하고 확실하게 이해하고 있지만 기능적 측면에서는 완전한 n00b인 사람에게 이것을 설명할 수 있는 방법을 아는 사람이 있습니까?

편집하다:지금까지의 답변 중 상당수는 불변 값의 장점을 나에게 확신시키려는 것 같습니다.나는 그 부분을 이해한다.완벽하게 이해됩니다.내가 이해하지 못하는 것은 변경 가능한 변수 없이 끊임없이 변화하고 변화해야 하는 값을 추적할 수 있는 방법입니다.

도움이 되었습니까?

해결책

또는 비디오 게임을하는 경우 모든 캐릭터의 위치부터 시작하여 끊임없이 움직이는 경향이 있습니다.변화하는 가치를 추적하지 않고 어떻게 유용한 일을 할 수 있습니까?

관심이 있으시면, 여기 Erlang을 사용한 게임 프로그래밍을 설명하는 일련의 기사입니다.

아마도 이 답변이 마음에 들지 않을 수도 있지만 그렇지 않을 것입니다. 얻다 기능적인 프로그램을 사용하기 전까지는.코드 샘플을 게시하고 "여기, 그렇지 않나요?"라고 말할 수 있습니다. 보다" -- 하지만 구문과 기본 원리를 이해하지 못하면 눈이 흐려질 뿐입니다.여러분의 관점에서 보면 제가 명령형 언어와 동일한 작업을 수행하지만 의도적으로 프로그래밍을 더 어렵게 만들기 위해 모든 종류의 경계를 설정하는 것처럼 보입니다.내 관점에서는, 당신은 단지 그런 일을 겪고 있는 것뿐이에요 블러브 역설.

처음에는 회의적이었지만 몇 년 전 함수형 프로그래밍 열차에 뛰어들었고 사랑에 빠졌습니다.함수형 프로그래밍의 비결은 패턴, 특정 변수 할당을 인식하고 명령형 상태를 스택으로 이동할 수 있다는 것입니다.예를 들어 for 루프는 재귀가 됩니다.

// Imperative
let printTo x =
    for a in 1 .. x do
        printfn "%i" a

// Recursive
let printTo x =
    let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
    loop 1

그다지 예쁘지는 않지만 돌연변이 없이 동일한 효과를 얻었습니다.물론 가능하다면 우리는 루프를 완전히 피하고 추상화하는 것을 좋아합니다.

// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)

Seq.iter 메서드는 컬렉션을 열거하고 각 항목에 대해 익명 함수를 호출합니다.매우 편리합니다 :)

나도 알아요, 숫자를 인쇄하는 것이 그다지 인상적이지는 않아요.그러나 게임에도 동일한 접근 방식을 사용할 수 있습니다.스택의 모든 상태를 유지하고 재귀 호출의 변경 사항으로 새 개체를 만듭니다.이러한 방식으로 각 프레임은 게임의 상태 비저장 스냅샷이며, 여기서 각 프레임은 단순히 업데이트가 필요한 상태 비저장 개체를 원하는 대로 변경하여 완전히 새로운 개체를 생성합니다.이에 대한 의사코드는 다음과 같습니다:

// imperative version
pacman = new pacman(0, 0)
while true
    if key = UP then pacman.y++
    elif key = DOWN then pacman.y--
    elif key = LEFT then pacman.x--
    elif key = UP then pacman.x++
    render(pacman)

// functional version
let rec loop pacman =
    render(pacman)
    let x, y = switch(key)
        case LEFT: pacman.x - 1, pacman.y
        case RIGHT: pacman.x + 1, pacman.y
        case UP: pacman.x, pacman.y - 1
        case DOWN: pacman.x, pacman.y + 1
    loop(new pacman(x, y))

명령형 버전과 기능형 버전은 동일하지만 기능형 버전은 분명히 변경 가능한 상태를 사용하지 않습니다.기능적 코드는 모든 상태를 스택에 유지합니다. 이 접근 방식의 좋은 점은 문제가 발생하더라도 디버깅이 쉽고 스택 추적만 필요하다는 것입니다.

모든 개체(또는 관련 개체 모음)가 자체 스레드에서 렌더링될 수 있기 때문에 이는 게임의 개체 수에 관계없이 확장됩니다.

내가 생각할 수있는 모든 사용자 애플리케이션은 상태를 핵심 개념으로 포함합니다.

함수형 언어에서는 객체의 상태를 변경하는 대신 원하는 변경 사항이 포함된 새 객체를 반환하기만 하면 됩니다.생각보다 효율적입니다.예를 들어 데이터 구조는 불변 데이터 구조로 표현하기가 매우 쉽습니다.예를 들어 스택은 구현하기 매우 쉽습니다.

using System;

namespace ConsoleApplication1
{
    static class Stack
    {
        public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
        public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
        {
            return x == null ? y : Cons(x.Head, Append(x.Tail, y));
        }
        public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
    }

    class Stack<T>
    {
        public readonly T Head;
        public readonly Stack<T> Tail;
        public Stack(T hd, Stack<T> tl)
        {
            this.Head = hd;
            this.Tail = tl;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
            Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
            Stack<int> z = Stack.Append(x, y);
            Stack.Iter(z, a => Console.WriteLine(a));
            Console.ReadKey(true);
        }
    }
}

위의 코드는 두 개의 불변 목록을 구성하고 이를 함께 추가하여 새 목록을 만든 다음 결과를 추가합니다.애플리케이션의 어느 곳에서도 변경 가능한 상태가 사용되지 않습니다.약간 부피가 커 보이지만 이는 C#이 장황한 언어이기 때문입니다.F#의 해당 프로그램은 다음과 같습니다.

type 'a stack =
    | Cons of 'a * 'a stack
    | Nil

let rec append x y =
    match x with
    | Cons(hd, tl) -> Cons(hd, append tl y)
    | Nil -> y

let rec iter f = function
    | Cons(hd, tl) -> f(hd); iter f tl
    | Nil -> ()

let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
let z = append x y
iter (fun a -> printfn "%i" a) z

목록을 생성하고 조작하는 데 변경이 필요하지 않습니다.거의 모든 데이터 구조는 기능적으로 동등한 것으로 쉽게 변환될 수 있습니다.나는 페이지를 썼다 여기 스택, 큐, 좌파 힙, 레드-블랙 트리, 게으른 목록의 불변 구현을 제공합니다.단일 코드 조각에는 변경 가능한 상태가 포함되어 있지 않습니다.트리를 "변형"시키기 위해 원하는 새 노드로 새로운 노드를 만듭니다. 이는 트리의 모든 노드를 복사할 필요가 없기 때문에 매우 효율적입니다. 새 노드에서 이전 노드를 재사용할 수 있습니다. 나무.

더 중요한 예를 사용하여 다음과 같이 썼습니다. 이 SQL 파서 이는 완전히 무국적입니다(또는 적어도 나의 코드가 상태 비저장이므로 기본 렉싱 라이브러리가 상태 비저장인지 여부는 알 수 없습니다.

상태 비저장 프로그래밍은 상태 저장 프로그래밍만큼 표현력이 풍부하고 강력합니다. 상태 비저장 프로그래밍을 시작하려면 약간의 연습만 필요합니다.물론 "가능한 경우 상태 없는 프로그래밍, 필요한 경우 상태 저장 프로그래밍"은 대부분의 불순한 함수형 언어의 모토인 것 같습니다.기능적 접근 방식이 깨끗하거나 효율적이지 않은 경우 변경 가능한 항목을 사용하는 것이 해가 되지 않습니다.

다른 팁

짧은 대답 : 당신은 할 수 없습니다.

그렇다면 불변성에 대한 소란은 무엇입니까?

당신이 명령적인 언어에 정통하다면, 당신은 "글로벌이 나쁘다"는 것을 알고 있습니다. 왜요? 코드에서 매우 어려운 종속성을 소개 (또는 소개 할 가능성이 있음). 의존성은 좋지 않습니다. 당신은 당신의 코드가되기를 원합니다 모듈 식. 프로그램의 일부는 가능한 한 다른 부분에 영향을 미치지 않습니다. 그리고 FP는 당신을 모듈 식의 거룩한 성배로 데려옵니다 : 부작용 없음 조금도. 당신은 당신의 f (x) = y를 가지고 있습니다. x를 넣고 y를 꺼냅니다. X 또는 다른 것도 변경되지 않습니다. FP는 상태에 대한 생각을 멈추고 가치 측면에서 생각을 시작하게합니다. 모든 기능은 단순히 값을 받고 새로운 값을 생성합니다.

이것은 몇 가지 장점이 있습니다.

우선, 부작용이 없다는 것은 더 간단한 프로그램을 의미하며 추론하기 쉬운 것을 의미합니다. 프로그램의 새로운 부분을 도입하는 것이 기존의 작업 부분을 방해하고 충돌시킬 것이라고 걱정하지 않습니다.

둘째, 이것은 프로그램이 사소하게 병렬화 할 수있게한다 (효율적인 병렬화는 또 다른 문제이다).

셋째, 가능한 성능 이점이 있습니다. 기능이 있다고 가정합니다.

double x = 2 * x

이제 3 인치의 값을 입력하면 6의 값을 얻습니다. 매번. 그러나 당신은 그 일을 명령 적으로 할 수 있습니다. 네. 그러나 문제는 . 내가 할 수있는:

int y = 2;
int double(x){ return x * y; }

그러나 나는 또한 할 수 있습니다

int y = 2;
int double(x){ return x * (y++); }

명령형 컴파일러는 부작용이 있는지 여부를 알지 못하므로 최적화하기가 더 어려워집니다 (즉, 매번 4가 될 필요는 없습니다). 기능적 인 사람은 내가하지 않을 것을 알고 있습니다. 따라서 "Double 2"를 볼 때마다 최적화 할 수 있습니다.

이제는 컴퓨터 메모리 측면에서 복잡한 유형의 값에 대해 매번 새로운 값을 만드는 것이 엄청나게 낭비되는 것처럼 보이지만 그렇게 될 필요는 없습니다. 만약 당신이 f (x) = y이고 x와 y가 "대부분 동일"(예 : 몇 개의 잎에서만 다른 나무)이기 때문에 x와 y는 기억의 일부를 공유 할 수 있기 때문입니다. .

따라서이 무의미한 것이 너무 위대하다면, 왜 당신이 돌연변이 가능한 상태없이 유용한 일을 할 수 없다고 대답 했는가? 음, 돌연변이가 없으면 전체 프로그램은 거대한 f (x) = y 함수 일 것입니다. 그리고 프로그램의 모든 부분에 대해서도 마찬가지입니다. 단지 기능과 "순수한"의미에서 기능. 내가 말했듯이, 이것은 f (x) = y를 의미합니다. 모든 시각. 예를 들어 readfile ( "myfile.txt")은 매번 동일한 문자열 값을 반환해야합니다. 너무 유용하지 않습니다.

따라서 모든 FP가 제공합니다 약간 돌연변이 상태. "순수한"기능적 언어 (예 : Haskell)는 Monads와 같은 다소 무서운 개념을 사용하여이를 수행하는 반면 "불순한"개념 (예 : ML)은 직접 허용합니다.

물론 기능 언어에는 일류 기능 등과 같이 프로그래밍을보다 효율적으로 만드는 다른 많은 제품이 있습니다.

기능 프로그래밍은 '상태'가 없다고 말하는 것은 약간 오해의 소지가 있으며 혼란의 원인 일 수 있습니다. 그것은 확실히 '돌연변이 가능한 상태'가 없지만 여전히 조작 된 값을 가질 수 있습니다. 그들은 단지 내내 변경 될 수 없습니다 (예 : 이전 값에서 새 값을 만들어야합니다).

이것은 과도한 단순화이지만, 클래스의 모든 속성이 생성자에서만 한 번만 설정되는 OO 언어를 가지고 있다고 상상해보십시오. 모든 방법은 정적 함수입니다. 메소드가 계산에 필요한 모든 값을 포함하는 객체를 가져간 다음 결과와 함께 새 개체를 반환하도록하여 거의 모든 계산을 수행 할 수 있습니다 (아마도 동일한 개체의 새 인스턴스 일 수 있음).

기존 코드를이 패러다임으로 변환하는 것은 '어려운'일 수 있지만 코드에 대한 완전히 다른 생각이 필요하기 때문입니다. 대부분의 경우 부작용으로서 무료로 병렬 처리 기회가 많이 있습니다.

부록: (변경해야 할 값을 추적하는 방법에 대한 편집과 관련하여)
그들은 물론 불변의 데이터 구조에 저장 될 것입니다 ...

이것은 제안 된 '솔루션'이 아니지만, 이것이 항상 작동한다는 것을 알 수있는 가장 쉬운 방법은 이러한 불변의 값을 '변수 이름'으로 키워진 구조와 같은 맵 (사전 / 해시 가능)에 저장할 수 있다는 것입니다.

분명히 실용적인 솔루션에서는 더 제정신 접근 방식을 사용하지만, 이는 다른 일이 없다면 최악의 상황을 보여 주면 호출 트리를 통해 가지고 다니는 그러한 맵으로 '돌연변이 가능한 상태를'시뮬레이션 '할 수 있습니다.

약간의 오해가 있다고 생각합니다. 순수한 기능 프로그램에는 상태가 있습니다. 차이점은 해당 상태를 모델링하는 방법입니다. 순수한 기능 프로그래밍에서 상태는 일부 상태를 취하고 다음 상태를 반환하는 기능에 의해 조작됩니다. 그런 다음 상태를 통한 시퀀싱은 일련의 순수한 기능을 통해 상태를 통과시켜 달성됩니다.

글로벌 돌연변이 상태조차도 이런 식으로 모델링 될 수 있습니다. 예를 들어 Haskell에서 프로그램은 세상에서 세계로의 기능입니다. 즉, 당신은 들어갑니다 전체 우주, 그리고 프로그램은 새로운 우주를 반환합니다. 그러나 실제로, 당신은 당신의 프로그램이 실제로 관심이있는 우주의 일부만 전달하면됩니다. 그리고 프로그램은 실제로 돌아옵니다 일련의 행동 이는 프로그램이 운영되는 운영 환경에 대한 지침으로 사용됩니다.

필수 프로그래밍 측면 에서이 설명을보고 싶었습니다. 좋아요, 기능적 언어로 정말 간단한 명령 프로그램을 살펴 보겠습니다.

이 코드를 고려하십시오.

int x = 1;
int y = x + 1;
x = x + y;
return x;

예쁜 가슴 표준 명령 코드. 흥미로운 일을하지는 않지만 그림은 괜찮습니다. 나는 당신이 여기에 관련된 주가 있다는 것에 동의 할 것이라고 생각합니다. x 변수의 값은 시간이 지남에 따라 변경됩니다. 이제 새로운 구문을 발명하여 표기법을 약간 변경해 봅시다.

let x = 1 in
let y = x + 1 in
let z = x + y in z 

이것이 의미하는 바를 더 명확하게하기 위해 괄호를 넣으십시오.

let x = 1 in (let y = x + 1 in (let z = x + y in (z)))

따라서 상태는 다음 표현의 자유 변수를 바인딩하는 일련의 순수한 표현식으로 모델링됩니다.

이 패턴은 모든 종류의 상태, 심지어 IO를 모델링 할 수 있음을 알게 될 것입니다.

여기에 있습니다 돌연변이 상태없이 코드를 작성하는 방법: 변경 상태를 변수 변수에 넣는 대신 함수의 매개 변수에 넣습니다. 그리고 루프를 쓰는 대신 재귀 기능을 작성합니다. 예를 들어이 명령 코드 :

f_imperative(y) {
  local x;
  x := e;
  while p(x, y) do
    x := g(x, y)
  return h(x, y)
}

이 기능 코드 (체계와 같은 구문)가됩니다.

(define (f-functional y) 
  (letrec (
     (f-helper (lambda (x y)
                  (if (p x y) 
                     (f-helper (g x y) y)
                     (h x y)))))
     (f-helper e y)))

또는이 haskellish 코드

f_fun y = h x_final y
   where x_initial = e
         x_final   = loop x_initial
         loop x = if p x y then loop (g x y) else x

에 관해서 기능적 프로그래머는이 작업을 좋아합니다 (귀하가 묻지 않은) 프로그램의 더 많은 부분은 무국적자입니다. 휴식을 취하지 않고 조각을 모으는 방법이 더 많을수록. 무국적 패러다임의 힘은 무국적 (또는 순도)에 있지 않습니다. 그 자체, 그러나 그것이 당신에게 강력한 글을 쓸 수있는 능력, 재사용 가능 기능과 결합.

John Hughes의 논문에서 많은 예제가 포함 된 좋은 튜토리얼을 찾을 수 있습니다. 기능 프로그래밍이 중요한 이유.

그것은 같은 일을하는 다른 방법 일뿐입니다.

숫자 3, 5 및 10과 같은 간단한 예제를 고려하십시오. 3 "(18). 이것은 특허 적으로 말도 안되는 것처럼 보이지만 본질적으로 국가 기반의 명령 프로그램이 종종 수행되는 방식입니다. 실제로, 당신은 값 3을 가지고 있지만 다른 많은 "3"를 가질 수 있지만 다릅니다. 우리는 우리가 숫자가 불변하다는 생각에 너무나 현명한 생각에 너무 뿌리를 내 렸기 때문에이 모든 것이 이상하게 보입니다.

이제 값을 불변 할 때 3, 5 및 10을 추가하는 것에 대해 생각해보십시오. 당신은 3과 5를 추가하여 다른 값 8을 생성하고, 그 값에 10을 추가하여 또 다른 값을 생성합니다.

이것들은 같은 일을하는 동등한 방법입니다. 필요한 모든 정보는 두 가지 방법 모두가 다른 형태로 존재합니다. 하나는 정보가 상태와 상태 변경 규칙에 존재합니다. 다른 한편으로는 정보가 불변의 데이터와 기능적 정의에 존재합니다.

뒤늦게 논의가 되었지만 함수형 프로그래밍에 어려움을 겪고 있는 분들을 위해 몇 가지 사항을 추가하고 싶었습니다.

  1. 기능적 언어는 명령형 언어와 완전히 동일한 상태 업데이트를 유지하지만 업데이트된 상태를 후속 함수 호출에 전달하여 이를 수행합니다..다음은 수직선을 따라 이동하는 매우 간단한 예입니다.귀하의 상태는 귀하의 현재 위치입니다.

먼저 명령형 방식(의사코드)

moveTo(dest, cur):
    while (cur != dest):
         if (cur < dest):
             cur += 1
         else:
             cur -= 1
    return cur

이제 기능적 방식(의사코드)입니다.나는 명령형 배경을 가진 사람들이 실제로 이 코드를 읽을 수 있기를 원하기 때문에 삼항 연산자에 크게 의존하고 있습니다.따라서 삼항 연산자를 많이 사용하지 않는 경우(필수적인 날에는 항상 피했습니다) 작동 방식은 다음과 같습니다.

predicate ? if-true-expression : if-false-expression

거짓 표현식 대신 새로운 삼항 표현식을 넣어 삼항 표현식을 연결할 수 있습니다.

predicate1 ? if-true1-expression :
predicate2 ? if-true2-expression :
else-expression

이를 염두에 두고 여기에 기능적 버전이 있습니다.

moveTo(dest, cur):
    return (
        cur == dest ? return cur :
        cur < dest ? moveTo(dest, cur + 1) : 
        moveTo(dest, cur - 1)
    )

이것은 사소한 예입니다.이것이 게임 세계에서 사람들을 움직이는 것이라면 화면에 개체의 현재 위치를 그리고 개체가 움직이는 속도에 따라 각 호출에 약간의 지연을 도입하는 등의 부작용을 도입해야 합니다.그러나 여전히 변경 가능한 상태는 필요하지 않습니다.

  1. 교훈은 기능적 언어가 다른 매개변수를 사용하여 함수를 호출하여 상태를 "변형"시킨다는 것입니다.분명히 이것은 실제로 변수를 변경하지는 않지만 유사한 효과를 얻는 방법입니다.즉, 함수형 프로그래밍을 하려면 재귀적으로 생각하는 데 익숙해져야 합니다.

  2. 재귀적으로 생각하는 법을 배우는 것은 어렵지 않지만 연습과 툴킷이 모두 필요합니다.팩토리얼을 계산하기 위해 재귀를 사용한 "Java 배우기" 책의 작은 섹션에서는 잘리지 않습니다.재귀에서 반복 프로세스를 만드는 것과 같은 기술 툴킷이 필요합니다(이것이 꼬리 재귀가 기능적 언어에 필수적인 이유입니다), 연속, 불변 등.액세스 수정자, 인터페이스 등에 대해 배우지 않고는 OO 프로그래밍을 할 수 없습니다.함수형 프로그래밍에서도 마찬가지입니다.

제가 추천하는 것은 Little Schemer("읽기"가 아니라 "실행"이라고 말함)를 수행한 다음 SICP의 모든 연습을 수행하는 것입니다.작업을 마치면 시작했을 때와는 다른 두뇌를 ​​갖게 될 것입니다.

함수형 프로그래밍 피하다 상태와 강조한다 기능.상태가 없다는 것은 결코 존재하지 않습니다. 그러나 상태는 실제로 불변이거나 작업 중인 아키텍처에 포함된 것일 수 있습니다.파일 시스템에서 파일을 로드하기만 하는 정적 웹 서버와 루빅스 큐브를 구현하는 프로그램 간의 차이점을 생각해 보세요.전자는 요청을 파일 경로 요청으로 해당 파일 내용의 응답으로 전환하도록 설계된 기능 측면에서 구현될 예정입니다.약간의 구성 외에는 사실상 어떤 상태도 필요하지 않습니다(파일 시스템 '상태'는 실제로 프로그램 범위를 벗어납니다.프로그램은 파일의 상태에 관계없이 동일한 방식으로 작동합니다.하지만 후자의 경우에는 큐브와 해당 큐브에 대한 작업이 상태를 변경하는 방법에 대한 프로그램 구현을 모델링해야 합니다.

실제로는 변한 상태가없는 언어로도 변한 상태처럼 보이는 것을 갖는 것은 매우 쉽습니다.

유형의 함수를 고려하십시오 s -> (a, s). Haskell Syntax에서 번역하면 유형의 하나의 매개 변수를 취하는 함수를 의미합니다. "s"그리고 유형의 한 쌍의 값을 반환합니다"a" 그리고 "s". 만약에 s 우리 상태의 유형이며,이 기능은 하나의 상태를 취하고 새로운 상태를 반환하고 값을 반환합니다 (항상 "단위"를 반환 할 수 있습니다. (), 그것은 일종의 것과 같습니다.void"C/C ++에서"a"type). 이와 같은 유형으로 여러 기능의 호출을 체인하는 경우 (상태를 하나의 함수에서 반환하고 다음 기능으로 전달 함)"Mutable "상태가 있습니다 (실제로 각 기능에 새 상태를 만들고 새로운 상태를 만들고 있습니다. 오래된 것을 버리고).

돌연변이 상태를 프로그램이 실행중인 "공간"으로 상상하고 시간 차원을 생각하면 이해하기가 더 쉬울 수 있습니다. Instant T1에서 "공간"은 특정 조건에 있습니다 (예 : 일부 메모리 위치는 값 5를 갖습니다). 이후의 순간 T2에서는 다른 조건에 있습니다 (예 : 메모리 위치는 이제 값 10을 갖는다). 이 시간 각각의 "슬라이스"는 상태이며 불변이 불변입니다 (변경하기 위해 시간을 거슬러 올라갈 수 없습니다). 따라서,이 관점에서, 당신은 시간 화살표 (돌연변이 상태)와 함께 전체 시공간에서 시공간 조각 세트 (여러 불변 상태)로 갔으며, 프로그램은 각 조각을 값으로 취급하고 각각을 계산하는 것입니다. 이전에 적용되는 함수로서.

좋아, 아마도 이해하기 쉽지 않았을 것입니다 :-)

전체 프로그램 상태를 값으로 명시 적으로 표현하는 것은 불충분 해 보일 수 있으며, 다음 순간에만 폐기되도록 만들어야합니다 (새 제품이 생성 된 후). 일부 알고리즘의 경우 자연 스러울 수 있지만 그렇지 않은 경우 또 다른 트릭이 있습니다. 실제 상태 대신 마커에 지나지 않는 가짜 상태를 사용할 수 있습니다 (이 가짜 상태의 유형을 부르겠습니다. State#). 이 가짜 상태는 언어의 관점에서 존재하며 다른 값과 마찬가지로 전달되지만 컴파일러는 기계 코드를 생성 할 때 완전히 생략합니다. 실행 순서를 표시하는 역할을합니다.

예를 들어, 컴파일러가 우리에게 다음과 같은 기능을 제공한다고 가정합니다.

readRef :: Ref a -> State# -> (a, State#)
writeRef :: Ref a -> a -> State# -> (a, State#)

이 Haskell과 같은 선언에서 번역, readRef 포인터 나 핸들과 유사한 값에 유사한 것을 수신합니다. "a", 가짜 상태와 유형의 값을 반환합니다"a"첫 번째 매개 변수와 새로운 가짜 상태를 지적했습니다. writeRef 비슷하지만 대신 가리키는 값을 변경합니다.

전화하면 readRef 그런 다음 다시 돌아온 가짜 상태를 전달하십시오 writeRef (아마도 중간에 관련없는 함수에 대한 다른 호출과 함께,이 상태 값은 함수 호출의 "체인"을 생성하면) 작성된 값을 반환합니다. 전화해도됩니다 writeRef 다시 동일한 포인터/핸들로 동일한 메모리 위치에 쓸 것입니다. 그러나 개념적으로 새로운 (가짜) 상태를 반환하기 때문에 (가짜) 상태는 여전히 모방 가능합니다 (새로운 것은 "생성되었습니다"). 컴파일러는 계산되어야하는 실제 상태 변수가 있으면 함수를 호출해야하지만 실제 하드웨어의 전체 (가연성) 상태가있는 유일한 상태는 함수를 호출해야합니다.

(Haskell을 아는 사람들은 내가 물건을 많이 단순화하고 몇 가지 중요한 세부 사항을 얻었습니다. 자세한 내용을보고 싶은 사람들을 위해 살펴보십시오. Control.Monad.State ~로부터 mtl, 그리고 ST s 그리고 IO (일명 ST RealWorld) 모나드.)

당신은 왜 그런 원형 교차로 (언어로 변한 상태를 갖지 않고) 왜 그렇게하는지 궁금 할 것입니다. 진정한 장점은 당신이 가지고 있다는 것입니다 다시 귀하의 프로그램의 상태. 이전에 암묵적이었던 것 (귀하의 프로그램 상태는 글로벌이었고 멀리서 행동) 이제 명시 적입니다. 상태를 받고 반환하지 않는 기능은 그것을 수정하거나 영향을받을 수 없습니다. 그들은 "순수"입니다. 더 좋은 점은 별도의 상태 스레드를 가질 수 있으며 약간의 유형의 마법을 사용하면 불순한 상태로 만들지 않고 순수한 계산에 명령적인 계산을 포함시키는 데 사용될 수 있습니다 ( ST Haskell의 Monad는이 트릭에 일반적으로 사용되는 것입니다. 그만큼 State# 위에서 언급 한 것은 실제로 GHC입니다 State# s, 그 구현에 의해 사용됩니다 ST 그리고 IO 모나스).

다른 사람들이주는 위대한 답변 외에도 수업에 대해 생각해보십시오. Integer 그리고 String 자바에서. 이 클래스의 사례는 불변이지만 인스턴스를 변경할 수 없기 때문에 클래스를 쓸모 없게 만드는 것은 아닙니다. 불변성은 당신에게 약간의 안전을 제공합니다. 문자열 또는 정수 인스턴스를 사용하는지 알고 있습니다. Map, 키는 변경할 수 없습니다. 이것을 Date 자바 수업 :

Date date = new Date();
mymap.put(date, date.toString());
// Some time later:
date.setTime(new Date().getTime());

당신은 당신의지도에서 키를 조용히 변경했습니다! 기능 프로그래밍과 같은 불변의 물체로 작업하는 것은 훨씬 더 깨끗합니다. 어떤 부작용이 발생하는지 추론하기가 더 쉽습니다. 이것은 프로그래머가 더 쉽고 최적화기가 더 쉽다는 것을 의미합니다.

게임과 같은 상호 대화식 응용 프로그램의 경우 기능적 반응성 프로그래밍 당신의 친구입니다 : 당신이 당신의 게임 세계의 속성을 다음과 같이 공식화 할 수 있다면 시변 값 (및/또는 이벤트 스트림), 당신은 준비되었습니다! 이 공식은 때때로 국가를 돌연변이하는 것보다 훨씬 자연스럽고 의도적 인 반응이 될 것입니다. x = v * t. 그리고 더 나은 것은 게임의 규칙이 그런 식으로 작성되었습니다. 구성하다 객체 지향 추상화보다 낫습니다. 예를 들어,이 경우, 볼의 속도는 시변 값 일 수 있으며, 이는 볼의 충돌로 구성된 이벤트 스트림에 따라 다릅니다. 보다 구체적인 설계 고려 사항은 참조하십시오 Elm에서 게임 제작.

이것이 바로 Fortran이 공통 블록없이 작동하는 방식입니다. 전달한 값과 로컬 변수가있는 방법을 작성합니다. 그게 다야.

객체 지향 프로그래밍은 우리에게 상태와 행동을 함께 가져 왔지만 1994 년 C ++에서 처음 접한 것은 새로운 아이디어였습니다.

Geez, 나는 기계 엔지니어 였을 때 기능 프로그래머 였고 그것을 몰랐습니다!

명심하십시오 : 기능적 언어가 완전하고 있습니다. 따라서, 당신이 불멸의 언어로 수행 할 유용한 작업은 기능적 언어로 수행 할 수 있습니다. 그러나 하루가 끝나면 하이브리드 접근법에 대해 할 말이 있다고 생각합니다. F# 및 Clojure와 같은 언어 (그리고 다른 사람들은 다른 사람들은 확신합니다)는 무국적 디자인을 장려하지만 필요할 때는 돌연변이를 허용합니다.

유용한 순수한 기능 언어를 가질 수 없습니다. 항상 다루어야 할 돌연변이 수준이있을 것입니다. IO는 한 가지 예입니다.

기능 언어를 사용하는 또 다른 도구로 생각하십시오. 그것은 특정한 것들에 좋지만 다른 것들은 아닙니다. 당신이 준 게임 예제는 기능적 언어를 사용하는 가장 좋은 방법이 아닐 수도 있습니다. 최소한 화면에는 FP와 관련하여 아무것도 할 수없는 돌연변이 상태가 있습니다. 문제를 생각하는 방식과 FP로 해결하는 문제의 유형은 명령적인 프로그래밍으로 사용되는 것과 다릅니다.

많은 재귀를 사용하여.

f#의 tic tac 발가락 (기능적 언어)

이것은 매우 간단합니다. 기능 프로그래밍에서 원하는만큼 많은 변수를 사용할 수 있지만 현지의 변수 (내부 함수 포함). 따라서 코드를 함수로 래핑하고 해당 함수 중 (전달 된 매개 변수 및 반환 된 값으로)를 앞뒤로 전달하십시오. 그리고 그게 전부입니다!

예는 다음과 같습니다.

function ReadDataFromKeyboard() {
    $input_values = $_POST[];
    return $input_values;
}
function ProcessInformation($input_values) {
    if ($input_values['a'] > 10)
        return ($input_values['a'] + $input_values['b'] + 3);
    else if ($input_values['a'] > 5)
        return ($input_values['b'] * 3);
    else
        return ($input_values['b'] - $input_values['a'] - 7);
}
function DisplayToPage($data) {
    print "Based your input, the answer is: ";
    print $data;
    print "\n";
}

/* begin: */
DisplayToPage (
    ProcessInformation (
        GetDataFromKeyboard()
    )
);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top