기능 프로그래머가 되길 원합니다 (5 부)

기능 프로그래밍 개념을 이해하기위한 첫 번째 단계를 취하는 것이 가장 중요하고 때로는 가장 어려운 단계입니다. 하지만 꼭 그럴 필요는 없습니다. 올바른 관점이 아닙니다.

이전 부품 : 1 부, 2 부, 3 부, 4 부

참조 투명도

참조 투명도는 순수한 함수를 표현식으로 안전하게 대체 할 수 있음을 나타내는 멋진 용어입니다. 이에 대한 예가 도움이 될 것입니다.

대수에서 다음 수식이있을 때 :

y = x + 10

그리고 들었다 :

x = 3

x를 방정식으로 다시 바꿔서 얻을 수 있습니다.

y = 3 + 10

방정식은 여전히 ​​유효합니다. 순수한 함수로 같은 종류의 대체를 할 수 있습니다.

제공된 문자열 주위에 작은 따옴표를 넣는 Elm의 함수는 다음과 같습니다.

인용 str =
    " '"++ str ++ "'"

다음은이를 사용하는 코드입니다.

findError 키 =
    "++ (따옴표 키)를 찾을 수 없습니다"

여기서 findError는 키 검색에 실패했을 때 오류 메시지를 작성합니다.

인용 함수는 순수하기 때문에 findError의 함수 호출을 인용 함수의 본문 (단지 표현식)으로 간단히 바꿀 수 있습니다.

findError 키 =
   ""++ ( " '"++ str ++ "'")를 찾을 수 없습니다.

이것을 역 리팩토링 (Reverse Refactoring)이라고 부르는데, 프로그래머 나 프로그램 (예 : 컴파일러 및 테스트 프로그램)이 코드를 추론하기 위해 사용할 수있는 프로세스입니다.

재귀 함수에 대해 추론 할 때 특히 유용합니다.

실행 순서

대부분의 프로그램은 단일 스레드입니다. 즉, 한 번에 하나의 코드 만 실행됩니다. 멀티 스레드 프로그램을 사용하더라도 I / O가 완료 될 때까지 대부분의 스레드가 차단됩니다 (예 : 파일, 네트워크 등

이것이 코드를 작성할 때 순서 단계로 자연스럽게 생각하는 이유 중 하나입니다.

1. 빵을 꺼내
2. 토스터에 슬라이스 2 개를 넣습니다
3. 어둠을 선택
4. 레버를 아래로 밉니다
5. 토스트가 나올 때까지 기다립니다
6. 토스트 제거
7. 버터를 꺼내
8. 버터 나이프를 얻으십시오
9. 버터 토스트

이 예에서는 버터를 얻고 빵을 굽는 두 가지 독립적 인 작업이 있습니다. 9 단계에서만 상호 의존적이됩니다.

1 단계와 6 단계는 서로 독립적이므로 7 단계와 8 단계를 동시에 수행 할 수 있습니다.

그러나 우리가 이것을하는 순간 일이 복잡해집니다.

실 1
--------
1. 빵을 꺼내
2. 토스터에 슬라이스 2 개를 넣습니다
3. 어둠을 선택
4. 레버를 아래로 밉니다
5. 토스트가 나올 때까지 기다립니다
6. 토스트 제거
실 2
--------
1. 버터를 꺼내
2. 버터 나이프를 얻으십시오
3. 스레드 1이 완료 될 때까지 기다리십시오.
4. 버터 토스트

스레드 1이 실패하면 스레드 2는 어떻게됩니까? 두 스레드를 조정하는 메커니즘은 무엇입니까? 누가 토스트를 소유합니까 : 스레드 1, 스레드 2 또는 둘 다?

이러한 복잡성에 대해 생각하지 않고 프로그램을 단일 스레드로 두는 것이 더 쉽습니다.

그러나 프로그램의 모든 가능한 효율성을 압박 할 가치가 있다면 멀티 스레딩 소프트웨어를 작성하기위한 엄청난 노력을 기울여야합니다.

그러나 멀티 스레딩에는 두 가지 주요 문제가 있습니다. 첫째, 다중 스레드 프로그램은 쓰기, 읽기, 추론, 테스트 및 디버그가 어렵습니다.

둘째, 일부 언어 (예 : 자바 스크립트는 멀티 스레딩을 지원하지 않으며 그렇지 않은 경우에는 지원하지 않습니다.

그러나 순서가 중요하지 않고 모든 것이 병렬로 실행된다면 어떨까요?

이 소리가 미치게 들리지만 소리만큼 혼란 스럽지는 않습니다. 이를 설명하기 위해 몇 가지 Elm 코드를 살펴 보겠습니다.

buildMessage 메시지 값 =
    방해
        upperMessage =
            String.toUpper 메시지
        quotedValue =
            " '"++ value ++ "'"
    ...에서
        upperMessage ++ ":"++ quotedValue

여기서 buildMessage는 메시지와 값을 취한 다음 대문자로 된 메시지, 콜론 및 작은 따옴표로 된 값을 생성합니다.

upperMessage와 quotedValue가 어떻게 독립적인지 확인하십시오. 우리는 이것을 어떻게 알 수 있습니까?

독립성을 위해 진실해야 할 두 가지가 있습니다. 첫째, 그것들은 순수한 기능이어야합니다. 이것은 다른 쪽의 실행에 영향을 받아서는 안되기 때문에 중요합니다.

그들이 순수하지 않다면, 우리는 그들이 독립적이라는 것을 결코 알 수 없었습니다. 이 경우 프로그램에서 호출 된 순서에 따라 실행 순서를 결정해야합니다. 모든 명령형 언어가 작동하는 방식입니다.

독립성을 위해 진실로해야 할 두 번째는 한 함수의 출력이 다른 함수의 입력으로 사용되지 않는다는 것입니다. 이 경우 두 번째를 시작하기 전에 하나가 완료 될 때까지 기다려야합니다.

이 경우 upperMessage 및 quotedValue는 모두 순수하며 다른 출력도 필요하지 않습니다.

따라서이 두 기능은 모든 주문에서 실행될 수 있습니다.

컴파일러는 프로그래머의 도움없이이 결정을 내릴 수 있습니다. 이는 부작용이 없는지 판단하기가 매우 어렵 기 때문에 순수 기능 언어에서만 가능합니다.

순수 기능 언어에서의 실행 순서는 컴파일러에 의해 결정될 수 있습니다.

CPU 속도가 빨라지지 않는다는 점을 고려하면 매우 유리합니다. 대신 제조업체는 점점 더 많은 코어를 추가하고 있습니다. 이는 코드가 하드웨어 수준에서 병렬로 실행될 수 있음을 의미합니다.

불행히도, Imperative Languages를 사용하면 매우 거친 수준을 제외하고는 이러한 핵심을 최대한 활용할 수 없습니다. 그러나 그렇게하려면 프로그램의 아키텍처를 크게 바꿔야합니다.

순수 기능 언어 (Functional Functional Languages)를 사용하면 단일 코드 행을 변경하지 않고도 미세한 수준에서 CPU 코어를 자동으로 활용할 수 있습니다.

타입 주석

정적 유형 언어에서 유형은 인라인으로 정의됩니다. 다음은 설명 할 Java 코드입니다.

공개 정적 문자열 따옴표 (문자열 str) {
    " '"+ str + "'"반환;
}

타이핑이 함수 정의와 어떻게 인라인되는지 확인하십시오. 제네릭을 사용하면 더욱 악화됩니다.

개인 최종 맵 <정수, 문자열> getPerson (맵 <문자열, 문자열> 명, 정수 personId) {
   // ...
}

나는 눈에 띄게 만드는 유형을 굵게 표시했지만 여전히 함수 정의를 방해합니다. 변수 이름을 찾으려면주의해서 읽어야합니다.

동적 형식 언어에서는 문제가되지 않습니다. 자바 스크립트에서 다음과 같은 코드를 작성할 수 있습니다.

var getPerson = 함수 (people, personId) {
    // ...
};

불쾌한 유형 정보가 모두 방해받지 않고 읽기가 훨씬 쉽습니다. 유일한 문제는 입력의 안전을 포기한다는 것입니다. 우리는 이러한 매개 변수를 쉽게 뒤로 전달할 수 있습니다. 즉 사람 번호 및 personId 객체입니다.

우리는 프로그램이 실행되기 전까지는 알 수 없었습니다. 컴파일되지 않기 때문에 Java에서는 그렇지 않습니다.

그러나 우리가 두 세계를 모두 이용할 수 있다면 어떨까요? Java의 안전성과 Javascript의 구문 단순성.

우리가 할 수있는 것으로 밝혀졌습니다. Elm의 타입 주석 기능은 다음과 같습니다.

추가 : Int-> Int-> Int
x y = 추가
    x + y

타입 정보가 어떻게 다른 줄에 있는지 확인하십시오. 이러한 분리는 세상을 변화시킵니다.

이제 타입 주석에 오타가 있다고 생각할 수 있습니다. 처음 보았을 때했던 일을 압니다. 첫 번째->는 쉼표 여야한다고 생각했습니다. 그러나 오타가 없습니다.

암시 괄호와 함께 보면 조금 더 의미가 있습니다.

추가 : Int-> (Int-> Int)

이것은 add는 Int 유형의 단일 매개 변수를 사용하고 단일 매개 변수 Int를 사용하고 Int를 반환하는 함수를 반환하는 함수입니다.

괄호가 표시된 다른 유형 주석은 다음과 같습니다.

doSomething : String-> (Int-> (String-> 문자열))
doSomething 접두사 값 접미사 =
    접두사 ++ (toString 값) ++ 접미사

doSomething은 String 유형의 단일 매개 변수를 사용하고 Int 유형의 단일 매개 변수를 사용하는 함수를 리턴하고 String 유형의 단일 매개 변수를 사용하고 String을 리턴하는 함수를 리턴합니다.

모든 것이 단일 매개 변수를 취하는 방법에 주목하십시오. 모든 기능이 Elm에서 카레되기 때문입니다.

괄호는 항상 오른쪽에 암시되어 있으므로 반드시 필요한 것은 아닙니다. 그래서 우리는 간단히 쓸 수 있습니다 :

doSomething : 문자열-> Int-> 문자열-> 문자열

함수를 매개 변수로 전달할 때는 괄호가 필요합니다. 그것들이 없으면, 타입 주석은 모호 할 것입니다. 예를 들면 다음과 같습니다.

takes2Params : Int-> Int-> 문자열
takes2Params num1 num2 =
    -무언가를해라

다음과 매우 다릅니다.

takes1Param : (Int-> Int)-> 문자열
takes1Param f =
    -무언가를해라

takes2Param은 Int와 다른 Int라는 2 개의 매개 변수를 요구하는 함수입니다. 반면 takes1Param에는 Int와 다른 Int를 취하는 함수 1 개의 매개 변수가 필요합니다.

지도에 대한 유형 주석은 다음과 같습니다.

지도 : (a-> b)-> 목록 a-> 목록 b
지도 f 목록 =
    // ...

여기서 f는 유형 (a-> b), 즉 a 유형의 단일 매개 변수를 사용하고 b 유형의 무언가를 리턴하는 함수이므로 괄호가 필요합니다.

여기에서 유형 a는 모든 유형입니다. 유형이 대문자 인 경우 명시 적 유형입니다 (예 : 끈. 유형이 소문자이면 모든 유형이 될 수 있습니다. 여기에 문자열이 될 수 있지만 Int 일 수도 있습니다.

(a-> a)가 표시되면 입력 유형과 출력 유형이 같아야한다는 것입니다. 그들이 무엇인지는 중요하지 않지만 일치해야합니다.

그러나지도의 경우 (a-> b)가 있습니다. 즉, 다른 유형을 반환 할 수 있지만 동일한 유형을 반환 할 수도 있습니다.

그러나 a에 대한 유형이 결정되면 전체 서명에 대한 유형이어야합니다. 예를 들어, a가 Int이고 b가 String 인 경우 서명은 다음과 같습니다.

(Int-> 문자열)-> List Int-> 목록 문자열

여기에서 모든 a는 Int로 바뀌었고 모든 b는 String으로 바뀌 었습니다.

List Int 유형은 목록에 Ints가 포함되어 있고 List String은 목록에 문자열이 포함되어 있음을 의미합니다. Java 또는 다른 언어로 제네릭을 사용한 경우이 개념이 익숙해야합니다.

나의 두뇌!!!!

지금은 충분합니다.

이 기사의 마지막 부분에서는 일상적인 업무에서 배운 내용, 즉 Functional Javascript 및 Elm을 사용하는 방법에 대해 설명합니다.

다음 : 6 부

이것을 좋아한다면 아래의 을 클릭하면 다른 사람들이 이것을 Medium에서 볼 수 있습니다.

Elm에서 Functional Programming을 사용하여 서로 배우고 웹 응용 프로그램을 개발하도록 돕는 웹 개발자 커뮤니티에 참여하려면 Facebook Group, Elm Programming Learn https://www.facebook.com/groups/learnelm/을 확인하십시오.

내 트위터 : @cscalfani