2018년 8월 17일 금요일

[javascript] 함수를 이용한 개발-02

출처 : 하스켈로 배우는 함수형 프로그래밍

함수를 이용한 개발

- 함수의 부품화, 그리고 조합(합성)


함수를 좀 더 부품화 할 필요성이 있다고 저자는 생각했었나 보다.
사실 첫번째 소스코드에서 보면 알 듯이 기본적으로 기존의 좌표를 새로운 좌표로 변형해서 리턴하는 것이 목표이다.

config값과 coord(좌표)값을 받으면 new coord 가 리턴되어서 나오는 것이다.
이제 필자는 config와 coord를 분리해야 한다고 한다.

왜냐하면 "무언가(config)와 좌표를 받아 좌표를 반환하는" 함수들을 결합할 경우,
"무언가" 부분을 부여해야 한다는 것이 조합할 때의 표현을 복잡하게 만들기 때문이라고 한다.

즉, 이제 함수를 부품화해서 레고처럼 조합할 껀데, 받아야 하는 것이 2개보다는 1개가 낫다.
뭐 이런 말이려나.

여튼 "좌표(coord)만 받아서 좌표를 반환하는" 함수를 서로 조합한다면 서로 아무리 조합해도
"좌표를 받아서 좌표를 내뱉는 "함수들 이기 때문에 조합이 더 편해질 것이다.

아래 내용은 "무언가(config)와 좌표를 받아 좌표를 반환했던" 함수였던 것에서
"무언가를 받고나서 '좌표를 받아 좌표를 반환하는 함수'를 반환하는 함수"로 변경했다.

일종의 커링을 사용한 것인데 무언가(config)를 좌표(coord)와 분리시키기 위해서
이전에 내가 임의로 붙여봤던 커링을 사용해서 config를 미리 함수 안에 넣어버렸다. 그리고 조합을 할 때는

좌표만을 다루는 함수끼리 조합이 되도록!

// 보다 나은 부품화

function clone(obj) {
  var f = function(){};
  f.prototype = obj;
  return new f;
}


// 함수 합성
function compose (f,g) { return function(x) { return f(g(x)); } };


// 병행 이동의 프리미티브
//var trans = function (dx, dy, coord) {
// var result = clone(coord);
// result.x += dx;
// result.y += dy;
// return result;
//}
var trans = function (dx, dy, coord) {
 return function(coord) {
  var result = clone(coord);
  result.x += dx;
  result.y += dy;
  return result;
 }
}

// 회전 이동의 프리미티브
//var rotate = function (theta, coord) {
// var result = clone(coord);
// result.x = Math.cos(theta) * coord.x - Math.sin(theta) * coord.y;
// result.y = Math.sin(theta) * coord.x + Math.cos(theta) * coord.y;
// return result;
//}
var rotate = function(theta) {
 return function(coord) {
  var result = clone(coord);
  result.x = Math.cos(theta) * coord.x - Math.sin(theta) * coord.y;
  result.y = Math.sin(theta) * coord.x + Math.cos(theta) * coord.y;
  return result;
 }
}

// 설정을 베이스로 한 병행 이동
//var transByConfig = function (config, coord) {
// return trans(config.ofsX, config.ofsY, coord);
//}
var transByConfig = function(config) {
 return trans(config.ofsX, config.ofsY);
}

// 설정을 베이스로 한 회전
//var rotateByConfig = function (config,coord) {
// var preTrans = trans(-config.rotAt.x, -config.rotAt.y, coord);
// var rotated = rotate(config.theta, preTrans);
// var postTrans = trans(config.rotAt.x, config.rotAt.y, rotated);
// return postTrans;
//}
var rotateByConfig = function(config) {
 return compose(trans(config.rotAt.x, config.rotAt.y),
      compose(rotate(config.theta),
        trans(-config.rotAt.x, -config.rotAt.y)));
}

// 설정을 베이스로 한 좌표 변환
//var convertByConfig = function(config, coord) {
// return transByConfig(config, rotateByConfig(config, coord));
//}
var convertByConfig = function(config) {
 return compose(transByConfig(config), rotateByConfig(config));
}


var config = {
 rotAt : { x : 0.5, y : 0.5 }
 , theta : Math.PI / 4
 , ofsX : -0.5
 , ofsY : -0.5
}

var unit_rect = [
{x:0,y:0}, {x:0,y:1}, {x:1,y:1}, {x:1,y:0}
]

// 변환 후의 좌표
//var converted_rect = unit_rect.map(function(coord) { 
// return convertByConfig(config, coord);
//}
// 매개변수가 하나로 변하면서 map에서 바로 만들어진 함수를 지정할 수 있다. 하지만 curry를 이용해서 사용할 수도 있겠다.
var converted_rect = unit_rect.map(convertByConfig(config));

converted_rect.map(function(coord) {
 console.log('(' + coord.x.toFixed(6) + ',' + coord.y.toFixed(6)+')');
}
함수 안에 있는 알고리즘을 보는 것이 아니라. return을 하는 방식이 변경되었고 compose를 이용한 함수합성을 유심히 봐야한다.

댓글 없음:

댓글 쓰기