Open gaepury opened 4 years ago
// 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'
// isValid :: String -> Boolean
function isValid(str) {
// 생략
}
// 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;
};
// isValid :: String -> Staus
const isValid = function(str) {
if(str.length == 0) {
return new Status(false, "잘못된 입력입니다. 빈 값일 리 없지요!");
} else {
return new Status(true, "성공");
}
}
const Tuple = require('../helper').Tuple;
const StringPair = Tuple(String, String);
const name = new StringPair('Barkley', 'Rosser');
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);
f(a,b,c)
비커링된 함수
커링된 함수
function curry2(fn) {
return function (firstArg) {
return function (secondArg) {
return fn(firstArg, secondArg)
}
}
}
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;
});
public interface StudentStore {
Student findStudent(String ssn);
}
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");
// fetchStudentFromDb :: DB -> (String -> Student)
const fetchStudentFromDb = R.curry(function (db, ssn) {
return find(db, ssn);
});
// 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 에러가 발생했습니다. !!');
/**
func
with partials
prepended to the_.bind
except it does notthis
binding._.partial.placeholder
value, which defaults to _
in monolithic
const consoleLog = _.partial(logger, 'console', 'json', 'FJS 부분 적용');
consoleLog("INFO") // not Function, 마지막 인수에 undefined를 넣고 실행됨
/**
* Creates a function that invokes `func` with the `this` binding of `thisArg`
* and `partials` prepended to the arguments it receives.
*
* The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
* may be used as a placeholder for partially applied arguments.
*
* **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
* property of bound functions.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to bind.
* @param {*} thisArg The `this` binding of `func`.
* @param {...*} [partials] The arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
* function greet(greeting, punctuation) {
* return greeting + ' ' + this.user + punctuation;
* }
*
* var object = { 'user': 'fred' };
*
* var bound = _.bind(greet, object, 'hi');
* bound('!');
* // => 'hi fred!'
*
* // Bound with placeholders.
* var bound = _.bind(greet, object, _, '!');
* bound('hi');
* // => 'hi fred!'
*/
var bind = baseRest(function(func, thisArg, partials) {
var bitmask = BIND_FLAG;
if (partials.length) {
var holders = replaceHolders(partials, getHolder(bind));
bitmask |= PARTIAL_FLAG;
}
return createWrap(func, bitmask, thisArg, partials, holders);
});
const log = _.bind(logger, undefined, ‘console’, 'json', 'FJS 부분 적용');
log("INFO", "함수형 프로그래밍, 정말 멋지네요!!")
String.prototype.first = _.partial(String.prototype.substring, 0, _);
let result = 'Functional Programming'.first(3); // -> 'Fun'
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초 후에 실행합니다.!! ")
})
그림 4-7 : 단순한 입력 텍스트 위젯 3개와 컨테이너 위젯 1개를 합하여 인적 사항 폼 컴포넌트를 만듦
그림 4-8 : 주소 폼, 인적 사항 폼, 버튼, 컨테이너 등 작은 위젯들을 합하여 학생 콘솔 위젯을 만듦
Node라는 재귀적 튜플을 정의
const Node = Tuple(Object, Tuple);
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
그림 4-10 : 함수 f, g의 입출력 형식.
그림 4-11 : 첫번째 함수의 입력을 직접 두번째 함수의 출력으로 매핑하는 새로운 합성 함수.
수학적 표기
람다JS가 R.compose함수를 지원함.
const trim = (str) => str.replace(/^\s*|\s*$/g, '');
const normalize = (str) => str.replace(/\-/g, '');
const validLength = (param, str) => str.length === param;
const checkLengthSsn = _.partial(validLength, 9);
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);
let students = ['Rosser', 'Turing', 'Kleene', 'Church'];
let grades = [80, 100, 90, 99];
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);
const find = R.curry((db, id) => db.find(id));
// 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]
});
const debugLog = _.partial(logger, 'console', 'basic', 'MyLogger', 'DEBUG');
const debug = R.tap(debugLog);
const cleanInput = R.compose(normalize, debug, trim);
const isValidSsn = R.compose(debug, checkLengthSsn, debug, cleanInput);
const showStudent = R.compose(
append('#student-info'),
csv,
alt(findStudent, createNewStudent));
showStudent('44444-4444');
### 4.6.4 순차열(S-조합기)
- seq : 함수 순차열을 순회함.
~~~js
const showStudent = R.compose(
seq(
append('#student-info'),
consoleLog),
csv,
findStudent);
showStudent('44444-4444');
const computeAverageGrade = R.compose(getLetterGrade, fork(R.divide, R.sum, R.length));
computeAverageGrade([99, 80, 89]);
4.1 메서드 체인 대 함수 파이프라인
파이프라이닝
하스켈의 함수 표기법
함수 체이닝과 파이프라이닝 차이
4.1.1 메서드를 여럿 체이닝
4.1.2 함수를 파이프라인에 나열
파이프라인