2020년 1월 30일 목요일

[리뷰] clojure polymorphism

이 책은 leanpub이라는 사이트에서 구매한 책이다.

저자는 코드의 재사용에서 가장 중요한 요소를 다형성이라 믿는 듯 하다.
하여 다형성에 대한 자신의 생각과 그 노하우를 clojure를 이용해서 아주 짧게 설명해주고 있다.


노트정리한 내용

2020년 1월 14일 화요일

[리뷰] 셰익스피어의 리어왕

셰익스피어의 4대 비극 중 가장 비극적인 이야기라고 생각한다.
한번의 믿음, 한번의 실수로 모든 것을 잃은 리어왕의 이야기는 남을 불신하는 것에 대한 이야기가 아닌
자신이 누구인지 다시 새겨보게 하는 이야기라고 할 수 있다.

나의 생각

리어왕은 세명의 딸에게 각각 땅을 나눠주기로 한다. 갈등의 시작은 이미 여기서 시작된다. 왜 땅을 나눠주려하는가. 왜 땅을 삼등분하였는가.
리어왕은 이미 처음부터 세 딸에 대해 제대로 보려고 하지 않는다. 이미 결정은 만들어졌고, 딸들은 리어왕을 친송하기만 하면 되는 것이다.
리어왕 자신은 그럴 가치가 있는 사람이라고 생각했나보다. 이제 나눠줘도 자신은 리어왕 그대로 이기 때문에 상관이 없다고 생각했나보다.
당연히 세 딸들이 자신을 사랑하고, 자신이 재산을 나눠주면 그 보답으로 무언가를 해줄 것이라 생각했나보다.
리어왕은 그 증거로 자신을 얼마나 사랑하는지 말하라 한다. 첫째 딸, 둘째 딸은 리어왕에 대한 사랑을 온갖 수식어를 붙여서 표현한다. 그는 흡족한다.
하지만 막내딸 코딜리어는 거짓을 말하지 않고 자신의 생각을 그대로 말한다.

리어왕은 분노하며 코딜리어에게 주어질 모든 땅을 빼앗고, 시집을 보낸다.

재산을 받은 두 딸은 재산을 받은 시점부터 리어왕을 무시하기 시작한다. 리어왕은 자신이 무슨 일을 저지른 것인지 뒤늦게 깨닫게 되는데...

이 이후의 내용은 굳이 설명하지 않도록 하겠다. 이 책이 비극이라는 점으로 추론하거나, 리어왕은 짧은 글이니 읽어보길 추천한다.

기억에 남는 문장을 말하라 한다면 글로스터가 두 눈을 잃고 하는 말이다.
갈 곳이 없으니 눈도 필요없다. 
눈이 있을 땐 넘어졌지.
이 말이 계속 머리 속에 맴도는 것이 신기하다. 특이 "눈이 있을 땐 넘어졌지" 라는 말이 신기하다.

눈이 없어서 부축을 받는 존재가 된 글로스터는 눈이 있을 때 넘어졌다는 말을
마치 거짓된 글들을 읽고 그것에 혹해 잘못을 저지른 자신을 되돌아 보는 말일 수도 있고

방향성이 있던 그의 삶에서는 넘어지기도 하고 나아갔겠지만
전의를 상실하였기에 나아가지도 않기에 넘이지지도 않는 다는 말일 수도 있을 것 같다.

셰익스피어의 리어왕에서 나오는 유명한 명언은 아래와 같다
있다고 다 보여주지 말고
안다고 다 말하지 말고
가졋다고 다 빌려주지 말고
들었다고 다 믿지 말고
이 내용만을 보면 아첨에 조심하라는 말이다.

내가 리어왕을 보면서 느낀 점은 이것과는 조금 다르다.
자신을 보고 싶은 대로 보고, 누군가를 현혹하려는 것은 인간의 본능임을 셰익스피어는 보여주려는 것 같다.
사랑하고자 하는 것을 바라보고, 믿고 싶은 것을 믿는 것 또한 인간이 하는 실수라고 생각한다.

사실 코딜리어가 아버지를 사랑하는 말을 해주었다면, 이런 비극이 과연 일어났을 것인가.
혹은 딸들에게 재산을 물려줄 때, 끝까지 막아섰던 신하를 버리지 않고 그를 믿었다면 어떻게 되었을가.

라는 생각을 해본다. 하지만 이 한 편의 연극이 단순한 격언을 말하기 위한 극이라고 하기에는 아깝다고 생각한다.

나는 리어왕의 이야기가 우리의 삶으로 느껴진다.

누군가를 어리석게 믿는 것은 피할 수 없으며 우리가 사랑하는 행위이기 때문이다.

2019년 12월 5일 목요일

[java] CountDownLatch와 CyclicBarrier의 차이 (Phaser로 들어가기 전)

출처 : https://www.baeldung.com/java-cyclicbarrier-countdownlatch

개요

사실 오래 전에 배워서 사용했던 Phaser라는 기능이 가물가물해서 기억을 하고자 CountDownLatch와 CyclicBarrier를 비교하면서 복습을 해보았다.
하지만 대부분 Phaser보다는 CountDownLath,CyclicBarrier를 많이 사용하지 않을까 싶다.


CountDownLatch

일단 Latch(빗장이었나?)로 걸어잠그고 숫자를 다 세면 열어주는 것이다. 그래서 만약에 CountDownLatch에 카운트를 5로 지정하면 0이 될때까지 현재 스레드가 기다린다.

CyclicBarrier

CyclicBarrier는 스레드 그룹이 모두 wait하고 있는 지점에 도달 할 때까지 기다린다.
이 시점에서 Barrier가 깨지면서 일을 할 수 있게 된다. 뭐 일을 안해도 되고 해도 된다.

차이점

Task vs Treads

보면 알겠지만 CountDownLatch는 숫자를 세는데 중점을 두고, CyclicBarrier는 모두 다 오길 기다린다.
CountDownLatch는 동일한 녀석이 숫자를 두번 세도 상관이 없다.
CyclicBarrier는 한명당 하나의 카운트라고 생각하면 된다. 무조건 다와야 한다.

CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t = new Thread(() -> {
  countDownLatch.countDown();
  countDownLatch.countDown();
});

t.start();
countDownLatch.await();

System.out.println("code1");
System.out.println(0 == countDownLatch.getCount());
System.out.println("===");

CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread t = new Thread(() -> {
  try {
    cyclicBarrier.await();
    cyclicBarrier.await();
  catch (InterruptedException | BrokenBarrierException e) {
    e.printStackTrace();
  }
});

t.start();

System.out.println("code2");
System.out.println(1 ==  cyclicBarrier.getNumberWaiting());
System.out.println(cyclicBarrier.isBroken());
System.out.println("===");


재사용성

CyclicBarrier는 wait()에 도달해서 다음으로 넘어가면 count값이 원래 값으로 바뀐다. 무한이 돈다
CountDownLatch에서는 한번 도달하면 끝이다.

List outputScraper = Collections.synchronizedList(new ArrayList<>());
CountDownLatch countDownLatch = new CountDownLatch(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
  es.execute(() -> {
    long prevValue = countDownLatch.getCount();
    countDownLatch.countDown();
    if (countDownLatch.getCount() != prevValue) {
      outputScraper.add("Count Updated");
    }
  });
}

es.shutdown();
System.out.println(outputScraper);
System.out.println(outputScraper.size() <= 7);
// https://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html
// https://stackoverflow.com/questions/6916385/is-there-a-concurrent-list-in-javas-jdk 참고
// Collections.synchronizedList는 내가 임의로 넣은 것이다.
List outputScraper = Collections.synchronizedList(new ArrayList<>());
CyclicBarrier cyclicBarrier = new CyclicBarrier(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
  es.execute(() -> {
    int numberWaiting = cyclicBarrier.getNumberWaiting();
    if (numberWaiting >= 0) {
      outputScraper.add("Count Updated"); 
    }
    try {
      cyclicBarrier.await();
    } catch (InterruptedException | BrokenBarrierException e) {
      e.printStackTrace();
    }
  });   
}

es.shutdown();
System.out.println(outputScraper);
System.out.println(outputScraper.size() > 7);
cyclicBarrier.await();
다음에는 Phaser에 대해 알아보자.

2019년 11월 22일 금요일

[javascript] jQuery는 어떻게 new가 없이 생성되는 것일까?

언젠가 개발을 처음 시작할 때 나프다의 [정개발]이라는 분과 슬랙 대화를 한 적이 있다. 당시 나는 자바만을 배우고 있었음에도, 그분은 언젠가 jQuery소스를 공부해야 한다고 했었다.
그때는 자바 개발하기에도 바쁜데 자바스크립트에 제이쿼리를 사용하는 것도 모자라 소스를 공부?
라고 생각했었다.

지금은 자바스크립트를 배우는 것이 자바에도 도움되고 더 나아가 내가 어떤 언어를 짜던 잘나가는 오픈소스를 읽는 것이 중요하다고 생각한다. (하지만 제대로 소스를 판 적은...)
오늘은 제이쿼리에게서 한 수 배워보고자 한다.

오늘 주제는 jQuery는 어떻게 new를 없앴는가? 이다.
대부분 new를 없앤다는 것은 생성자를 사용하지 않는 다는 말과 같다. 매개변수가 필요하면 그 매개변수를 클로저로 덮어서 함수로 던지는 것이다. 그러면 그 함수의 스코프는 자기 함수 밖에 있는 변수(매개변수 포함)를 덮은(closure) 채로 세상 밖에 내던져진다.

하지만 jQuery는 그런 것 같지 않다. 어떻게 그렇지 않은지 아주 쉬운 방법으로 알 수 있다.


이처럼 __proto__안에 수 많은 함수들이 있는 것을 알 수 있다. 어쩌면 당연한 것이다. 수 많은 기능들을 사용할 때마다 각 Execution Context에 포함해서 던진다는 말은 사용할 때마다 동일한 기능들이 메모리에 올라간다는 말이니까. 한 두개면 괜찮겠지만 저게 몇 개인가!

new를 사용하는 것이 분명하다.

그렇다면 jQuery소스를 한번 구경해보자.

jQuery($)의 정체

제이쿼리 사이트에서 일반 개발용소스를 참고했다.(https://code.jquery.com/jquery-3.4.1.js)

일단 jQuery를 실행하면 무엇이 나오는가? 그것이 시작일 것이다. 일단 jQuery는 함수일 것이다. 그러니까 new가 없이 실행되는 것이다. jQuery라는 이름의 함수를 찾아보자.

var version = "3.4.1",
  // Define a local copy of jQuery
  jQuery = function( selector, context ) {
  // The jQuery object is actually just the init constructor 'enhanced'
  // Need init if jQuery is called (just allow error to be thrown if not included)
      return new jQuery.fn.init( selector, context );
  },
...


그렇다. jQuery를 실행하면 내부적으로 jQuery.fn.init이라는 생성자를 실행한다!

jQuery.fn.init의 정체

이제부터는 jQuery의 소스를 이용하면, 말이 많아지기 때문에, 제이쿼리의 간단한 구조를 간단히 설명하고자 한다. 조그만한 자바스크립트 라이브러리를 만들었다고 해보자.

/*
제이쿼리 구조를 설명하기 위해 만든 간단한 함수
*/
(function (global) {
  var mini = function(name) {
    return new mini.fn.init(name);
  }

  mini.fn = mini.prototype = {};

  mini.fn.sayHi = function() {
    console.log("hi " + this.name);
    return this;
  }

  mini.fn.sayBye = function() {
      console.log("bye " + this.name);
      return this;
  }

  mini.fn.init = function(name) {
    this.name = name;
  }

  mini.fn.init.prototype = mini.fn;
  global.mini = mini;
})(window);

var M = mini("Nam");
M.sayHi().sayBye();

녀석의 이름은 mini다. 즉시실행함수를 만들어서 스코프가 밖으로 나가지 못하도록 하였으며 글로벌에 mini라는 변수를 넣어야 하기 때문에 window를 매개변수로 넣었다.

jQuery에서는 이 global이라는 녀석을 받아서 jQuery에서 사용할 수 있는 녀석인지 유효성 체크도 한다.
그리고 mini를 실행하면 mini.fn.init 이라는 객체를 뱉을 것이다.
한번보자.

실행해보자.
var M = mini("nam");
M.sayHi().sayBye();

잘 보면 두 함수를 연속적으로 실행할 수 있는데, 이건 다른 라이브러리에서도 많은 보았을 것이다. 이렇게 함수를 연결하는 방법은 우리가 사용하는 객체를 계속 리턴하는 것이다.
그런데 왜 prototype이 아니라 fn이라는 녀석에 넣은 것일까?

아마 계속 쓰일 녀석이라서 fn으로 간편화 한게 아닌가 추측을 하지만 정확한 이유는 모른다.
이렇게 new가 없이 객체를 생성하는 방법을 알아보았다.

2019년 11월 20일 수요일

[java] 상속 대신 구성을 사용하자.

왜 이 글을 쓰는 걸까

나는 자바 개발자로 일을 하면서 주위 사람들과 계속 이야기 하였다. 하지만 다들 수긍을 하는 듯 하면서 절대 바뀌지 않는 것이 있었다. 바로 **상속 대신 구성**이라는 말이다. 꽤나 오랜시간 구성을 사용하자고 설득하였지만 아무도 변화시키지 못했다. 나의 능력이 부족해서 속상하면서도 한 편으로는 내 말을 듣지 않는 것 같은 동료들이 마냥 아쉬웠다.

그런 의미로 나의 주장을 정리하고 가다듬어야 겠다는 생각을 했다.

내가 처음 자바를 공부할 때부터 들어온 말이 있다. 재사용을 위해 상속을 사용하라. 코드의 재사용을 위해서는 상속을 사용해야 한다. 두 개의 코드가 중복되면 상속을 해라. 그런데 뭔가 이상한 것을 느꼈다. 한 명이 말 한 것이 아니며 나에게 자바를 처음 알려준 강사부터 일터에 나가서 까지 그들은 마치 개발의 시작과 끝은 extends라는 키워드만 알면 다 할 수 있는 것으로 설명했다.

마치 상속이라는 단어가 마법의 단어이며, 이것만 있으면 다른 것을 알 필요도 없고 객체지향이건 유지보수 가능한 구조이며 모든 것이 가능하다고 설파했다.
그런데 막상 시간이 지나면 "이건 뭔가 잘못된 구조야", "아! 이건 상속을 좀 잘못했군. 제대로 했어야지", "앗! 이건 못 고친다. 이거 고치면 다 바꿔야해!" 상속은 점점 사람들을 옥죄었고 무엇이 잘못되었는지 몰랐다.

다들 너무 착하다. 사람들은 마치 상속으로 재사용 도모하라는 책들의 말을 믿고 구조가 이상하게 되면 마치 자기 탓인 것 마냥 아파한다. 자기 머리를 쥐어뜯는다. 하지만 끝까지 상속의 잘못은 아니라고 한다.

하지만 이건 그들의 잘못이 아니다 상속이라는 상아탑을 맹신하게하는 분위기가 그들의 도전을 막는다. 상속은 왠지 클래스 사이에 절대 바뀌지 않을 거라는 약속을 하며 새끼손가락을 거는 것 같다. 장기처럼 한 번 두면 무를 수 없는 약속이었다. 나는 이런 약속을 하고 싶지는 않았다. 무조건 깨질 것인데
후에 이 상속 구조를 자유롭게 수정할 수 있을까? 정말 이 구조가 맞는 걸까? 나중에 이 구조가 틀려서 바꿔야 한다면 바꿀 수는 있는 것일까?

없을 것 같았다. 시간이 지나면 지날수록 상속으로 끈끈하게 만들어진 유대관계는 깨기가 힘들어 보였다. 상속계층의 위로 올라갈 수록 그 약속이 많아지기 때문에 수정되면 안된다. 가령 자바에서 가장 위에 있는 객체는 `Object`이다. 이 녀석은 왠만해서는 절대 바뀌지 않는다. 수많은 자식들이 자신을 쳐다보고 있다. 그 자식들(서브클래스)은 당신(Object)이 변하지 않는다는 것만 믿고 재사용을 하기로 한 것이다.

상속은 마치 서브 클래스가 부모 클래스를 위에다가 그대로 복사해서 쓰는 것과 비슷하다. 만약 부모가 바뀐다고 해보자. 서브클래스도 당연히 바뀐 것이다.


2003년도부터 혹은 그 이전부터 상속은 문제였다.

https://www.javaworld.com/article/2073649/why-extends-is-evil.html
someone : If you could do Java over again, what would you change?
Gosling : I'd leave out classes
고슬링은 클래스를 없애고 싶다고 했다. 덧붙이자면 클래스 자체에는 문제가 없으나 extends관계가 문제이며 `implements`관계를 선호하라고 한다.


어떤 개발자는 자신의 99%의 코드에 상속을 사용하지 않았다고 한다

https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117
위 블로그에 있는 내용을 인용하겠다.
Using inheritance is not the only way to extend a class behavior.
But definitely is the most dangerous and harmful one.

I do not use inheritance in the 99% of my code.
And I really mean 99%.

- use interfaces to define the contracts between your classes.
- use final classes to implement behavior for those interfaces.
- use composition (through constructor dependency injection) to put things
together and prevent complexity


[이펙티브 자바]에서는 상속 대신 구성을 사용하라고 권고한다.

자세한 건 책을 참고


[오브젝트]에서는 상속을 절대 재사용을 위해 사용하지 말라고 한다.

계층구조를 표현하기 위해 사용해야 한다고 말이다. 즉 타입을 상속하기 위해서 쓰라는 말이다.


C++창시자분도 유튜브로 상속이 없이 어떻게 개발해야 하는지 발표하는 시대가 왔다.

세상은 정말 많이 변했다. 비야넷 아저씨도 변한 세상에 따라 자신의 주장을 바꿨다.

내가 정리한 문제점

내가 생각하는 상속의 문제점은 아래와 같다.
1. equals 구현에 문제가 생긴다(추이성)
2. 상속을 할 때 부모클래스 중에 구현된 기능이 있다면, 그것을 제대로 알고 구현을 해야 한다. 이 말은 하나의 소스코드가 둘로 쪼개져 있는 것과 같다.
3. 부모클래스가 변경되면 자식클래스 또한 전부 점검해야 한다.
4. 자식클래스가 바뀔 때, 부모클래스를 안 볼 수가 없다.
5. 버그를 찾기가 힘들다. 누구의 책임인지 확인하기가 쉽지 않다. 특히 `super`를 함께 쓰게되면 마치 저 클래스 멀리 있는 코드가 내 코드처럼 행동하면서 핑퐁을 하기 시작한다. 위로갔다 내려왔다 롤러코스터를 타게 된다.

그러므로 나는 상속보다는 `Composition` 소위 구성이라는 기능이 기본이 되어야 한다고 생각한다. `Composition`은 상속과 다르게 내부 구현을 알 수 없다. 필요한 부분에만 책임을 위임한다. 내부 소스로직이 수정되었다고 다른 소스에 문제를 일으키지 않는다. 위임한 책임만 완수하면 어떻게 바뀌던 상관이 없다. 하지만 상속은 문제를 끼칠 수 있다. 구성은 그래서 수정에 자유롭다. 아니 아예 전부 뜯어 고쳐서 새로 넣어도 된다. 그렇게 하라고 의존성 주입이 있는 거니까.

방금 구성을 사용해야 하는 다른 이유가 나왔다. 구성은 런타임에 의존성이 바뀔 수 있다. 즉, 프로그래밍이 실행 중에도 내 할일을 다시 집어넣을 수 있다는 말이다. 원래라면 하늘 아래 부모클래스는 하나밖에 가질 수 없고, 그것은 컴파일 이후에는 운명처럼 거스를 수가 없는데 말이다.


결론

글을 쓰면서 내가 틀렸던 건 아닐까 하는 생각으로 정리를 해보았으나.
틀리지 않은 것 같다. 오히려 자신감이 생긴 것 같다.

상속을 쓰기 전에 구성을 할 수 있는지 확인하자. 어떻게 해서든지 구성을 사용하려고 해보자. 코드에 더 믿음이 갈 것이다. 클래스에 final을 붙여서 상속을 막아 보자. 나 자신뿐만 아니라 사람들이 그 코드를 믿게 될 것이다. 상속보다 구성으로 짠 코드를 볼 때 더 별거 아닌 코드라고 생각하는 사람들이 많다. 그것이 목적이다. 더 별거 아닌 것처럼 보이게 더 쉽게 읽을 수 있게 말이다.

2019년 10월 27일 일요일

[리뷰][맨먼스미신]을 읽고

이 책은 저자가 한 프로젝트를 진행하면서 겪었던 내용을 에세이로 적은 책이다.
이 책은 소프트웨어 개발자의 이야기라기보다 인간들의 이야기이다.
우리 인간들이 일을 함께 하면서 일어나는 일이다. 용어들만 바꾼다면 어떤 산업에서도 적용될 이야기이다.

이 책에서 그 유명한 대사가 나오는데 바로
여자 9명이 있다고 아기가 1개월만에 나오는 것이 아니다.

이 대사가 우리가 일을 할 때, 무엇을 생각하고 계획해야 하는지 알려주는 것이라 생각한다.

내용

이 책은 프로젝트를 어떻게 관리해야 하는지 설명한다. 그는 자신이 믿지는 않지만 개발자의 경력과 생산성에는 큰 차이가 없는 연구결과를 지속적으로 알려준다.(믿지않으면서 왜 지속적으로 보여주는 거지)
아마 우리가 생각하는 통념을 조금이나마 깨뜨리려고 하는 것이 아닌가.
나에게 가장 기억에 남는 것은 일반적으로 개발을 해서 앱을 만드는 것과 그 앱을 상용화하기 위한 제품으로 만드는 것에는 아주 큰 차이가 있다는 것이다.

생산성 측면에서 보면 적은 인원의 외과의(스타개발자)가 최고의 성능을 가져오면 버그율도 적다. 하지만 큰 프로젝트를 진행하려면 시간이 아주 오래 걸릴 것이다.
그래서 사람이 더 필요하게 되는데 사람이 더 많아질 수록 의사소통, 교육 등에 병목이나, 마찰율(책에는 없는 단어다 ㅋㅋ)같은 것이 발생하여 진행이 많이 떨어지게 된다는 점을 말한다.
하지만 상용제품은 그밖에도 다른 것들에 문제가 있는데, 바로 요구사항 적립의 체계화, 테스트, 문서화, 재무관리 등으로 실제 개발시간에 수배의 노력(책에서는 9배라고 나온 것으로 기억한다.)이 필요하다는 것이다.

외과 수술 팀이라는 형식으로 팀을 꾸릴 것을 제안한다. 외과의과 나머지라고 부를 수도 있겠다. 바로 집도하는 외과의를 필두로 그를 보조하는 사람들을 분야별로 나눠서 구성하는 것이다.
재무, 프로그래밍언어, 새로운 툴 등을 분야별로 나눠서 처리하여 외과의를 보조한다. 신선한 방식이다. 아직 개발을 하면서 이런 형태를 본 적이 없어서 어떨지는 잘 모르겠지만, 좋을 것 같긴한다.

그리고 뒷부분으로 갈수록 문서화에 대한 내용에 노력을 기울인다. 주목할만한 점 중에 하나는, 개발을 하는데 서로의 이슈나, 요구사항 변경내용을 모두 적어서 모든 담당자들이 보도록 해서 서로 무슨일을 하는지 공유하는 것이 중요하다고 했지만,
종단에는 시간이 지나서 자신이 틀렸다고 말한다. 서로가 무슨 일을 하는지 모르고 자신의 업무에 집중하며 연결되어야 하는 부분에만 집중하는 것이 더 효율적이라고 주장이 대두되었고, 그는 그에 동조한다.

또한 논란이 되었던 은 탄환은 없다는 소프으웨어 개발에 지름길이란 없다는 것을 서술한다.
소프트웨어가 복잡한 것은 그 내재된 성질이기에, 도구로 고칠 수 있는 것이 아니라는 것이다. 객체지향 언어나 다른 방법론들이 쏟아져 나오지만, 결국 복잡성은 밖에 문제가 아닌, 소프트웨어 개발 자체가 가진 성질이며, 그것들을 없애주는 은탄환은 없다고 말한다.

이 책은 개발보다는 소통에 관한 내용이 많다. 그래서 누구라도 읽어볼만 하다고 할 수 있다.

[리뷰][클린 아키텍처]를 읽고

클린 아키텍처는 밥 아저씨의 클린 시리즈에서 가장 최신작인 것 같다.
이 책은 이전에 읽었던 [클린코드]와는 달리 아주 멀리서 소프트웨어산업을 바라보고 자신의 생각을 담아내고 있다.

내용

초반에는 자신이 생각하는 소프트웨어에 대해 설명하며 무엇이 소프트웨어를 더욱 소프트하게 만드는지 알려준다.
소프트함이라는 것은 변하기 쉬워야 한다는 것이다. 변하기 어렵게 만들거였으면, 하드웨어로 만들었을 것이라는 것이 그의 주장이다.
극단적인 예로, 변하기 쉽지만 요구사항에 맞지 않는 프로그램과 요구사항에는 맞지만 변하기 어려운 프로그램을 주어지면 그는 주저없이 첫번째를 선택하겠다고 말한다.
변하기 쉬운 것은 요구사항에 맞출 수 있지만, 후자는 비즈니스 로직이 변하거나, 버그를 발견했을 때, 바꾸기 어려우므로 안된다는 것이다.
물론 전대 변할 수 없는 프로그램은 없다. 하지만 프로그램을 변화시켜서 기대되는 비용과 개발을 하는 비용의 수지타산이 맞지 않을 때, 변할 수 없는 프로그램이라 부를 수 있다면 그런 프로그램은 존재할 것이다.

중반부터는 어떻게 하면 안전한 코드를 만드는지 설명한다. 하지만 코딩자체를 말하지는 않는다. 오히려 컴포넌트나 서비스같은 단위로 설명한다. 하지만 이 모든 내용은 작게는 코드에 대한 내용과 별 다를 것이 없어보인다.
그는 객체지향에 대해 설명을 하지만, 객체지향에 어떤 것도 이전에 C로 코딩을 하던 개발자들을 더 나은 개발자로 만들지는 못했다는 것을 설명한다. 왜냐하면 당시 개발자들 모두 그렇게 개발을 했었기 때문이다.
상속, 캡슐화, 다형성 모두 C언어로 가능한 구현들이었다. 대부분 문법적으로 간단하게 해준 것이며, 그 마저도 어떤 것은 오히려 구현이 불안정하게 만들었다. 저자가 객체지향으로 넘어가면서 얻은 장점은 다형성이라고 주장한다.

그리고는 지속적으로 그래프를 보여주면서 컴포넌트의 의존성의 방향이 얼마나 중요한지 설명한다. 나는 처음 이런 것을 보면서, 저자의 깊이를 더 알게 되었고, 정말 나중에 한 번 더 읽어야 겠다는 생각을 했다.
더해서 DI라는 것이 어떻게 아키텍처에서 쓰이는지 알게 되었다. 아무때나 DI를 다 붙잉는 것이 아니라. 이 방향을 바꾸기 위해서 하는 것이라는 것을 알게 되었다.
더해서 컴포넌트를 분리할 때도 DI를 사용해서 A,B컴포넌트에 서로 로직들이 잘 분리되도록 하는 것이다.

확실히 저자는 다형성에 큰 의의를 두고 있었다.

또 기억나는 것은 바로 구현에 대한 결정은 나중으로 미루라이라는 것이다. 그는 지속적으로 플러그인 패턴을 강조했다. 예를들어, 출력되는 곳이 콘솔일지, 프린터일지, HTML일지 이런것은 나중에 결정하라는 것이다.
환경은 또 어떨까? 웹으로 할 것인지 아닌지도 나중에 결정할 수 있도록 해야한다. 영속성은? DB로 할지도 나중에 결정해야 한다. 단순히 오라클/MySQL/... 중에 뭘 고를지 정하는 것이 아니다.
처음에는 map자료구조에 메모리로 저장하여 개발함으로써 영속성의 결정을 미룬다. 그리고 파일로 저장해서 또 미룬다. 미루고 미룬 후에 결정하는 것이다. 저자는 때때로 DB를 사용하지 않고 파일로 저장하고 가져와서 로딩하는 방식을 사용했다.
왜냐하면 DB가 필요 없었기 때문이다. 파일로도 충분했다.

이 구현을 미루는 것은 아키텍처에게 유현함(soft)함을 가져오게 해주고, 이런 외부 사항들로과 비즈니스로직을 자연적으로 분리시켜주게 된다. 그는 software가 firmware가 되는 것을 싫어했다. 펌웨어는 한 하드웨어에 종속되어 움직이는 경우가 대부분인데
그의 관점에서는 특정 DB에 종속된 것, 아니면 DB에 종속된 것을 펌웨어라 간주한다. 왜냐하면 세상은 많이 복잡해졌고, 그 정도의 종속도 펌웨어라는 것이다.
HTTP에 종속된 것도 펌웨어이다. 그는 코어 로직이 어딘가에 종속되지 않기를 바란다.

예를 들어보면 아래와 같다.

Core <- DB-AccessObject -> DB
     <- Network-Object
여기서 화살표 방향은 해당 객체가 무엇을 사용하는지 알려준다. 왼쪽에 있는 Core컴포넌트는 아무것도 사용하지 않아야 한다. 그래야 플랫폼이 바뀌어도 사용할 수 있다. DB를 사용하기로 결정했어도 Core는 변경되지 않는다. 그런데 말이 되지 않는다. 만약 Network에서 요청이 오고 DB-AccessObject를 실행해야 하는데 어떻게 Core에서는 사용하지도 않는데 실행을 한다는 거지? 그 답은 아래 DI에 있다(!!) 아래처럼 화살표가 되어 있다고 해보자.
Core -> DB-AccessObject -> DB
     <- Network-Object
이렇게 구현하는 것이 자연스러울 것이다. 여기서 DI를 적용하면?!
Core -> I-Persistent(in Core Component) <- DB-AccessObject -> DB
     <- Network-Object
이렇게 I-Persistent를 이용하여 저장하고 조회하는 것이다. I-Persistent는 코어 컴포넌트 안에 있고, 영속성관리를 무엇으로 해도 상관이 없게 되었다.
이것이 DI의 힘이다. 나는 지금까지 재사용을 쓰기 위해서다. 구현이 바껴도 사용할 수 있어서 DI를 쓴다고 알고 있었다. 물론 맞는 말이긴 해도, 이정도의 깊이가 있는 사람이 동일한 말을 하는 사람과는 전혀 달랐다.
나는 이정도의 넓은 숲을 보면서 말하지는 않았다. 왜냐하면 이런 세상을 전혀 몰랐으니까...

방향을 바꿔서 컴포넌트의 분리를 꾀하고, 종단에는 코어를 (유연하게 하는 것)지키는 것.
일류 아키텍처의 속마음을 잠깐 들여다 볼 수 있는 순간이었다.
마지막으로 가면 갈수록 그는 이 DI를 더 강조하는 것처럼 보인다. 그리고 실제적인 예시들을 보여주면서 어떻게 해야 하는지 알려 준다. 사실 다 읽었지만, 완벽하게 이해했다고 생각되지는 않는다.

나에게 또한 많은 생각을 하게 한 내용은 바로 프레임워크다.
그는 프레임워크에 종속되면 안된다고 말한다. 물론 프레임워크 개발자는 당신이 종속되도록 요청한다. 아예 하나의 언어처럼 붙어살길 원한다. 하지만 당신은 그렇게 하면 안된다는 것이다.
당신은 프레임워크를 툴로 써야 하며, 언제든지 바꿀 준비가 되어있어야 한다. 도구로써 써야 한다. 마법과 같은 일을 할 수 있다고 계약하지 말라.
당신에게 분명 되돌아 온다. 프레임워크에 종속되어 펌웨어가 되지 말라고 한다.

예를들어 자바의 스프링프레임워크를 예를 들면, Autowired를 하지말라고 한다. 차라리 Main에 다가 종속성을 다 쓰라고 말한다. 당신이 사용하는 모든 클래스에 spring이 import되어 있다면
종속되어 있는 것이다. 어쩌면 플러그인 패턴을 사용하여 DI로 프레임워크를 분리해서 사용하는 것이 해답이 될 수도 있을 것 같다.

끝없이 소프트하게 하라, 그것이 소프트웨어 개발자가 해야 할 일인 것이다.

이 책은 분명 다시 읽어야겠다. 누군가가 개발자가 되었다면, 그리고 소프트웨어개발자라면, 언젠가는 읽어야 할 책이라고 소개하고 싶다.

2019년 10월 25일 금요일

[리뷰][let over lambda]를 읽고-1

이 책은 [on lisp]를 보다가 머리를 식힐 겸 구매한 책이다.
다들 [on lisp]를 먼저 보라고 추천을 했지만, 어느정도 [on lisp]로 쉬운부분(매크로 초반까지) 읽고 [let over lambda]로 가는 것이 더 좋지 않을까 라는 생각을 한다.
[on lisp]보다 쉽다고 개인적으로 생각한다. [on lisp]는 아직 끝내지도 못했다... 무서워서... (일단 [on lisp]는 예제 코드가 너무 무섭다. 너무 길고 하나하나 쓰면서 이해하는데 시간이 너무 든다. 하지만 분명 나중에 정복해야 하는 책임은 분명하다.)

이 글은 단순한 감상이며 나중에 좀 더 심도 있는 내용을 적어야 할 것같다.

줄거리

그나저나
왜 이름이 [let over lambda]인가?
이것은 리스프에서 아주 기본적으로 사용되는 패턴으로 let으로 lambda를 덮는 형태를 말한다.
;; common lisp
(let ((a 10))
  (+ a 1))
;= 11
그러므로 [let over lambda]를 클로저라는 이름을 리습세상의 언어를 이용하여 우아하게 표현한 것이다.
이 [let over lambda]가 중요한 이유는 pointer를 조작하는 또 다른 방식이 될 것이기 때문이다.
이 let에서 생성되는 포인터를 이용하여 리스프 세상에서는 더 멋지고 자유롭게 표현할 수 있는 여지를 가지게 된다.
이것은 특히 커먼리습에서 대두된다.

이 책은 단순히 클로저만을 소개하는 책이 아니다. 시작이다. 이 책은 매크로를 설명한다. 이 책은 매크로로 어디까지 할 수 있는지 조목조목 설명해준다.
특히 뒤로 갈 수록 그 난이도는 올라가지만, 더 흥미로움은 더욱 커진다.

가장 충격적인 것은 Name Capturing이다. 이름이 충돌하는 문제인데, 이것은 람다에서도 나오는 베타-리덕션을 하다가 동일한 이름을 쓰는 매개변수가 있는 경우 리덕션에 문제가 생기는 경우와 비슷하다.
람다 계산의 경우 알파-컨버젼을 이용하여 시퀀스를 뒤에 붙이는 경우와 비슷하다.

책에서는 (gensym)으로 컴파일러가 내부적으로 현재 사용하고 있는 이름들을 확인하고 그들과는 겹치지 않는 이름을 만들어서 충돌이 일어나지 않도록 하는 방법을 제시한다.
하여 아래 코드는 절대로 참이 나올 수 없다.
왜냐하면 이 코드를 확인 한 후에 겹치지 않도록 이름을 제시해줄 것이기 때문이다.
(equal (gensym) (gensym))

이 서적은 더 나아가서, Name Capturing을 일부러 만드는 방식을 제시한다.
마치 큰 파도가 밀려오면 키를 돌려 정면으로 돌파해야 하는 것처럼.
Name Capturing을 피하는 것이 아닌, 정면 돌파하여 더 우아한 언어로 만들어주는 것이다.
마치 원래 그 언어가 있었 던 것처럼...
나중에 다뤄야 겠다.

후기

[let over lambda]는 이전에 람다계산 관련 책에 이어서 이번해에 읽은 최고의 책 중에 하나로 선정하겠다.
이번해에도 꽤나 많은 책을 읽었는데
기억에 남는 책은 이 두 권이라니... 나의 선택능력은 꽝인 것 같다.

꼭 읽어야 하는 책이다. 이 책을 읽기 위해서 common lisp, scheme, clojure 같은 언어를 둘러 본다해도
시간이 아깝지 않을 것이다.

2019년 10월 2일 수요일

[리뷰] lambda calculus에 대해 알아보기

https://www.notion.so/tombox/Lambda-Calculus-aa7f35c6a6ec4ecb9288e2bd33493dc8
lambda calculus에 관하여 이 책을 일독하였으나 기억이 가물가물해져서
다시 읽으면서 Notion에 적어보고 있다.

다시 읽어도 새롭다. 엄청난 책임이 틀림없다. 그리고 시간이 나면 Combinator에 대한 책도 읽고자 한다.

2019년 9월 11일 수요일

[clojure programming] 2. Functional Programming - 로깅파일 만들기

출처 : Clojure programming
Building a Primitive Logging System. with Composable Higher-Order Functions

(defn print-logger [writer]
  #(binding [*out* writer]
     (println %)))

(def *out*-logger (print-logger *out*))
(*out*-logger "hello")

버퍼를 만들어 보자.
(def writer (java.io.StringWriter.))
(def retained-logger (print-logger writer))
(retained-logger "hello-1")
(retained-logger "hello-2")
(str writer)

파일에 써보자.
(require 'clojure.java.io)
(defn file-logger [file]
  #(with-open [f (clojure.java.io/writer file :append true)]
      ((print-logger f) %)))

(def log-file (file-logger "message.log"))
(log->file "HELLO FILE")
지금까지는 아주 적은 코드로 서로 다른 곳에 쓸 수 있는 방법을 알아봤다.
만약, 여러곳에 써야 한다면 어떻게 해야 할까. doseq를 쓴다.
(defn multi-logger
  [& logger-fns]
    #(doseq [f logger-fns]
       (f %)))
(def log (multi-logger
           (print-logger *out*)
           (file-logger "message.log")))
(log "hello console and file")

마지막 예제, 진짜 로깅인 것처럼 시간 포맷까지 앞에 붙여보자.
Adding a piece of "logging middleware" to include a timestamp with each log message
(defn timestamped-logger
  [logger]
  #(logger (format "[%1$tY-%1$tm-%1$ts %1$tH:%1$tM%1$tS] %2%s" (java.util.Date.) %)))

(def log-timestamped (timestamped-logger
                      (multi-logger
                        (print-logger *out*)
                        (file-logger "message.log"))))
(log-timestamped "goobye, now")

12. Generalized variable

챕터8에서 말했듯이 매크로의 장점 중 하나인 매개변수의 변화이다.
setf매크로를 보자. 이 챕터는 setf에 숨겨진 것들을 본다. 그리고 예지로 무엇을 할 수 있는지도.

setf를 이용하여 매크로를 만드는 건 어렵다. 섹션1에서 틀린 방식을 보여줄 것이고,
다음섹션에서 무엇이 문제인지, 어떻게 바꾸는지 본다.
마지막 섹션에서는 어떻게 setf의 반전(inversion)을 만드는지 보자.

12.1 The Concept
setf 매크로는 setfq의 일반화 버전이라 생각하자, setf에서는 단순변수가 아니라. 호출식일 수 있다!
(setf lst '(a b c))
(setf x y)는 x가 y로 평가되도록 처리해라(see to it that x evaluate y)로 이해될 수 있는데,
매크로에서, setf는 위 문장이(일반적으로) 참이 되도록 하기 위해서 매개변수의 내부를 볼 수 있다.(보아야한다)

첫번째 매개변수가 심볼이면, setf는 setq로 확장한다.
하지만 queryf라면 setf는 그 쿼리와 연관된 assertion(단정?)으로 확장한다.
두번째 매개변수는 상수가 당연하니까 아래처럼 될 것이다.
(progn (rplaca lst 480) 480)
이렇게 쿼리에서 assertion(rplaca)로 변화하는 것을 inversion이라 한다.
커먼리습에 정의된 대부분 access함수들은 이미 정해진 inversion이 있다.
car, cdr, nth, aref, get, gethash...
setf의 첫번째 매개변수에 들어올 수 있는 표현식을 generalized variable이라 부른다.
이 setf를 이용한 매크로 예를 보자.
(defmacro toggle (obj) ;; 틀림
  `(setf ,obj (not ,obj)))
이 녀석ㅇ느 generalized variable을 toggle하는 매크로다.
(let ((lst '(a b c)))
  (toggle (car lst))
  lst)
(NIL B C)
다음으로 넘어가자. 작은 마을에서 거주자들끼리 관계를 DB에 저장하고자 한다.
사람들의 친구를 기록하는 테이블을 만들어보자.
(defvar *friends* (make-hash-table))
; 이 hash-table 안에 각 사람들의 친구들이 있는데 이것 또한 hash-table이다.
(setf (gethash 'mary *friends*) (make-hash-table))
; John을 Mary의 친구로 만들자.
(setf (gethash 'john (gethash 'mary *friends*)) t)
마을은 이제 두 파벌로 나눴다(친구가 있다 없다). 이 말은 양쪽 일수도 둘다 아닐 수도 없다.
둘 중 하나이다. (왠지 toggle이 유용해보임)
친구가 이다/아니다를 toggle할 것이다.
(setf (gethash x (gethash y *friends*))
      (not (gethash x (gethash y *friends*))))
; 꽤나 복잡한데 setf가 없이 더 편리하게 만들 수 있다.
; 일단 아래처럼 db로 접근하는 매크로를 만든다면
(defmacro friend-of (p g)
  `(gethash ,p (gethash ,g *friends*)))
(toggle (friend-of x y))
; 이렇게하니 꽤나 이쁘다. 그런데 문제가 있다고? 아래 섹션을 보자.

12.2 다중평가문제
toggle을 다시보자
(defmacro toggle (obj) ; wrong
  `(setf ,obj (not ,obj)))
이 문제는 10.1에서 대중평가와 동일하다.
사이드이펙트가 있는 경우 두번실행되면서 문제가 대두됨.
(toggle (nth incf i) lst)
; 여기에서 incf가 여러번 일어나면 그 숫자는 달라질 것!
(setf (nth (incf i) lst)
      (not (nth (incf i) lst)))
; i가 두번 증가한다. 하여!
(let ((lst '(t nil t))
      (i -1))
  (toggle (nth incf i) lst))
  lst)
(T NIL T)
?? 바뀐게 없는데 왜 그러지? 자세히 보자.
(setf (nth 0 lst)
      (not (nth 1 lst)))
두번째 값의 toggle을 첫번째에 덮은 것이다...

우린 이제 표현식 내부에서 뭘 하는지 봐야한다. 하나하나 분리해서 사이드이팩트가 있는지 보고 따로 평가해야한다.
이걸 쉽게하기 위해 커먼리습에서 define-modify-macro를 제공하고 얘가 대신 해준다.
이녀석은 3개의 매개변수를 받는데
1. name of macro
2. its additional parameters (after the generalized variable)
3. name of the function (which yeilds the new value for the generalized variable)
이제 toggle 매크로를 다시 정의하자
(define-modify-macro toggle () not)
; toggle : 이름
; ()  매개변수 (두번째 매개변수 부터라고 생각하면 된다)
; 함수 (generalized variable을 가져와서 새로운 값을 리턴하면 그것으로 재세팅됨)
여기 코드의 뜻은
(toggle place)를 평가하려면, place에서 특정 위치를 찾고, val이라는 값이 있으면 (not val)로 넣어서 그 리턴값을 세팅해라.
다시 평가하면 잘 나올 것.

이 toggle을 좀 더 일반화하자. (여러 매개변수를 넣을 수 있다면??!!)
; fig 12.1 Macros which operate on generalized variables.
(defmacro allf (val &rest args)
  (with-gensyms (gval)
    `(let ((,gval ,val))
       (setf ,@(mapcan #'(lambda (a) (list a gval))
                       args)))))

(defmacro nilf (&rest args) `(allf nil ,@args))

(defmacro tf (&rest args) `(allf t ,@args))

(defmacro toggle (&rest args)
  `(progn
     ,@(mapcar #'(lambda (a) `(toggle2 ,a))
               args)))

(define-modify-macro toggle2 () not)

12.3 New Utilities
이제 generalized variable을 이용한 유틸들을 보자.
Fig 12.1에 nilf가 특이하다
(setf x nil y nil z nil)
; 이녀석은 아래와 같다
(nilf x y z)
그림 12.2 리스트의 마지막을 파괴적으로 수정하는 매크로들이다.
Section 3.1에서 (nconc x y)보다는 (setq (nconc x y))를 쓰라고 한다.
둘 다 같은 일을 하지만 뒤에 내용이 명시적으로 잘 보이기 때문인가 보다.
이제 이 형식을 매크로로 만든 것이 12.2의 concf다.
(define-modify-macro concf (obj) nconc)
; concf 이름
; (obj) 매개변수 (기본 매개변수 place뒤에 obj, (concf place obj)이렇게 쓰는 것
; nconc 가져온 값을 이거로 치환
; obj는 여기서 y로 보임. obj는 generalized variable 뒤에 오는 객체랬으니까
; y역할을 하는 것이 맞다. 왜 이렇게 나눠놓았을까 생각해봤는데
; 아마 generalized variable(값이 바뀌어야 하는 변수)는 따로 다루는게 아닌가 싶고
; 당연히 다루려고 해당 매크로를 쓰는 거니까 상관없다고 생각했을 수도.

;; 그리고 conc1f는 list 뒤에 요소 하나를 추가하는 녀석이다.
(define-modify-macro conc1f (obj)
    (lambda (place obj)
            (nconc place (list obj))))
; 역시 두개를 받는 람다를 만들어서
; nconc를 했다. 그리고 place 뒤에 (list obj)를 붙였다.
; 저절로 place에 저장될 것!

(define-modify-macro concnew (obj &rest args)
    (lambda (place obj &rest args)
            (unless (apply #'member obj place args)
                (nconc place (list obj)))))
; concew는 conc1f와 같지만 멤버가 없을 때만 넣는 것.
; args는 member를 위한 다른 equals 방식이라던가 그런 설정값이다.

[on lisp] 11.5~6 클로저와 매크로의 차이

mvpsetq는 3개의 유틸리티 함수를 사용한다.
(defun mklist (obj)
  (if (listp obj) obj (list obj)))

(defun group (source n)
  (if (zerop n) (error "zero length"))
  (labels ((rec (source acc)
             (let ((rest (nthcdr n source)))
               (if (consp rest)
                   (rec rest (cons (subseq source 0 n) acc))
                   (nreverse (cons source acc))))))
    (if source (rec source nil) nil)))
(defun shuffle (x y)
  (cond ((null x) y)
        ((null y) x)
        (t (list* (car x) (car y) 
                  (shuffle (cdr x) (cdr y))))))
shuffle을 한번 써보자
(shuffle '(a b c) '(1 2 3 4 5))
(A 1 B 2 C 3 4 5) 
mvpsetq로 그림 11.13의 mvdo를 정의할 수 있다.
condlet처럼 이 매크로는 mapcar대신 mappend를 쓰는데 이유는 오리지널 매크로 호출의 변경을 바지하기 위함이다.
mappend-mklist 는 트리를 1레벨로 펼친다
(defun mappend (fn &rest lsts)
  (apply #'append (apply #'mapcar fn lsts)))

(mappend #'mklist '((a b c) d (e (f g) h) ((i)) j))

mvpsetq를 보자.
(defmacro mvpsetq (&rest args)
  (let* ((pairs (group args 2))
         (syms  (mapcar #'(lambda (p)
                            (mapcar #'(lambda (x) (gensym))
                                    (mklist (car p))))
                        pairs)))
    (labels ((rec (ps ss)
               (if (null ps)
                   `(setq
                     ,@(mapcan #'(lambda (p s)
                                   (shuffle (mklist (car p)) 
                                            s))
                               pairs syms))
                   (let ((body (rec (cdr ps) (cdr ss))))
                     (let ((var/s (caar ps))
                           (expr (cadar ps)))
                       (if (consp var/s)
                           `(multiple-value-bind ,(car ss) 
                                                 ,expr
                              ,body)
                           `(let ((,@(car ss) ,expr))
                              ,body)))))))
      (rec pairs syms))))
이걸 한번 매크로확장해보자.
(print 
 (macroexpand
  '(mvpsetq a 10 b 20 c 30 d 40)))
(LET ((#:G1 10))
 (LET ((#:G2 20))
  (LET ((#:G3 30))
   (LET ((#:G4 40)) (SETQ A #:G1 B #:G2 C #:G3 D #:G4))))) 
이렇게 LET으로 (gensym)을 하고 한번에 setq를 한다.

그런데 psetq랑 뭐가 다른거지?
(macroexpand
  ' (psetq a 1 b a c 3))
(LET ((#:PSETQ-1 1) (#:PSETQ-2 A) (#:PSETQ-3 3)) ; 밸류들
  (SETQ A #:PSETQ-1) (SETQ B #:PSETQ-2) (SETQ C #:PSETQ-3) NIL) 

(setf a 10)
(psetq a 1 b a c 3)
(print b) ; 10

그런데 어떻게 확장되는 걸까
1. args를 2그룹으로 나눈다.
2. 갯수만큼 (gensym)을 만든다.
(group '(a 10 b 20 c 30 d 40) 2)
((A 10) (B 20) (C 30) (D 40)) 

(mapcar #'(lambda (p)
    (mapcar #'(lambda (x) (gensym))
                (mklist (car p))))
    '((A 10) (B 20) (C 30) (D 40)))
((#:G3210) (#:G3211) (#:G3212) (#:G3213)) 
아래 함수를 이해해보자.
#'(lambda (p s) (shuffle (mklist (car p)) s))
mklist는 리스프로 만드는 함수 리스트면 그대로 뱉고 리스트가 아니면 감싼다.
shuffle은 위에 사용법이 있다 지그재그로 첫번째,두번째 매개변수를 합친다
p는 pairs이며, syms는 심볼인듯.(gensym)값
즉 pairs와 syms를 받아서 p의 첫번째 값을 gensym에 하나씩 넣어준다.
(labels ((rec (ps ss) ;; 내부함수에 label로 이름 지정, 매개변수 지정
  (if (null ps) ; ps가 이제 없다면
    `(setq ; (SETQ A #:G1 B #:G2 C #:G3 D #:G4) 부분을 만듬.
       ,@(mapcan #'(lambda (p s) (shuffle (mklist (car p)) s))
                 pairs syms))
     (let ((body (rec (cdr ps) (cdr ss))))
       (let ((var/s (caar ps))
             (expr (cadar ps)))
       (if (consp var/s)
         `(multiple-value-bind ,(car ss) 
                               ,expr
                               ,body)
         `(let ((,@(car ss) ,expr))
               ,body)))))))
 (rec pairs syms))
이제 다음으로 넘어가자. mvdo는 mvdo*를 봤으니 넘어가자.

11.6 Need for Macros
이렇게 평가를 잠시 미루도록 할 수 있는 건 매크로만의 전유물은 아니다.
함수를 closure로 감싸는 방법이 있다.
조건절 혹은 반복 평가들 모두 이런 걸로 가능하게 된다.
예를들어 if매크로를 아래처럼 바꿀 수 있다.
(defun fnif (test then &optional else)
  (if test
      (funcall then)
      (if else (funcall else))))
이걸 사용하려면 기존처럼 사용하면 안된다.
(if (rich) (g-sailing) (rob-bank))
여기서
(fninf (rich)
  #'(lambda () (go-sailing))
  #'(lambda () (rob-bank)))
이렇게 쓰면 된다. 만약 우리가 원하는 것이 조건평가만이라면 매크로는 전혀 쓸모 없다.
이것들은 그저 프로그램을 깨끗하게 하는 녀석들일 것이다. 하지만 매크로는 우리가 아규먼트 폼을 빼낼때나, 매개변수를 바인딩 한단거나 하는 일에서 필수이다.
반복에도 동일한 논리가 적용된다. 오로지 반복만을 위해서는 매크로는 필요 없다.
dolist 같은 걸 보자. 이걸 대체하려면 mapc와 lambda를 이용하면 된다.
(dolist (b bananas)
  (peel b)
  (eat b))

(mapc #'(lambda (b)
           (peel b)
           (eat b))
      bananas)
forever도 함수로 적을 수 있다.
(defun forever (fn)
  (do ()
      (nil)
      (funcall fn)))
이렇게 바디도 감싸서 보낼 수 있다.
하지만 반복을 생성할 때는, 그저 반복만을 위해 만들지 않는다.(as forever does)
이것들은 대게 binding와 iteration의 조합을 위해서 쓴다.
함수에서는 이런 개의 유틸리티 함수를 사용한다.
(defun mklist (obj)
  (if (listp obj) obj (list obj)))

(defun group (source n)
  (if (zerop n) (error "zero length"))
  (labels ((rec (source acc)
             (let ((rest (nthcdr n source)))
               (if (consp rest)
                   (rec rest (cons (subseq source 0 n) acc))
                   (nreverse (cons source acc))))))
    (if source (rec source nil) nil)))
(defun shuffle (x y)
  (cond ((null x) y)
        ((null y) x)
        (t (list* (car x) (car y) 
                  (shuffle (cdr x) (cdr y))))))
shuffle을 한번 써보자
(shuffle '(a b c) '(1 2 3 4 5))
(A 1 B 2 C 3 4 5) 
mvpsetq로 그림 11.13의 mvdo를 정의할 수 있다.
condlet처럼 이 매크로는 mapcar대신 mappend를 쓰는데 이유는 오리지널 매크로 호출의 변경을 바지하기 위함이다.
mappend-mklist 는 트리를 1레벨로 펼친다
(defun mappend (fn &rest lsts)
  (apply #'append (apply #'mapcar fn lsts)))

(mappend #'mklist '((a b c) d (e (f g) h) ((i)) j))

mvpsetq를 보자.
(defmacro mvpsetq (&rest args)
  (let* ((pairs (group args 2))
         (syms  (mapcar #'(lambda (p)
                            (mapcar #'(lambda (x) (gensym))
                                    (mklist (car p))))
                        pairs)))
    (labels ((rec (ps ss)
               (if (null ps)
                   `(setq
                     ,@(mapcan #'(lambda (p s)
                                   (shuffle (mklist (car p)) 
                                            s))
                               pairs syms))
                   (let ((body (rec (cdr ps) (cdr ss))))
                     (let ((var/s (caar ps))
                           (expr (cadar ps)))
                       (if (consp var/s)
                           `(multiple-value-bind ,(car ss) 
                                                 ,expr
                              ,body)
                           `(let ((,@(car ss) ,expr))
                              ,body)))))))
      (rec pairs syms))))
이걸 한번 매크로확장해보자.
(print 
 (macroexpand
  '(mvpsetq a 10 b 20 c 30 d 40)))
(LET ((#:G1 10))
 (LET ((#:G2 20))
  (LET ((#:G3 30))
   (LET ((#:G4 40)) (SETQ A #:G1 B #:G2 C #:G3 D #:G4))))) 
이렇게 LET으로 (gensym)을 하고 한번에 setq를 한다.

그런데 psetq랑 뭐가 다른거지?
(macroexpand
  ' (psetq a 1 b a c 3))
(LET ((#:PSETQ-1 1) (#:PSETQ-2 A) (#:PSETQ-3 3)) ; 밸류들
  (SETQ A #:PSETQ-1) (SETQ B #:PSETQ-2) (SETQ C #:PSETQ-3) NIL) 

(setf a 10)
(psetq a 1 b a c 3)
(print b) ; 10

그런데 어떻게 확장되는 걸까
1. args를 2그룹으로 나눈다.
2. 갯수만큼 (gensym)을 만든다.
(group '(a 10 b 20 c 30 d 40) 2)
((A 10) (B 20) (C 30) (D 40)) 

(mapcar #'(lambda (p)
    (mapcar #'(lambda (x) (gensym))
                (mklist (car p))))
    '((A 10) (B 20) (C 30) (D 40)))
((#:G3210) (#:G3211) (#:G3212) (#:G3213)) 
아래 함수를 이해해보자.
#'(lambda (p s) (shuffle (mklist (car p)) s))
mklist는 리스프로 만드는 함수 리스트면 그대로 뱉고 리스트가 아니면 감싼다.
shuffle은 위에 사용법이 있다 지그재그로 첫번째,두번째 매개변수를 합친다
p는 pairs이며, syms는 심볼인듯.(gensym)값
즉 pairs와 syms를 받아서 p의 첫번째 값을 gensym에 하나씩 넣어준다.
(labels ((rec (ps ss) ;; 내부함수에 label로 이름 지정, 매개변수 지정
  (if (null ps) ; ps가 이제 없다면
    `(setq ; (SETQ A #:G1 B #:G2 C #:G3 D #:G4) 부분을 만듬.
       ,@(mapcan #'(lambda (p s) (shuffle (mklist (car p)) s))
                 pairs syms))
     (let ((body (rec (cdr ps) (cdr ss))))
       (let ((var/s (caar ps))
             (expr (cadar ps)))
       (if (consp var/s)
         `(multiple-value-bind ,(car ss) 
                               ,expr
                               ,body)
         `(let ((,@(car ss) ,expr))
               ,body)))))))
 (rec pairs syms))
이제 다음으로 넘어가자. mvdo는 mvdo*를 봤으니 넘어가자.

11.6 Need for Macros
이렇게 평가를 잠시 미루도록 할 수 있는 건 매크로만의 전유물은 아니다.
함수를 closure로 감싸는 방법이 있다.
조건절 혹은 반복 평가들 모두 이런 걸로 가능하게 된다.
예를들어 if매크로를 아래처럼 바꿀 수 있다.
(defun fnif (test then &optional else)
  (if test
      (funcall then)
      (if else (funcall else))))
이걸 사용하려면 기존처럼 사용하면 안된다.
(if (rich) (g-sailing) (rob-bank))
여기서
(fninf (rich)
  #'(lambda () (go-sailing))
  #'(lambda () (rob-bank)))
이렇게 쓰면 된다. 만약 우리가 원하는 것이 조건평가만이라면 매크로는 전혀 쓸모 없다.
이것들은 그저 프로그램을 깨끗하게 하는 녀석들일 것이다. 하지만 매크로는 우리가 아규먼트 폼을 빼낼때나, 매개변수를 바인딩 한단거나 하는 일에서 필수이다.
반복에도 동일한 논리가 적용된다. 오로지 반복만을 위해서는 매크로는 필요 없다.
dolist 같은 걸 보자. 이걸 대체하려면 mapc와 lambda를 이용하면 된다.
(dolist (b bananas)
  (peel b)
  (eat b))

(mapc #'(lambda (b)
           (peel b)
           (eat b))
      bananas)
forever도 함수로 적을 수 있다.
(defun forever (fn)
  (do ()
      (nil)
      (funcall fn)))
이렇게 바디도 감싸서 보낼 수 있다.
하지만 반복을 생성할 때는, 그저 반복만을 위해 만들지 않는다.(as forever does)
이것들은 대게 binding와 iteration의 조합을 위해서 쓴다.
함수에서는 이런 바인딩에서는 제한적이다.
만약 당신이 연속적인 리스트 요소를 하나씩 차례대로 반복하면서 바인딩 하고 싶다면, mapping 함수를 쓸 수 있다.
하지만, 만약 요구사항이 이것보다 조금만 복잡해지면, 매크로를 써야 한