NMP-Study / FunctionalProgrammingInJavascript

0 stars 0 forks source link

Chatper 4. 재사용 가능한, 모듈적인 코드로 #3

Open gaepury opened 4 years ago

fairsun commented 4 years ago

4.1 메서드 체인 대 함수 파이프라인

파이프라이닝

하스켈의 함수 표기법

그림4-1 그림4-2

함수 체이닝과 파이프라이닝 차이

4.1.1 메서드를 여럿 체이닝

4.1.2 함수를 파이프라인에 나열

파이프라인

fairsun commented 4 years ago

4.2 함수 호환 요건

4.2.1 형식이 호환되는 함수

// trim :: String -> String
const trim = (str) => str.replace(/^\s*|\s*$/g, '');
// normalize :: String -> String
const normalize = (str) => str.replace(/\-/g, '');

const R = require('ramda');
const cleanInput = R.compose(normalize, trim);
let result = cleanInput(' 444-44-4444 '); //-> '444444444'

4.2.2 함수와 항수: 튜플

// makeAsyncHttp :: String, String, Array -> Boolean function makeAsyncHttp(method, url, data) { // 생략 }

- 튜플 : 형식이 다른 원소를 한데 묶어 다른 함수에 건네주는 일이 가능한 불변성 자료 구조
    -  ex> isValid :: String -> (Boolean, String)
    - 객체 리터럴이 나 배렬 같은 임의 형식으로 반환하는 방법도 있음
    - 튜플 장점
        - 불변성 : 한번 만들어지면 내용이 못 봐꿈
        - 임의 형식의 생성 방지 : 전혀 무관한 값을 서로 연관 지을 수 있음.
        - 이형 배열의 생성 방지 : 형식이 다른 원소가 배열에 섞여 있으면 형식을 검사하는 방어 코드를 정의해야 함. 배열은 태생 자체가 동일한 형식의 객체를 담는 자료구조임.
- 자바스크립는 튜플 자료형을 처음부터 지원하지 않음. 직접 구현 필요 (scala는 지원함)
- 코드 4-2 : 형식화한 튜플 자료형
    - const Status = Tuple(Boolean, String);
~~~js
const Tuple = function( /* types */ ) {
    const typeInfo = Array.prototype.slice.call(arguments, 0);
    const _T = function( /* values */ ) {
        const values = Array.prototype.slice.call(arguments, 0);
        if(values.some(val => val === null || val === undefined)) {
            throw new ReferenceError('Tuples may not have any null values');
        }
        if(values.length !== typeInfo.length) {
            throw new TypeError('Tuple arity does not match its prototype');
        }   
        values.map((val, index) => {            
            this['_' + (index + 1)] = checkType(typeInfo[index],val);           
        }, this);       
        Object.freeze(this);
    };
    _T.prototype.values = function () {             
        return Object.keys(this).map(k => this[k], this);
    };  
    return _T;
};

let [first, last] = name.values(); // In Node you need to use let expect(first).toEqual('Barkley'); expect(last).toEqual('Rosser'); expect(() => { const fullname = new StringPair('J', 'Barkley', 'Rosser'); }).toThrow(TypeError);

fairsun commented 4 years ago

4.3 커리된 함수를 평가

커링

function getName(firstName, secondName) { return firstName + " " + secondName }

const name = curry2(getName);

name('yjh') // return function name('yjh')('jungHye') // return String console.log(name('yjh')('jungHye'));


- 람다JS : 또 다른 함수형 라이브러리
- R.curry : 인수 개수에 상관없이 순수 함수형 언어의 자동 커링 장치를 모방할 수 있음.
- 자동 커링 : 선언된 인수 개수만큼 중첩된 함수 스코프를 인위적으로 생성하는 작업
~~~js
const R = require('ramda');

// checkType :: Type -> Type -> Type | TypeError
const checkType = R.curry(function(typeDef, obj) {
    if(!R.is(typeDef, obj)) {
        let type = typeof obj;
        throw new TypeError(`Type mismatch. Expected [${typeDef}] but found [${type}]`);
    }
    return obj;
});

4.3.1 함수 팩토리를 모방

public class DbStudentStore implements StudentStore { public Student findStudent(String ssn) { // ...생략... return new Student(ssn, name); } }

public class CacheStudentStore implements StudentStore { public Student findStudent(String ssn) { // ...생략... return cache.get(ssn); } }

    - 함수 팩토리로 적합한 구현부를 가져옴
~~~java
StudentStore store = getStudentStore();
store.findStudent("444-44-4444");

// fetchStudentFromArray :: Array -> (String -> Student) const fetchStudentFromArray = R.curry(function (arr, ssn) { return arr[ssn]; });

const fetchStudent = useDb ? fetchStudentFromDb(db) : fetchStudentFromArray(arr);

findStudent('444-44-4444');


### 4.3.2 재사용 가능한 함수 템플릿 구현
- 함수 템플릿 : 생성 시점에 커리된 인수 개수를 기준으로 연관된 함수들을 묶어놓은 것
- 아래 코드 참고만! 실행되지 않음! (아마도 Log4js 버전업 이후 안되는 것 같음)
~~~js
const logger = function (appender, layout, name, level, message) {
    const appenders = {
        'alert': new Log4js.JSAlertAppender(),
        'console': new Log4js.BrowerConsoleAppender()
    };

    const layouts = {
        'basic': new Log4js.BasicLayout(),
        'json': new Log4js.JSONLayout(),
        'xml': new Log4js.XMLLayout()
    };

    const appender = appenders[appender];
    appender.setLayout(layouts[layout]);
    const logger = new Log4js.getLogger(name);
    logger.addAppender(appender);
    logger.log(lever, message, null);
}

const log = R.curry(logger)('alert', 'json', 'FJS')
log('ERROR', '에러가 발새하였습니다!!');

const logError = R.curry(logger)('console', 'basic', 'FJS', "ERROR");
log('코드 402 에러가 발생했습니다. !!');
fairsun commented 4 years ago

4.4 부분 적용과 매개변수 바인딩

부분 적용(partial application)

4.4.1 언어의 핵심을 확장

String.prototype.asName = _.partial(String.prototype.replace, /(\w+)\s(\w+)/, '$2, $1'); result = 'Alonzo Church'.asName(); //-> 'Church, Alonzo'

String.prototype.explode = _.partial(String.prototype.match, /[\w]/gi); result = 'ABC'.explode(); //-> ['A', 'B', 'C']

String.prototype.parseUrl = _.partial(String.prototype.match, /(http[s]?|ftp):\/\/([^:\/\s]+).([^:\/\s]{2,5})/); result = 'http://example.com'.parseUrl(); // -> ['http', 'example', 'com']


### 4.4.2. 지연된 함수에 바인딩
- 소유 객체를 전제로 메서드를 다룰 때에는 함수 바인딩으로 콘덱스트 객체를 세팅하는 일이 중요함.
- 스케줄러만 있으면 함수 본체 안에 감싼 코드를 원하는 시간 이후에 실행시킬 수 있음.
~~~js
const _ = require("lodash");

const Scheduler = (function () {
    const delayedFn = _.bind(setTimeout, undefined, _, _);

    return {
        delay5: _.partial(delayedFn, _, 5000),
        delay10: _.partial(delayedFn, _, 10000),
        delay: _.partial(delayedFn, _, _)
    };
})();

Scheduler.delay5(function () {
    console.log("5초 후에 실행합니다.!! ")
})
fairsun commented 4 years ago

4.5 함수 파이프라인을 합성

4.5.1 HTML 위젯에서 합성하기

const element = R.curry((val, tuple) => new Node(val, tuple));

- 그림 4-9 : 숫자 리스트를 형성하는 머리/꼬리 부분, 함수형 언어에서는 배열을 처리할 때 머리/꼬리를 함수로 사용할 수 있음

<img width="617" alt="그림4-9" src="https://user-images.githubusercontent.com/45279506/72545777-ed10b380-38cc-11ea-9b13-fb85c0eb95ce.png">

### 4.5.2 함수 합성: 서술과 평가를 구분
- 함수 합성 : 복잡한 작업을 한데 묶어 간단한 작업으로 쪼개는 과정
- countWords의 인수를 받아 호출되기를 기다리는 또 다른 함수가 반환됨
- 함수의 서술부와 평가부의 분리
~~~js
const str = `We can only see a short distance ahead but we can see plenty there that needs to be done`;
const explode = (str) => str.split(/\s+/);
const count = (arr) => arr.length;
const countWords = R.compose(count, explode);
countWords(str); //-> 19    

const cleanInput = R.compose(normalize, trim); const isValidSsn = R.compose(checkLengthSsn, cleanInput);

cleanInput(' 444-44-4444 '); //-> '444444444' isValidSsn(' 444-44-4444 '); //-> true

- 그림 4-12 : 복잡한 함수도 단순한 함수들을 합성하여 만듦.
<img width="744" alt="그림4-12" src="https://user-images.githubusercontent.com/45279506/72547939-04ea3680-38d1-11ea-8f84-04924bf6498d.png">

#### 자바스크립트의 Function 프로토타입에 compose를 추가해서 기능을 확장할 수 있음(#4 모나드)
~~~js
Function.prototype.compose = R.compose;

const cleanInput = checkLengthSsn.compose(normalize).compose(trim);

4.5.3 함수형 라이브러리로 합성

const smartestStudent = R.compose( R.head, R.pluck(0), R.reverse, R.sortBy(R.prop(1)), R.zip);

let result = smartestStudent(students, grades); //-> 'Turing'

- 코드 4-10 : 알기 쉽게 함수 별칭 사용
    - 재사용성 측면에선 좋지 않음. 함수형 어휘를 숙지해서 꾸준한 연습과 훈련을 통해 지식을 습득하는 것을 추천함.
~~~js
let students = ['Rosser', 'Turing', 'Kleene', 'Church'];
let grades = [80, 100, 90, 99];

const first = R.head;
const getName = R.pluck(0);
const reverse = R.reverse;
const sortByGrade = R.sortBy(R.prop(1));
const combine = R.zip;
let result = R.compose(first, getName, reverse, sortByGrade, combine);  

4.5.4 순수/불순 함수 다루기

// findObject :: DB -> String -> Object const findObject = R.curry(function (db, id) { const obj = find(db, id); if (obj === null) { throw new Error(Object with ID [${id}] not found); } return obj; });

// findStudent :: String -> Student const findStudent = findObject(db);

const csv = ({ ssn, firstname, lastname }) => ${ssn}, ${firstname}, ${lastname};

// append :: String -> String -> String const append = R.curry(function (elementId, info) { console.log(info); return info; });

// showStudent :: String -> Integer const showStudent = R.compose( append('#student-info'), csv, findStudent, normalize, trim);

let result = showStudent('44444-4444'); //-> 444-44-4444, Alonzo, Church


### 4.5.5 무인수 프로그래밍
- 자신의 구성 함수의 매개변수를 하나도 드러내지 않음.
- compose(또는 pipe) 함수를 사용하면 인수를 선언할 필요가 전혀 없기 때문에 간결하면서도 선언적인 무인수 코드를 작성할 수 있음.
- 암묵적 프로그래밍 
    - 커링 : 마지막 인수를 제외한 나머지 인수들을 유연하게 부분 정의하는 중요한 역할을 담당
- #4 에서 자세히 다룬다고 함.
- 코드 4-12 : 람다 JS 함수로 만든 무인수 스타일의 유닉스 프로그램
~~~js
const runProgram = R.pipe(
    R.map(R.toLower),
    R.uniq,
    R.sortBy(R.identity));

let result = runProgram(['Functional', 'Programming', 'Curry', 'Memoization', 'Partial', 'Curry', 'Programming']);  //-> [curry, functional, memoization, partial, programming] 
});
fairsun commented 4 years ago

4.6 함수 조합기로 제어 흐름을 관리

4.6.3 선택(OR-조합기)

showStudent('44444-4444');

### 4.6.4 순차열(S-조합기)
- seq : 함수 순차열을 순회함.
~~~js
const showStudent = R.compose(
    seq(
            append('#student-info'),
           consoleLog),
    csv,
    findStudent);

showStudent('44444-4444');

4.6.5 포크(조인) 조합기

computeAverageGrade([99, 80, 89]);