2019년 3월 20일 수요일

[리뷰][Coursera] 코세라의 [Concurrent Programming in Java] 수강을 마치며

Coursera에서 수강할 수 있는 Concurrent Programming의 수강을 마쳤다.

이번에는 lock와 readwritelock에 대해 배웠으며, Actor의 개념도 살짝 나왔다.

실제로 Actor를 제대로 구현해서 사용하는 것은 아니지만 개념정도 알고 어떻게 사용되는
지만 알아도 Actor가 좀 더 친근해진 느낌이다.


조금 어려운 내용이 나오기도 했지만 프로젝트 자체는 어렵지 않고, 친절하게 프로젝트 자체를 설명해주기 때문에, 잘 넘길 수 있었다. 내 생각에 이 코스에서 중요한 부분은 프로젝트 어사인먼트가 아니라 QUIZ인 것 같다.

개념을 잘 이해해야 하는 경우가 있으며, 내가 어떤 부분을 헷갈려 하는지 알 수 있기 때문에 같은 부분을 계속 듣고 확인하게 만들어 준다.

괜찮은 강의였으나, 좀 더 설명이 길었으면 하는 아쉬움이 있다.

2019년 3월 5일 화요일

[javascript] 그래서 커리를 어떻게 쓰려고?

내가 처음 커리를 써야겠다고 생각했던 것은 로직이 계속 겹치고 있었기 때문이다.

예를들어보자. 인풋별로 유효성을 체크하는데 유효성을 체크할 때마다 하는일이 조금씩 다르다.

// 핸드폰 인풋 묶음 3개 유효성검사
var phone_1 = trim($("#phone_1").val());
var phone_2 = trim($("#phone_2").val());
var phone_3 = trim($("#phone_3").val());

if (phone_1 < 3 && phone_1 ...) { // 여러 and문과 유효성검사
  $("#phone_1").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
... phone_2, phone_3

$("#hidden_phone_").val(phone_1 + "-" phone_2 + "-" + phone_3);
이런 코드가 있다. 익숙하다. 일단 커리를 쓰기 전에 여기서 if문에 있는 모든 유효성검사는 하나의 함수로 추상화 해놓자.
if (validate(phone_1) { // 여러 and문과 유효성검사
  $("#phone_1").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
if (validate(phone_2) {
  $("#phone_2").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
if (validate(phone_3) {
  $("#phone_3").focus();
  alert("휴대폰 유효성검사실패");
  return;
}
이런 코드를 본 적 없는가? 여기서 분명 우리는 뭔가 더 할 수 있을 것 같다. 어떻게 해야 할까? 맞다 객체를 넘기는 것이다.
function validateAndFocus(phone, $phone) {
  if (!validatePhone(phone) {
    alert("휴대폰 유효성검사 실패");  
    $phone.focus();
    return false;
  }
  return true;
}
이제 이걸 사용해보자
if(!validateAndFocus(phone_1, $("#phone_1")) {
  return;
}
if(!validateAndFocus(phone_2, $("#phone_2")) {
  return;
}
if(!validateAndFocus(phone_3, $("#phone_3")) {
  return;
}
...
뭔가 여기도 겹치는 것 같다.
if (!(validateAndFocus(phone_1, $("#phone_1")) &&
      validateAndFocus(phone_2, $("#phone_2")) &&
      validateAndFocus(phone_3, $("#phone_3"))) {
  return;
}
undersocre.js의 and가 있다면 더 좋을 것 같다. 끝이 없을 것 같으니 여기서 넘어가자. underscore를 쓸 수 없어서 curry만 임시 사용하는 것이다. 이정도만 깔끔해보인다. 그런데 여기 주민번호와 카드번드까지 추가되었다.
var card1 = $("#card1");
var card2 = $("#card2");
var card3 = $("#card3");
var card4 = $("#card4");

var national_id_1 = $("#national_id");
var national_id_2 = $("#national_id");

우리는 validateAndFocus를 사용할 수 있는가? 사용할 수 없다. 1. 휴대폰번호의 유효성과 아이디의 유효성은 다르다. (predicate의 분리 필요) 2. 유효성 검사 이후 하는 일이 각각 다를 것이다. 하지만 형태는 동일하다. 1. 주어진 값의 유효성을 검사한다. 2. 통과하면 true 실패하면 false를 리턴한다. 3. 유효성검사에 실패하여 false를 리턴하기 전에 각각 해야하는 일이 있다.
// 동일한 형태의 일을 하는 함수 템플릿 정의
function validateTemplate(pred, func, obj1, obj2, obj3) {
  if (pred(obj1, obj2, obj3)) {
    func(obj1, obj2, obj3);
    return false;
  }
  return true;
}
자 이 형태를 만들었다. 이제 어떻게 사용할지 보자. 유효성 검사를 분리해서 넣을 것이다. 그렇다면 이렇게 될 것이다.
var curried = curry(validateTemplate);
var validatePhoneCurried = curried(function(n) {
  return validatePhone(n);
}
var validateCardCurried = curreid(function(n) {
  return validatePhone(n);
}
var validateNationalidCurreid = curried(function(n) {
  return validateNationalid(n);
}
뭐 대충 이렇게 코딩했다. 현재까지는 pred를 커리하고 이제는 각 pred마다 할일을 다르게 넣을 수 있겠다. 첫번째 인자를 _로 한 이유는 focus나 val("") 등 DOM조작을 위한 객체를 따로 넘긴다고 하자.
var validatePhoneFinal = validatePhoneCurried(function(_, $dom) {
  alert("헨드폰문제!");
  $dom.focus();
});

var validateCardFinal = validateCardCurried(function(_, $dom) {
  alert("카드문제");
  $dom.focus();
});

var validateNationalidFinal = validateNationalidCurreid (function(_, $dom) {
  alert("주민번호문제");
  $dom.focus();
}
이렇게 만들었다 치자. 이제 위에 코드를 저것들로 바꾸면 된다.
if (!(validatePhoneFinal(phone_1, $("#phone_1")) &&
      validatePhoneFinal(phone_2, $("#phone_2")) &&
      validatePhoneFinal(phone_3, $("#phone_3"))) {
  return;
}

....
if (!(validateCardFinal(card_1, $("#card_1")) &&
      validateCardFinal(card_2, $("#card_2")) &&
      validateCardFinal(card_3, $("#card_3")) &&
      validateCardFinal(card_4, $("#card_4"))) {
  return;
}
...
if (!(validateNationalidFinal(national_id_1 , $("#national_id_1")) &&
      validateNationalidFinal(national_id_2 , $("#national_id_2"))) {
  return;
}
여기서 map이나 every 같은 것을 이용하는 것이 큰 도움이 될 것 같지만 그러진 않겠다. 오늘의 주제는 커리니까.
curry라는 함수 한번 가지고 놀아봤다.

[javascript]자바스크립트 curry 구현소스를 파악해보자.

출처 :
https://edykim.com/ko/post/writing-a-curling-currying-function-in-javascript/
https://medium.com/@kevincennis/currying-in-javascript-c66080543528

커링이라는 개념은 하스켈을 공부할 당시 알게 되었다.
clojure의 partial과 비슷한 개념이다. (동일한가?)

여튼 자바스크립트로 curry를 쓰고 싶은 욕구가 강했지만, underscore.js같은 라이브러리를 사용할 수 없는 제약이 있어,
다른 누군가가 어떻게 curry만을 구현했는지 확인하고 복붙을 하기로 하였다.

그 중에 위의 링크를 확인했고 하나하나 파고들어갔다. 아래 내용은 위 블로그를 읽고 나만의 부연설명을 추가한 것이다.


function curry(fn) {
 var arity = fn.length; // 함수의 필요 인자
 // 매번 curry된 함수를 호출할 때마다 새로운 인자를 배열에 넣어 클로저 내에 저장한다.
 // 배열의 길이는 fn.length와 동일해야 한다. (실행될 때)
 // 혹여 인자의 수가 동일하지 않으면 새로운 함수로 반환한다.
 
 // 인자 목록을 가지는 클로저가 필요하다 (함수로 둘러쌓아야 한다 
 // 또 여기서 개별의 클로저가 생성되야 하니까 즉시 실행함수로 만든다.)
 // 전체인자(배열)과 fn.length를 확인
 // 인자의 수가 부족하면 부분적으로 적용된 함수를 반환 
 // 인자의 수가 충족하면 fn에 모든 인자를 적용,호출하여 리턴
 return (function resolver() {
  // 지금까지 받은 인자를 복사한다.  // 이전 클로저가 오염되지 말게
  var memory = Array.prototype.slice.call(arguments);
  // resolver는 익명함수를 반환한다. 
  // resolver는 인자가 부족할 때 반환한다.
  // resolver가 새로 반환되는 이유는 클로저를 위한 것같다.
  
  // resolver는 바로 실행되고 익명함수를 하나 리턴한다.
  // resolver가 이전까지 모은 인자를 가지고 있다. (memory)
  // 이걸 변수에 담았다가 나중에 실행시킬 것이다.
  // 실행시키면 arguments에서 인자를 memory와 합체한다.
  // 그리고 원래 실행되어야할 함수(fn)이 필요로 하는 인자의 갯수와 비교
  // 지금까지 모은 인자(local)과 arity의 길이가 맞다면
  // 원래함수를 호출(fn), 그렇지 않으면 resolver를 다시 반환 (인자를 더 받는다)
  return function() {  
   var local = memory.slice();
   Array.prototype.push.apply(local, arguments);
   next = local.length >= arity ? fn : resolver;
   return next.apply(null, local);
  };
 }());
}

한번 생성한 커리에 함수를 넣어보자. 지금 함수를 넣으면 인자 fn에 들어가는 것이다.
====
function volume(l, w, h) {
  return l * w * h;
}

var curried = curry(volume);

이제 아래 코드를 보면서 어떻게 되는지 보자.
function curry(fn) {
 var arity = fn.length; // 1. 숫자 3이 저장된다.
 return (function resolver() {
  var memory = Array.prototype.slice.call(arguments); // 2. resolver를 인자없이 실행하였다.(현재는 커리만 되는 상태)
  
  return function() {
   var local = memory.slice();
   Array.prototype.push.apply(local, arguments);
   next = local.length >= arity ? fn : resolver;  // 3. arity가 부족하므로 함수를 리턴.
   return next.apply(null, local);
  };
 }());
}

함수를 리턴받아서 curried에 넣었다. 여기에 다른 인자들을 넣어보자.
일단 2를 넣을 것인데 l, w, h 총 3개가 필요한 함수에게는 부족한 인자 갯수이다.
var length = curried(2);

어떤 일이 일어나는지 아래를 보자.
function curry(fn) {
 var arity = fn.length; 
 return (function resolver() {
  var memory = Array.prototype.slice.call(arguments);
  
  return function() {  // 1. resolver로 반환된 익명함수가 실행됨. 
   var local = memory.slice();  // 2. memory는 현재 0개의 인자를 가지고 있다. 
   Array.prototype.push.apply(local, arguments);  // 3.이번에 추가된 2가 local에 push된다.
   next = local.length >= arity ? fn : resolver;  // 4.아직 인자가 1개이기 때문에 다시 resolver를 반환한다.
   return next.apply(null, local);  // 4. 알다시피 이번에 던져지는 resolver는 또한 새로운 클로저와 함께하는 새로운 함수다.
  };
 }());
}

한번 더
var lengthAndWidth = length( 3 );

function curry(fn) {
 var arity = fn.length; 
 return (function resolver() {
  var memory = Array.prototype.slice.call(arguments);
  
  return function() {  
   var local = memory.slice();  // 1. 새로운 클로저에 들어있는 memory는 [2] 이다. local에 복사한다. (이전 클로저에 해를 끼치지 않게)
   Array.prototype.push.apply(local, arguments);  // 2. 새로운 인자 3을 추가한다. [2,3]
   next = local.length >= arity ? fn : resolver;  // 3.아직 인자가 2개이기 때문에 다시 resolver를 반환한다.
   return next.apply(null, local);  // 4. 알다시피 이번에 던져지는 resolver는 또한 새로운 클로저와 함께하는 새로운 함수다.
  };
 }());
}

이제 마지막으로 하나의 인자만 더 넣으면 실행될 것이다.
console.log( lengthAndWidth( 4 ) ); // 24

왜 그렇게 되는지 아래를 보자.
function curry(fn) {
 var arity = fn.length; 
 return (function resolver() {
  var memory = Array.prototype.slice.call(arguments);
  
  return function() {  
   var local = memory.slice();  // 1. 새로운 클로저에 들어있는 memory는 [2,3] 이다. local에 복사한다. (이전 클로저에 해를 끼치지 않게)
   Array.prototype.push.apply(local, arguments);  // 2. 새로운 인자 4을 추가한다. [2,3,4]
   next = local.length >= arity ? fn : resolver;  // 3.아직 인자가 3개이기 때문에 fn을 넣는다. (fn은 지금까지 변경된 적도 사용된 적도 있다. 하지만 이날을 위해 기다렸다)
   return next.apply(null, local);  // 4. 이번에는 함수를 던지지 않고 fn의 리턴값이 실행될 것이다. (하지만 fn이 리턴하는게 함수라면... 음...)
  };
 }());
}

2019년 2월 15일 금요일

[clojure] 4clojure easy버전 0에서 50까지 문제 풀고 중간점검

clojure로 뭘 할지 감이 잡히지 않아서, 그냥 4clojure나 가끔씩 풀어보기로 했다.
elementary, easy부분만 풀었는데 괜찮았다. 글자그대로 쉬웠지만 어떤 것들은 꽤나 생각을 해야 했다.
4clojure를 풀다보면 내 뇌의 생각하는 방식이 바뀌어야 한다는 느낌이 든다. 운동을 할 때도 가동범위가 중요한데, 사용하지 않던 뇌부분을 사용한 느낌이 들면서, 나의 뇌 가동범위가 부족한 것이 아닌가 싶었다.

clojure로 언젠가 개발을 하지 않더라도, 이 문제를 풀면서 뭔가 개발을 할 때 생각하는 방식이 늘어나는 느낌을 받을 것 같다. (좀더 난이도가 올라가면 말이다)

결과적으로 4clojure를 풀기로 결심한 것은 만족이다.
https://github.com/ssisksl77/clj-web-demo/blob/master/src/web_demo/4clojure/elementary.clj

51~100도 금방 가자.

2019년 2월 14일 목요일

[리뷰][Coursera] 코세라의 [Parallel Programming in Java] 수강을 마치며

코세라에서 수강할 수 있는 병렬프로그램의 첫번째 코스를 마쳤다.
생각보다 어렵지 않았다.

1. 병렬프로그램 개발을 하기 이전에 나의 코드를 분석할 수 있는 기본적인 방법을 알려준다. WORK, SPAN 등등
2. Fork/Join Framework에 대해서 설명한다.
3. Future에 대해서 배운다.
4. Barrier를 배운다.
5. Phaser를 배운다.
6. Phaser를 이용한 다양한 예시를 배운다.

사실 이번 강의에서 인상깊었던 것은 4주차에서 배운 Phaser라는 기능이다. 이 기능은 정말 멋지다. 동기화에 대한 많은 생각을 해주게 하는 기능이다. 아니 Latch와 Barrier 이상으로 뭔가 더 동기화 해야할 일이 있단 말인가?

여기에선 있다고 말한다. 아쉬웠던 점은 project assignment가 너무 쉽다. 그냥 수강한 걸보고 배운데로 하면 된다. 좀 더 어려웠으면 하기도 하지만, 그러면 다 fail을 할 수도 있겠다고 생각한 걸까? 예시로 동기화를 하는 기능은 정말 많은 이해가 되었지만, 비동기 통신을 하는 것 자체는 전부 주어진 메소드로 실행해야 하기 때문에 처음부터 만들지는 않는다.

코스가 끝나면 아래처럼 링크드인에 Certificate을 넣을 수 있다.
그리고 주어진 값을 넣으면 링크드인의 자격증명에 보이기 시작한다.

4주 짜리긴 한데 2주만에 끝났다. 이외로 내용이 길지 않았다.
꽤나 후련하다.
3월 쯔음에나 두번째 코스로 들어가지 않을까 싶다.

꽤나 기대된다. 두번째 코스도 내용이 긴 것 같지는 않다.
하지만 액터모델이라는 것이 꽤나 기대된다.

이러다가 스칼라를 공부해야 하는 지도 모르겠다.
사실 스칼라에 손을 대지 않은 이유는 온전히 나의 고집이었다.
그저 Clojure가 더 낫지 않을까 하는 나의 근거없는 확신 때문에 가끔씩 4Clojure나 풀면서 "나는 괜찮은 개발자야" 라고 자위했다.

하지만 둘 다 한다고 더 많은 시간을 들일 것 같지도 않고, 함수형 프로그래밍에 Clojure와 하스켈 맛보기만으로는 아직 잘 모르겠다. (사실 하스켈은 이제... 모나드나 어플리커티브 펑터를 배운 이후로 별로 하고 싶지 않다. 그래도 모나드를 배운 것은 정말 유용했다.)

일단 바로 할지는 모르겠고, Coursera에서 스칼라를 이용한 함수형 프로그래밍 강의가 있는데 함수형 프로그래밍에 대해 더 알기 위해서라도 익혀놔야겠다.

2019년 2월 7일 목요일

코세라에서 병렬프로그래밍을 배우기 시작했다 (1주차)

코세라에서 제공하는 Parallel Programming in Java 라는 코스를 듣게 되었다.
1주차 내용을 Fork/Join 프레임워크에 대한 설명이었다. 사실 프레임워크를 설명하는 것보다는 병렬프로그래밍을 할 때 사용하는 기본적인 패턴을 가르쳐주었으며,
우리가 나중에 만들 병렬프로그램 설계를 분석하는 방법을 알려준다.(암달의 법칙을 빠지지 않는다.)

1주차를 들으면서 든 생각은 [생각보다 괜찮다]였다. 내용이 잘 짜여져 있고, 가르쳐주는 강사님(교수님?) 또한 아주 잘 가르쳐주신다. (한글 자막은 없다, 코세라는 재능기부를 기다리고 있다.)

또한 괜찮은 것은 바로 assignment다 직접 코딩을 하고 점수를 받는 것이다. 필자는 이것을 푸는데 꽤나 오랜시간이 걸렸다. 1시간 정도 걸린 것 같다. 풀고 보니 별 것 아니었다. 병렬프로그래밍이나 Fork/Join 프레임워크에 익숙하다면 풀기 쉬울 것이다. 그렇지 않다면 약간 버벅이면서 풀 수 있는 정도였다.


2주차가 기대된다.


2019년 1월 17일 목요일

[on lisp] 5.6 Recursion on Subtrees

5.6 Recursion on Subtrees
리스프 프로그램에서 흔히 볼 수 있는 또 다른 재귀 패턴이 있다. 서브트리의 재귀
이 패턴은 중첩 리스트로 시작하여 car와 cdr을 모두 재귀적으로 사용하려는 경우에 나타남.

lisp 리스트는 다재다능한 구조이다. 리스트는 시퀀스, 세트, 매핑, 배열 및 트리를 나타낼 수 있다.
리스트를 트리로 해석하는 몇 가지 방법이 있다. 가장 보편적인 것은 왼쪽 가지가 car이고 오른쪽 가지가 cdr인 이진 트리로 간주하는 것이다.
;; Figure 5.7: Lists as trees.
(a . b) 
(a b c) 
(a b (c d))
만약 리스트가 아래와 같은 형태로 고려되면 쉽게 해석할 수 있다.
(a b c) = (a . (b . (c . nil))) 
(a b (c d)) = (a . (b . ((c . (d . nil)) . nil)))
어떠한 형태의 리스트도 2진트리로 해석될 수 있다. copy-list와 copy-tree와 같은 공통 리스프 함수의 쌍 간의 구별을 돕는다.
전자는 리스트를 시퀀스로 간주하고 복사한다. 만약 리스트에 서브리스트(sublists)를 보유한다면, 그것이 해당 리스트의 요소일 뿐이라도 복사되지 않는다.
(setq x '(a b) listx (list x 1))
((A B) 1)
(eq x (car (copy-list listx)))
T
;; copy-list구현을 다시 보자.
;; copy-list
(lrec #'(lambda (x f) (cons x (funcall f))))
;; Figure 5.5: Function to define flat list recursers
;; 이전에 본 소스임.
;; 특이하게 함수를 리턴함
(defun lrec (rec &optional base) 
  (labels ((self (lst) 
    (if (null lst) 
     (if (functionp base) 
      (funcall base) 
   base) 
  (funcall rec (car lst) 
               #'(lambda () (self (cdr lst))))))) 
 #'self))

;; 반대로 copy-tree는 리스트를 트리로 간주한다.
;; 서브리스트들 전부 복사된다.
(eq x (car (copy-tree listx)))
NIL
copy-tree구현체를 봐보자.
(defun our-copy-tree (tree)
  (if (atom tree)
      tree
      (cons (our-copy-tree (car tree))
            (if (cdr tree) (our-copy-tree (cdr tree))))))