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

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

이전 부품 : 1 부

친절한 알림

코드를 천천히 읽으십시오. 계속 진행하기 전에 이해해야합니다. 각 섹션은 이전 섹션 위에 구축됩니다.

서두르면 나중에 중요한 뉘앙스를 놓칠 수 있습니다.

리팩토링

잠시 동안 리팩토링에 대해 생각해 봅시다. 자바 스크립트 코드는 다음과 같습니다.

함수 validateSsn (ssn) {
    if (/^\d{3}-\d{2}-\d{4}$/.exec(ssn))
        console.log ( '유효한 SSN');
    그밖에
        console.log ( '잘못된 SSN');
}
기능 validatePhone (전화) {
    if (/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))
        console.log ( '유효한 전화 번호');
    그밖에
        console.log ( '잘못된 전화 번호');
}

우리는 시간이 지남에 따라 이와 같은 코드를 모두 작성했습니다.이 두 기능은 실제로 동일하며 몇 가지 요소 (굵게 표시됨) 만 다릅니다.

validateSsn을 복사하고 붙여 넣기 및 편집하여 validatePhone을 작성하는 대신 단일 기능을 작성하고 붙여 넣기 후 편집 한 항목을 매개 변수화해야합니다.

이 예에서는 값, 정규식 및 인쇄 된 메시지 (최소한 인쇄 된 메시지의 마지막 부분)를 매개 변수화합니다.

리팩토링 된 코드 :

함수 validateValue (값, 정규식, 유형) {
    if (regex.exec (value))
        console.log ( '잘못된'+ 유형);
    그밖에
        console.log ( '유효한'+ 유형);
}

이전 코드의 ssn 및 phone 매개 변수는 이제 값으로 표시됩니다.

정규식 / ^ \ d {3}-\ d {2}-\ d {4} $ / 및 / ^ \ (\ d {3} \) \ d {3}-\ d {4} $ / 정규식으로 표시됩니다.

마지막으로‘SSN’및‘전화 번호’메시지의 마지막 부분이 유형별로 표시됩니다.

하나의 기능을 갖는 것이 두 개의 기능을 갖는 것보다 훨씬 낫습니다. 또는 더 나쁜 3, 4 또는 10 기능. 이렇게하면 코드가 깨끗하고 유지 관리 가능합니다.

예를 들어, 버그가있는 경우 전체 코드베이스를 검색하는 대신이 기능을 붙여넣고 수정 한 위치를 찾기 위해 한곳에서 버그를 수정하면됩니다.

그러나 다음과 같은 상황이 발생하면 어떻게됩니까?

함수 validateAddress (주소) {
    if (parseAddress (주소))
        console.log ( '유효한 주소');
    그밖에
        console.log ( '잘못된 주소');
}
function validateName (name) {
    if (parseFullName (이름))
        console.log ( '유효한 이름');
    그밖에
        console.log ( '잘못된 이름');
}

여기에서 parseAddress 및 parseFullName은 문자열을 가져와 구문 분석하는 경우 true를 반환하는 함수입니다.

우리는 이것을 어떻게 리팩터링합니까?

이전과 마찬가지로 주소와 이름에 값을 사용하고‘주소’와‘이름’에 유형을 사용할 수 있지만 정규 표현식은 예전처럼 기능했습니다.

함수 만 매개 변수로 전달할 수 있다면 ...

더 높은 주문 기능

많은 언어는 함수를 매개 변수로 전달하는 것을 지원하지 않습니다. 어떤 사람들은 쉽지만 쉽지 않습니다.

함수형 프로그래밍에서 함수는 언어의 일류 시민입니다. 다시 말해, 함수는 또 다른 값일뿐입니다.

함수는 값일 뿐이므로 매개 변수로 전달할 수 있습니다.

Javascript가 순수 기능 언어는 아니지만 일부 기능 조작을 수행 할 수 있습니다. 구문 분석 함수를 parseFunc라는 매개 변수로 전달하여 단일 함수로 리팩토링 된 마지막 두 함수는 다음과 같습니다.

validateValueWithFunc (value, parseFunc, type) 함수 {
    if (parseFunc (value))
        console.log ( '잘못된'+ 유형);
    그밖에
        console.log ( '유효한'+ 유형);
}

우리의 새로운 기능을 고차 함수라고합니다.

고차 함수는 함수를 매개 변수, 리턴 함수 또는 둘 다로 사용합니다.

이제 4 개의 이전 함수에 대해 상위 함수를 호출 할 수 있습니다 (Regex.exec는 일치하는 것이 발견 될 때 정확한 값을 반환하기 때문에 Javascript에서 작동 함).

validateValueWithFunc ( '123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');
validateValueWithFunc ( '(123) 456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, '전화');
validateValueWithFunc ( '123 Main St.', parseAddress, 'Address');
validateValueWithFunc ( 'Joe Mama', parseName, 'Name');

이것은 거의 동일한 네 개의 기능을 갖는 것보다 훨씬 낫습니다.

그러나 정규식에 주목하십시오. 그들은 조금 장황하다. 코드를 정리하여 코드를 정리해 보겠습니다.

var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;
var parsePhone = /^\(\d{3}\)\d{3}-\d{4}$/.exec;
validateValueWithFunc ( '123-45-6789', 구문 분석, 'SSN');
validateValueWithFunc ( '(123) 456-7890', 구문 분석 전화, '전화');
validateValueWithFunc ( '123 Main St.', parseAddress, 'Address');
validateValueWithFunc ( 'Joe Mama', parseName, 'Name');

그게 낫다. 이제 전화 번호를 구문 분석 할 때 정규식을 복사하여 붙여 넣을 필요가 없습니다.

그러나 구문 분석 및 구문 분석 전화뿐만 아니라 구문 분석 할 정규 표현식이 더 있다고 가정하십시오. 정규식 파서를 만들 때마다 .exec를 끝에 추가해야합니다. 그리고 나를 믿어, 이것은 잊어 버리기 쉽다.

exec 함수를 반환하는 고차 함수를 만들어서 이것을 막을 수 있습니다.

makeRegexParser (regex) 함수 {
    정규식 반환;
}
var parseSsn = makeRegexParser (/ ^ \ d {3}-\ d {2}-\ d {4} $ /);
var parsePhone = makeRegexParser (/ ^ \ (\ d {3} \) \ d {3}-\ d {4} $ /);
validateValueWithFunc ( '123-45-6789', 구문 분석, 'SSN');
validateValueWithFunc ( '(123) 456-7890', 구문 분석 전화, '전화');
validateValueWithFunc ( '123 Main St.', parseAddress, 'Address');
validateValueWithFunc ( 'Joe Mama', parseName, 'Name');

여기에서 makeRegexParser는 정규식을 사용하고 문자열을받는 exec 함수를 반환합니다. validateValueWithFunc는 문자열, 값을 구문 분석 함수, 즉 exec에 전달합니다.

parseSsn 및 parsePhone은 정규식의 exec 함수 인 이전과 사실상 동일합니다.

물론, 이것은 약간의 개선이지만 여기에 표시되어 함수를 반환하는 고차 함수의 예를 보여줍니다.

그러나 makeRegexParser가 훨씬 더 복잡한 경우 이러한 변경을 통해 얻을 수있는 이점을 상상할 수 있습니다.

다음은 함수를 반환하는 고차 함수의 다른 예입니다.

makeAdder (constantValue) 함수 {
    함수 가산기 리턴 (값) {
        상수 값 + 값 반환;
    };
}

여기에는 constantValue를 가져와 전달되는 모든 값에 해당 상수를 추가하는 adder를 반환하는 makeAdder가 있습니다.

사용 방법은 다음과 같습니다.

var add10 = makeAdder (10);
console.log (add10 (20)); // 30을 인쇄합니다
console.log (add10 (30)); // 40을 인쇄합니다
console.log (add10 (40)); // 50을 인쇄합니다

상수 10을 makeAdder에 전달하여 add10 함수를 작성합니다. makeAdder는 모든 것에 10을 추가하는 함수를 리턴합니다.

makeAddr이 리턴 된 후에도 adder 함수는 constantValue에 액세스 할 수 있습니다. addValue가 생성 될 때 constantValue가 해당 범위에 있었기 때문입니다.

이 동작이 없으면 함수를 반환하는 함수는 그다지 유용하지 않기 때문에이 동작은 매우 중요합니다. 따라서 우리는 그들이 어떻게 작동하고이 행동이 무엇인지 이해하는 것이 중요합니다.

이 동작을 폐쇄라고합니다.

폐쇄

다음은 클로저를 사용하는 함수의 예입니다.

grandParent (g1, g2) {
    var g3 = 3;
    반환 함수 parent (p1, p2) {
        var p3 = 33;
        반환 함수 child (c1, c2) {
            var c3 = 333;
            리턴 g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;
        };
    };
}

이 예에서 자녀는 변수, 부모 변수 및 grandParent 변수에 액세스 할 수 있습니다.

부모는 변수 및 grandParent 변수에 액세스 할 수 있습니다.

grandParent는 변수에만 접근 할 수 있습니다.

(설명은 위의 피라미드를 참조하십시오.)

사용 예는 다음과 같습니다.

var parentFunc = grandParent (1, 2); // parent ()를 반환
var childFunc = parentFunc (11, 22); // child ()를 반환
console.log (childFunc (111, 222)); // 738을 인쇄합니다
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738

grandParent가 parent를 반환하므로 parentFunc는 부모의 범위를 유지합니다.

마찬가지로 childFunc는 부모 인 parentFunc가 child를 반환하므로 자식의 범위를 유지합니다.

함수가 생성되면 생성시 해당 범위의 모든 변수에 함수 수명 동안 액세스 할 수 있습니다. 여전히 참조가있는 한 함수가 존재합니다. 예를 들어 childFunc가 여전히 참조하는 한 자녀의 범위가 존재합니다.

클로저는 해당 함수에 대한 참조로 유지되는 함수의 범위입니다.

Javascript에서는 변수를 변경할 수 있으므로 클로저에 문제가 있습니다. 즉, 변수를 닫은 시점부터 반환 된 함수가 호출 된 시점까지 값을 변경할 수 있습니다.

고맙게도 기능 언어의 변수는 변경할 수 없으므로 이러한 일반적인 버그와 혼동을 피할 수 있습니다.

나의 두뇌!!!!

지금은 충분합니다.

이 기사의 다음 부분에서는 기능 구성, 커리, 일반적인 기능 기능 (예 :지도, 필터, 접기 등) 등에 대해 이야기하겠습니다.

다음 : 3 부

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

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

내 트위터 : @cscalfani