서양 사람들 눈에 비친 언어의 학습 난이도 (learning curve) 순위에 
당당히 한국어가 가장 어려운 언어로 뽑혔던 기사가 있었었죠.  (중국어도)   관련정보가기

그럼 프로그래밍 언어는 어떨까  생각해서 끄적여본게 아래..

배우기도 쉽고 사용하기도 쉽다 : 파이썬
배우기는 쉬운데 사용하기는 어렵다 : C 
배우기는 어렵고 사용하기는 보통 : Scala
배우기도 어렵고 사용하기도 어렵다 : C++
배우기 보통 사용하기 보통 : 자바,스위프트,Go

배우는것도 아리송하고 사용하기도 아리송하다 : Javascript 
참고 -> 심심풀이 자바스크립트 퀴즈 http://hamait.tistory.com/465


근데 사실 표본이 불명확하고 ( 얼만큼 해당 언어를 공부했고, 어떤 플젝을 했고)
쉽다라는 표현도 불분명 (사용성? 편리성?  사용층?) 해서 그냥 재미로 보시고.. 
회사에 와서 다른 사람들 생각을 검색을 좀 해보니  몇개가 있긴하네요.

https://github.com/Dobiasd/articles/blob/master/programming_language_learning_curves.md
https://generalassemb.ly/blog/the-8-most-in-demand-programming-languages-of-2016/
http://blog.8thcolor.com/en/2013/03/do-not-fall-in-the-learning-curve/


그래프 아래의 코멘트는 저의 개인적 해석입니다. 

자바스크립트 :
경험이 쌓이다가 콜백을 다루기 시작하면서 부터 생산성은 떨어지고 , 우쭐대는 감정은 올라간다.
깊게 사용하기 시작하면서 발생하는 직관적이지 않은 더티한 모습을 꼬집는게 아닌가 싶은..
 

자바 :
생산성은 언어에 익숙해질수록 높아지다가 디자인패턴을 알게되고 적용해야지 하는 순간부터 생산성은 
별차이없어지고 (패턴으로 나눈다는거 자체만으로도 엄청난 정신노동) 커플링을 없애고, 
함수도 쪼개는등 리팩토링을 통해 자기만족도는 높아진다.

 

C++:
경험이 쌓여도 생산성은 쬐금씩 올라가지만.. 근데 자부심은 이상하게 높다.
근데 템플릿을 만나고 깊이 있게 바라보려하는 순간부터 좌절을 함.
템플릿을 정복하는 순간부터 자부심은 하늘을 찌르고~
그 후에 C++ template meta progrmming
, Modern C++Design 을 이해못해서
다시 그래프가 떨어져야하는데 그건 없군요 ㅎㅎ

 

파이썬:
경험이 쌓일수록 꾸준히 생산성도 증대하고 만족도도 증대함. 뒤통수 치는 포인트가 딱히 없음.
유닛테스트 야그는 동적타입의 약점으로 인한 뒤통수를 방지하는 순간
디버깅하느라 빼앗긴 시간을 생산성으로 돌릴 수 있다는 뜻. 

 

LISP:
경험이 쌓일수록 꾸준히 생산성도 증대대고 만족도도 증대함.  매크로를 활용하는 순간부터 좀 더 좋아짐.
 

하스켈:
경험이 쌓여도 x 같은 외계개념들 때문에 생산성도 제자리.. 그 개념 하나 익힐때마다 자신감은 솟지만
바로 이어지는 다른 개념에 좌절..어느정도 정복하고 자신감이 높아지던 찰라, 모나드를 만나서 다시 좌절모드... 카테고리 이론에서 다시 좌절.. 모든걸 극복하면 생산성은 극대화 ;;


PHP:
자기 만족도가 높다.

상태를 가진 서비스 만들기에 대하여  [번역중]


상태없는 (비연결) 서비스는 낭비요소가 많다.

  • Stateless services have worked well. Storing the canonical source of truth in the database and horizontally scaling by adding new stateless service instances as needed has been very effective.

  • The problem is our applications do have state and we are hitting limits where one database doesn’t cut it anymore. In response we’re sharding relational databases or using NoSQL databases. This gives up strong consistency which causes part of the database abstraction to leak into services.

  • Data Shipping Paradigm

    • A client makes a service request. The service talks to the database and the database replies with some data. The service does some computation. A reply is sent to the client. And then the data disappears from the service.

    • The next request will be load balanced to a different machine and the whole process happens all over again.

  • It’s wasteful to repeatedly pull resources into load balanced services for applications that involve chatty clients operating over a session over a period of time. Examples: games, ordering a product, any application where you are updating information about yourself.

상태를 (연결) 가진 서비스는 프로그램하기 더 쉽다.

  • Caveat: stateful services are not magic. Stateless services are still incredibly effective if horizontal scalability is a requirement. Yet stateful services do offer a lot of of benefits.

  • Data Locality. The idea that requests are shipped to the machine holding the data that it needs to operate on. Benefits:

    • Low latency. Don’t have to hit the database for every single request. The database only needs to be accessed if the data falls out of memory. The number of network accesses is reduced.

    • Data intensive applications. If a client needs to operate a bunch of data it will all be accessible so a response can be returned quickly.

  • Function Shipping Paradigm

    • A client makes a request or starts a session the database is accessed one time to get the data and the data then moves into the service.

    • Once the request has been handled the data is left on the service. The next time the client makes a request the request is routed to the same machine so it can operate on data that’s already in memory.

    • Avoided are extra trips to the database which reduces latency. Even if the database is down the request can be handled.

  • Statefulness leads to more highly available and stronger consistency models.

    • In the CAP world where we have different levels of consistency that we operate against, some are more available than others. When there’s a partition CP systems chose consistency over availability and AP systems chose availability over consistency.

    • If we want to have more highly available systems under AP we get Write From Read, Monotonic Read, Monotonic Write. (for definitions)

    • If we have sticky connections where the data for a single user is on a single machine then you can have stronger consistency guarantees like Read Your Writes, Pipelined Random Access Memory.

    • Werner Vogel 2007: Whether or not read-your-write, session and monotonic consistency can be achieved depends in general on the "stickiness" of clients to the server that executes the distributed protocol for them. If this is the same server every time than it is relatively easy to guarantee read-your-writes and monotonic reads. This makes it slightly harder to manage load balancing and fault-tolerance, but it is a simple solution. Using sessions, which are sticky, makes this explicit and provides an exposure level that clients can reason about.

    • Sticky connections give clients an easier model to reason about. Instead of worrying about data being pulled into lots of different machines and having to worry about concurrency, you just have the client talking to the same server, which is easier to think about and is a big help when programming distributed systems.


쓰레드

똑똑똑!

누구니?

쓰레드 에요..

프로그램(프로세스) 안에서 실행 되는 하나의 흐름 단위에요.
내부에서 while 을 돌면 엄청 오랬동안 일을 할 수 도 있답니다.

쓰레드 끼리는 값 (메모리) 을 공유 할 수 있습니다.
가끔 서로 말도 없이 값을 바꾸어서 곤란에 빠지기도  합니다. 

내가 알람을 6시로 맞춰 놨는데 B가 8시로 맞춰놓는바람에 지각을 하고 말았어요. 

쓰레드는 필요 할때마다 OS 공장에서 만들어서 
사용하고 , 다 사용하고 나면 공장에서 수거해가요.

쓰레드는 동일한 메모리 영역에서 생성되고 관리되어서 상태 변이 속도가 '프로세스' 보다는 빠르지만 
그래도 생성/수거에 드는 비용이 나름 있다고 해요.

각각의 쓰레드는 동시에 자기가 맡은 일을 하기 때문에 빠르게 처리 된답니다.
멀티쓰레드 라고 해요.

 

쓰레드 풀

생성 / 수거에 드는 비용을 줄이기고 재사용성을 높이기 위해 
풀장에다가 여러개의 쓰레드를 먼저 만들어두고 사용하기도 해요.

큐같은 자료구조를 가진 클래스에 쓰레드를 미리 몇개 만들어서 놓아두면
그게 쓰레드 풀이에요. 별거 없어요.

작업 할거 있으면  그 중에 나 지금 한가해요~~라고 표식이 된 놈을 찾아서 일 시키면 됩니다.
일하는 놈을 따로 모아두고, 일 안하는 놈을 따로 모아두면 더 일 시키기 쉽긴 하겠죠? 

실제로 일 하는 놈 일 안하는 놈을 등을 관리하는 기술이 아주 다양하답니다.
다만 쓰레드가 하나 뿐이어도 충분히 일 할 수 있는데 10개씩 미리 만들어 둘 필요는 없어요.

Java 5 에서는 저런것을 만들어주는 방법이 생겼다네요.
이 생각 저 생각해서 만들었다는데 

첫째.  1분에 한번씩 임무를 수행 시키기 위한 쓰레드풀 
1초에 한 개씩  DB 에 값이 들어 갈 때 , 1분 평균을 구해서 다른 테이블에 집어 넣을 때  필요.

newScheduledThreadPool(int corePoolSize)

둘째. 풀장에 쓰레드를 고정적으로 몇개를 놀게 해줄지 정하는 방식도 제공해요.
항상 비슷하게 일이 많다면 이렇게 만들어주면 좋을거 같아요.

newFixedThreadPool(int nThreads)

세째.  어쩔땐 일이 없지만 어쩔땐 일이 많아 질때는 어떻하죠?
그때는 유기적으로 쓰레드의 숫자가 증가하고 감소하는 쓰레드풀도 있답니다.

newCachedThreadPool()

 

이런 쓰레드풀에게 어떤 업무가 배달되면 서로 간에 자기가 하겠다고 다투기도 한답니다.
배달되는 편지함이 하나인데 서로 편지함을 열어 보겠다고 난장판을 벌이죠..

그래서 어떤 경우는 쓰레드 자기 만의 고유 편지함을 따로 만들기도 합니다.

좀 더 자세히 살펴보도록 해요.

보통의 쓰레드 풀입니다.

폴더 안에서 파일을 읽어서 '사랑' 이라는 단어가 몇개 있는지 확인해 보기 위해
1. 폴더 3개를 각각 잡으로 만들어서 쓰레드 풀에 줍니다. 
2. 쓰레드 풀은 각 잡을 내부의 쓰레드에 배분 해줍니다.
3. 쓰레드들은 배분 받은 일을 합니다.
4. 끝  

이렇게 했을때 폴더 A 에는 내부에 폴더가 또 10개 정도 있고 파일도 많아서 
다른 쓰레드 들이 다 업무를 마쳤는데도 불구하고 혼자 일하고 있습니다.
나머지 쓰레드들은 멀뚱히 놀고만 있네요. 

자 이제 ForkJoin 방식을 보시죠.

폴더 안에서 파일을 읽어서 '사랑' 이라는 단어가 몇개 있는지 확인해 보기 위해
1. 폴더 3개를 감싸고 있는 부모 폴더를 잡으로 만들어 보냅니다.

2. 쓰레드 A 는 부모 잡을 받아서 자신의 로컬 큐에 1차 분활합니다.
3.쓰레드  B 는 놀고 있기때문에 A 로컬 큐에서 잡을 훔쳐다가 일을 합니다.
                       (훔쳐서까지 일을 하고 싶은 쓰레드 B -.-;;)
4. 쓰레드 B 가 훔쳐온 잡을 보니, 양이 많아서 분활합니다.
5. 이런식으로 세부적으로 분활해서 모든 쓰레드가 골고루 가져가서 일하게 됩니다.
6. 모든 쓰레드가 일을 종료하는 시간이 비슷해집니다. 

대략 감이 오시는 지요? 인생사 그렇듯이 그렇다고 항상 좋은건 아닙니다.

대략 말로 설명 드려 볼게요. 상상해 봅시다.

천만개의 랜덤한 숫자(1~100사이)가 있습니다. 
이 숫자들 중에서 10보다 작은 수가 몇개나 되는지 세는 코딩을 해봅시다.
CPU 는 4개라고 해요. 

방법 1.  그냥 쓰레드 하나로  천만번을 순회하면서 숫자를 센다.  (6초 걸림) 


방법2. ForkJoinPool 을 사용해서 리프가 100개 일 때까지 분활(fork)해서 각각의 수치를 위로 합쳐서(join) 계산한다. 쓰레드 4개를 골고루 사용하며 대신 태스크 객체는 분활한 만큼 만들어 지게 된다.(2.5초 걸림)  

방법3.  그냥 ThreadPoolExecutor 로 쓰레드 4개를 만든 후에 각각 천만개/4 로 나뉘어진 영역에 대해 순회하면서 숫자를 계산해서 합친다. ( 2초 걸림)  ForkJoinPool 보다 더 빠르네요? 네 그렇습니다. 쓸 때없는 객체 생성이 없어졌기때문이에요.

방법4. 저렇게 쓰레드 4개가 거의 동일한 일을 하게 된다면 ForkJoinPool 이 오히려 독이겠지만 하나의 쓰레드가 굉장히 오래 걸리고 나머지 3개의 쓰레드는 금방 끝이나는 경우는?? 네 이 경우는 ForkJoinPool 이 빛을 발하게 됩니다. (ThreadPoolExecutor  4초 , ForkJoinPool  3초) 

* 참고로  newFixedThreadPool 이런 팩토리 함수를 이용해서 만들어지는 것이 ThreadPoolExecutor 이다.즉 ThreadPoolExecutor 의 매개변수를 적절히 조절하면 newFixedThreadPool 나 newCachedThreadPool 에  해당하는 것들을 직접 만들 수 있다는 말.  

* 위의 방식에 대한 자세한 코드 및 설명은 "자바 성능 튜닝" 을 참고하세요. 


멀티쓰레드 처리율(throughput)   임백준님의 Akka 시작하기에서 발췌 
아카를 이용한 리팩토링을 끝마쳤을 때, 똑같은 컴퓨터 위에서 전과 동일한 몬테 카를로 시나리오를 수행하는데 걸리는 시간이 6시간에서 2시간으로 단축되었다. 66%의 시간이 절약된 것이다. 결과를 확인한 사람들은 깜짝 놀랐다. 단순히 자바 스레드에서 아카로 라이브러리를 바꾸었을 뿐인데 그렇게 엄청난 차이가 있을 수 있냐며 고개를 갸웃거렸다. 물론 이런 차이를 일반화할 수는 없다. 이런 결과 하나를 가지고 아카가 자바 스레 드보다 3배 빠르다고 말하는 어리석은 사람은 없을 것이다. 아카도 내부적으로 자 1 아카에 대하여 - 017 바 스레드를 사용하기 때문에 그런 비교 자체가 성립하지 않는다. 하지만 일반적 인 차원에서 짚고 넘어갈만한 부분도 있다. 이렇게 커다란 차이가 어디에서 비롯 되었는지 이해하려면 우선 암달의 법칙Amdahl’s law을 생각해볼 필요가 있다. 암달의 법칙은 이렇다. “멀티코어를 사용하는 프로그램의 속도는 프로그램 내부에 존재하는 순차적sequential 부분이 사용하는 시간에 의해서 제한된다.” Thread나 Task를 만들어서 ExecutorService에게 제출하는 식으로 동시성 코드를 작성하면 여러 개의 스레드가 동시에 작업을 수행한다. 하지만 프로그램 안에는 Thread나 Task가 포함하지 않는 코드가 존재한다. 여러 개의 스레드가 동시에 작업을 수행하더라도 synchronized 블록이나 데이터베이스, 네트워크 API 호출 등을 만날 때 다른 스레드와 나란히 줄을 서서 순차적으로 작업을 수행 해야 하는 경우도 있다. 암달의 법칙은 프로그램이 낼 수 있는 속도의 상한이 이런 순차적 코드가 사용하는 시간에 의해서 제한된다고 말하는 것이다. 이러한 순차적 코드의 또 다른 이름은 블로킹blocking 콜이다. 문제는 스레드 자체 가 아니라 스레드를 사용하면서 자기도 모르게 만들어내는 블로킹 콜이다. 조금 과장해서 말하자면 자바 개발자가 스레드를 이용해서 만들어내는 ‘동시성’ 코드는 일종의 신기루다. 사실은 코드 곳곳에 존재하는 블로킹 콜, 순차적 코드 때문에 전 체적인 프로그램의 처리율은 이미 상한이 정해져 있지만 여러 개의 스레드가 ‘동 시에’ 동작한다는 사실로부터 위안을 받을 뿐이다.

아래 부터는 좀 더 딱딱한 글이니 시간이 없으면 필요할때 보시면 될거 같습니다.

Fork Join Pool 

Java 7에서 새로 지원하는 fork-join 풀은 기본적으로 큰 업무를 작은 업무로 나누어 배분해서 , 일을 한 후에 
일을 취합하는 형태입니다. 분할 정복 알고리즘과 비슷하다고 보면 되는데 그림을 볼까요?

이렇게 Fork 를 통해서 업무를 분담하고 Join 을 통해서 업무를 취합합니다.

자바에서 풀을 관리하는  ThreadPoolExecutor 와 마찬가지로
ForkJoinPool 도 내부에 inbound queue 라는 편지함이 하나 있습니다.
그걸 두고 싸우느라 시간을 낭비하는것을 방지하기 위해 ForkJoinPool 은 쓰레드 개별 큐를 만들었어요.

 

위에 보다시피 왼쪽에서 업무를 보내면 (submit) 하나의  inbound queue 에 누적되고 그걸 A 와 B 쓰레드가 가져다가 일 처리를 합니다. A 와 B 는 각자 큐가 있으며 , 자신의 큐에 아무 업무가 없으면 상대방의 큐에서 업무를 훔쳐(?) 오기도 하네요. 최대한 노는 쓰레드가 없게 하기 위한 알고리즘입니다.
멍청하게 놀고 있는 쓰레드를 방지하기 위함이에요.

  • 쓰레드 자신의  task queue 로 덱(deque) 을 사용합니다. 덱은 양쪽 끝으로 넣다,뺏다 할수 있는 독특한 구조이며, C++ 에서 deque 자료구조는 vector,list 의 기능을 섞어 놓은듯한 모양새를 보입니다.이 덱은 ForKJoin 풀에서 중추를 담당하고 있습니다. 각 쓰레드는 덱의 한쪽 끝에서만 일합니다. 스택 처럼 말이죠. 나머지 한쪽 끝에는 잡을 훔치러온 다른 쓰레드가 접근합니다. 결과적으로 훔치러 온 녀석끼리 동일한 큐에서 경쟁을 벌일 수 는 있게 됩니다.

 

  • 잠재적인 문재는 쓰레드가 다른 쓰레드의 잡을 훔치러 갔다가 실패할 경우가 빈번할때 생깁니다.계속 빈 큐에다 대고 삽질을 하는거죠. 이것을 막기위해 "unemployed" 워커 쓰레드는 룰에 따라 휴식 상태로 바꿉니다. 

 

모든 기술은 항상 옳는 법이 없습니다.
단순 jsp 모델1 이 스프링 보다 좋을때도 많으며 
어떤 정렬 자료구조, 어떤 검색 알고리즘이 항상 좋은것은 아니듯이 

위의 ForkJoinPool 도 적절하게 사용하면 쓰레드들이 최적의 CPU 를 활용하는 효율성을 보이겠지만 
그렇지 않다면 그냥 거추장 스러운 개별 큐가 추가되고 불필요한 객체생성으로 낭비만 더 해 질 뿐입니다.

 

                                  ForkJoinPool 인터페이스

ForkJoinPool 은 저런것들을 편하게 사용자가 수행하는데 도움이 되는 특별한 인터페이스를 제공합니다.
쓰레드가 새로운 잡(업무) 을 분활해서 자신의 로컬 큐에 적재시키는 것 같은거 말이죠. 거기에는 Runnables 나 Callables 같은게 사용되지 않습니다.  대신 ForkJoinTask 이것이 사용됩니다. 그리고 ForkJoinTask 는 두가지로 방법을  제공하는데 하나는 리턴이 없는것( RecursiveAction ) 과 리턴이 있는 것 (.RecursiveTask) 입니다.

 

자 이제 코드로 말해볼까요? 개발자는 코드 아니겠습니까~

간단한 사용예 (RecursiveAction)

import java.util.ArrayList; import java.util.List; import java.util.concurrent.RecursiveAction;  public class MyRecursiveAction extends RecursiveAction {      private long workLoad = 0;      public MyRecursiveAction(long workLoad) {         this.workLoad = workLoad;     }      @Override     protected void compute() {          //if work is above threshold, break tasks up into smaller tasks         if(this.workLoad > 16) {             System.out.println("Splitting workLoad : " + this.workLoad);              List<MyRecursiveAction> subtasks =                 new ArrayList<MyRecursiveAction>();              subtasks.addAll(createSubtasks());              for(RecursiveAction subtask : subtasks){                 subtask.fork();             }          } else {             System.out.println("Doing workLoad myself: " + this.workLoad);         }     }      private List<MyRecursiveAction> createSubtasks() {         List<MyRecursiveAction> subtasks =             new ArrayList<MyRecursiveAction>();          MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2);         MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2);          subtasks.add(subtask1);         subtasks.add(subtask2);          return subtasks;     }  }

MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24); forkJoinPool.invoke(myRecursiveAction);

 

복잡한 사용예 (RecursiveTask)

FolderProcessor.java

package forkJoinDemoAsyncExample;
 
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
 
public class FolderProcessor extends RecursiveTask<List<String>>
{
   private static final long serialVersionUID = 1L;
   //This attribute will store the full path of the folder this task is going to process.
   private final String      path;
   //This attribute will store the name of the extension of the files this task is going to look for.
   private final String      extension;
 
   //Implement the constructor of the class to initialize its attributes
   public FolderProcessor(String path, String extension)
   {
      this.path = path;
      this.extension = extension;
   }
 
   //Implement the compute() method. As you parameterized the RecursiveTask class with the List<String> type,
   //this method has to return an object of that type.
   @Override
   protected List<String> compute()
   {
      //List to store the names of the files stored in the folder.
      List<String> list = new ArrayList<String>();
      //FolderProcessor tasks to store the subtasks that are going to process the subfolders stored in the folder
      List<FolderProcessor> tasks = new ArrayList<FolderProcessor>();
      //Get the content of the folder.
      File file = new File(path);
      File content[] = file.listFiles();
      //For each element in the folder, if there is a subfolder, create a new FolderProcessor object
      //and execute it asynchronously using the fork() method.
      if (content != null)
      {
         for (int i = 0; i < content.length; i++)
         {
            if (content[i].isDirectory())
            {
               FolderProcessor task = new FolderProcessor(content[i].getAbsolutePath(), extension);
               task.fork();
               tasks.add(task);
            }
            //Otherwise, compare the extension of the file with the extension you are looking for using the checkFile() method
            //and, if they are equal, store the full path of the file in the list of strings declared earlier.
            else
            {
               if (checkFile(content[i].getName()))
               {
                  list.add(content[i].getAbsolutePath());
               }
            }
         }
      }
      //If the list of the FolderProcessor subtasks has more than 50 elements,
      //write a message to the console to indicate this circumstance.
      if (tasks.size() > 50)
      {
         System.out.printf("%s: %d tasks ran.\n", file.getAbsolutePath(), tasks.size());
      }
      //add to the list of files the results returned by the subtasks launched by this task.
      addResultsFromTasks(list, tasks);
      //Return the list of strings
      return list;
   }
 
   //For each task stored in the list of tasks, call the join() method that will wait for its finalization and then will return the result of the task.
   //Add that result to the list of strings using the addAll() method.
   private void addResultsFromTasks(List<String> list, List<FolderProcessor> tasks)
   {
      for (FolderProcessor item : tasks)
      {
         list.addAll(item.join());
      }
   }
 
   //This method compares if the name of a file passed as a parameter ends with the extension you are looking for.
   private boolean checkFile(String name)
   {
      return name.endsWith(extension);
   }
}

And to use above FolderProcessor, follow below code:

Main.java

package forkJoinDemoAsyncExample;
 
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
 
public class Main
{
   public static void main(String[] args)
   {
      //Create ForkJoinPool using the default constructor.
      ForkJoinPool pool = new ForkJoinPool();
      //Create three FolderProcessor tasks. Initialize each one with a different folder path.
      FolderProcessor system = new FolderProcessor("C:\\Windows", "log");
      FolderProcessor apps = new FolderProcessor("C:\\Program Files", "log");
      FolderProcessor documents = new FolderProcessor("C:\\Documents And Settings", "log");
      //Execute the three tasks in the pool using the execute() method.
      pool.execute(system);
      pool.execute(apps);
      pool.execute(documents);
      //Write to the console information about the status of the pool every second
      //until the three tasks have finished their execution.
      do
      {
         System.out.printf("******************************************\n");
         System.out.printf("Main: Parallelism: %d\n", pool.getParallelism());
         System.out.printf("Main: Active Threads: %d\n", pool.getActiveThreadCount());
         System.out.printf("Main: Task Count: %d\n", pool.getQueuedTaskCount());
         System.out.printf("Main: Steal Count: %d\n", pool.getStealCount());
         System.out.printf("******************************************\n");
         try
         {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e)
         {
            e.printStackTrace();
         }
      } while ((!system.isDone()) || (!apps.isDone()) || (!documents.isDone()));
      //Shut down ForkJoinPool using the shutdown() method.
      pool.shutdown();
      //Write the number of results generated by each task to the console.
      List<String> results;
      results = system.join();
      System.out.printf("System: %d files found.\n", results.size());
      results = apps.join();
      System.out.printf("Apps: %d files found.\n", results.size());
      results = documents.join();
      System.out.printf("Documents: %d files found.\n", results.size());
   }
}

Output of above program will look like this:

Main: Parallelism: 2 Main: Active Threads: 3 Main: Task Count: 1403 Main: Steal Count: 5551 ****************************************** ****************************************** Main: Parallelism: 2 Main: Active Threads: 3 Main: Task Count: 586 Main: Steal Count: 5551 ****************************************** System: 337 files found. Apps: 10 files found. Documents: 0 files found.

 

How it works?

In the FolderProcessor class, Each task processes the content of a folder. As you know, this content has the following two kinds of elements:

  • Files
  • Other folders

If the task finds a folder, it creates another Task object to process that folder and sends it to the pool using the fork() method. This method sends the task to the pool that will execute it if it has a free worker-thread or it can create a new one. The method returns immediately, so the task can continue processing the content of the folder. For every file, a task compares its extension with the one it’s looking for and, if they are equal, adds the name of the file to the list of results.

Once the task has processed all the content of the assigned folder, it waits for the finalization of all the tasks it sent to the pool using the join() method. This method called in a task waits for the finalization of its execution and returns the value returned by the compute() method. The task groups the results of all the tasks it sent with its own results and returns that list as a return value of the compute() method.


참고 싸이트

http://www.h-online.com/developer/features/The-fork-join-framework-in-Java-7-1762357.html
http://howtodoinjava.com/java-7/forkjoin-framework-tutorial-forkjoinpool-example/
http://www.oracle.com/technetwork/articles/java/fork-join-422606.html
http://zeroturnaround.com/rebellabs/fixedthreadpool-cachedthreadpool-or-forkjoinpool-picking-correct-java-executors-for-background-tasks/
http://gee.cs.oswego.edu/dl/papers/fj.pdf
http://stackoverflow.com/questions/7926864/how-is-the-fork-join-framework-better-than-a-thread-pool

 


저번 주말에는 "신과 함께" 라는 만화를 4900원인가 주고 결재해서 8권 완결본을 다 보았습니다.

1부는 "지옥" 편 

2부는 "이승" 편

3부는 "신화" 편

요렇게 구성되있는데요. 1부 "지옥" 편은  40대 직장인이 과로 및 음주로 사망한후에 지옥을 
가서 일생에 대한 재판을 받는 여정을 그리고 있습니다.

그 지옥편을 보면서 어렸을때 부처님 오시날이면 종종 해주던 영화가 생각 나더라구요.
"지옥에간 어머니를 구하기 위해 지옥으로 뛰어든 스님" 의 이야기를 그린 영화였던거 같은데 그 동양적 SF 와 신화적인 분위기는 , 반지의 제왕이나 해리포터 시리즈 같은  서양 판타지에서 느낄 수 없는 무엇인가가 있었습니다. 지금도 그 분위기가 아련하게 느껴지는군요. 

다시 만화 스토리로 넘어와서 
만화는 주인공 "김자홍" 이 다음 순서대로 대왕이 있는 곳으로  찾아가서 재판을 받는  모험~


진광대왕   - 도산지옥을 담당  (가물가물..)

초강대왕   - 화탕 지옥을 담당 (불의 고통)

송제대왕   - 한빙 지옥을 담당 (추위의 고통)

오관대왕   -  검수 지옥을 담당 ( 칼로 썰어버림)

염라대왕  -  혓바닥에 농사를 짓던가..가물가물 

변성대왕  - 독사 지옥을 담당 (뱜~)

태산대왕  - 거해 지옥을 담당 

이렇게  7 대왕의 재판을 차례로 받은 후에 마지막에 6 개의 환생문  즉 지옥,아귀,축생,아수라,인간,천도 중 하나로 다시 윤회하게 됩니다. 착하게 살면 (?)  이 윤회의 고통에서 벗어나게 되는것입니다.

..

..


저는  이번주 웹프론트엔드 작업을 하고 있습니다. 그 중에서 부트스트랩으로 Full Page 와꾸를 꾸미고 있거든요. 그 후에는 AngularJS 로 생명을 좀 주고, 그 후에는 Ajax , 그 후에는 Play2 나 Spring , 그 후에는 DB 그 후에는 앱 -> 미들웨어 -> 게이트웨이 -> 빅데이터  -> 머신러닝 -> 산출물->마무리 이 여정을 떠날듯 싶습니다. 그 후에 또 다시 사업계획서 작성 ->  DevOps 구축 -> 프런트엔드 개발이 다람쥐 쳇바퀴 돌듯이... (형편이 넉넉치 못하여 거의 혼자 합니다) 

지금 css / bootstrap 지옥을 지나고  AngluarJS 지옥으로 가는 이 시점에서 문득 생각해 봅니다.
이 윤회의 사슬에서 벗어나는 방법은  "로또"   뿐...


세줄요약 :

- 기승전"로또" 
- 개발 지옥 로또 천국
- func main () {  print  "hell world " }


추가 ) 

지옥에도 꽃은 핍니다.일이 술술 풀리거나, 기술적 깨우침의 순간에는 천국입니다.
소프트웨어개발직의 보람이죠 ㅎㅎ


p.s

"지옥에간 어머니를 구하기 위해 지옥으로 뛰어든 스님"  는 목련존자에 대한 이야기로써 
흥미있으시면 링크를 타고 가보세요~


엄청 깊이 있는 내용은 아니나 , 시간이 급한 일을 넉넉한 돈으로 해야한다. 

*파트를 나누어서 개별 파트에 능숙한 개발자를 이용합니다.

깊이있는 내용은 아니고 시간은 나름 넉넉한데 회사가 유지될 돈이 중요하다. 고용에 부담이 있는 상황이다. 
* 풀스택 개발자를 구합니다.

깊이있는 내용이 한두꼭지있고, 대부분 평이하다. 시간은 넉넉하다.
* 풀스택 개발자를 구합니다. 해당 분야의 전문가를 단기적으로 이용하거나 똑똑한 혹은 진취적인 개발자가 전담합니다.

어느정도 안정적이 궤도에 들어섰다. 시간,돈도 준비되었다
* 파트로 나누어서  개발자를 이용한다.  완성도를 높이기 시작합니다.


결국 시간과 돈에 따라서 결정되는 그냥 이것 일 뿐이며, 이상한 미사여구를 가져다 붙여가며 폄하하거나 띄워주는 논쟁은 의미가 없습니다. (악용하는 사례는 어느종류에서든 있기 마련) 풀스택 개발자 타령은  말도 안된다? 풀스택 개발자를 모두 지향해야한다? 이렇게 말하는 사람은 틀렸습니다.  


예를 하나 들어 본다면 이번에 2년짜리 과제/아이디어가 선정되었는데 2년 후에 어찌 될 지 모른다. 1년 동안 디자인/프런트엔드/백엔드/앱/하드웨어 개발을 해야한다. 

아이디어 개발이기 때문에 양이 그리 많거나 하진 않다.

이 경우 하드웨어(및 임베디드) 개발자 와  풀스택 개발자를 구합니다. 그리고 여력이 있으면 해당 아이디어에 따른  다른 역활, 특화된 역할을 하는 사람을 구합니다. (참고로 풀스택은 web 에 한정된다고 얘기하는 분도 계신데 , 어떻게 인력을 관리할 것인가의 문제지 분야의 문제로 협소하게 볼 필요는 없습니다. 예를들어 포토,일러,HTMML,CSS,Jquery 를 다 하는것도 풀스택이며  빅데이터 분석,인프라구축을 하면서 앱개발을 하는것도 , SoC 에서 ZWave 임베디드 코딩하면서 웹개발까지 하는것도 풀스택입니다.  물론 스탓업이 유행하는 요즘 대부분의 아이디어 기반 창업이 웹,앱에 극도로 몰려있는 상황에선 웹,앱으로 한정지어도 무방하긴 합니다. 사물인터넷 덕분으로 하드웨어도 조금씩 많이지고 있긴 하죠?)


사업을 하는데 가장 중요한것 중 하나는 인건비입니다. ( 아이디어,타이밍,특화기술 등과 함께 ) 
소수의 사람이 오랫동안 뭉쳐있을 수 있냐가 성공의 요소이기도 합니다.
사업은 타이밍이 중요합니다 만 무조건 빠르게 만든다고 그 타이밍에 맞춰지는것도 아닙니다.

따라서 CEO 의 판단에 따라서 개발자를 늘려서 적시 타이밍에 제품을 완성해야하는지 혹은 조금 시간이 걸려도 인건비 관리를 하냐는 그(그들) 의 선택 일 뿐 무엇이 더 중요하다 라는 게 정해진건 아닌거죠.


요즘은 특히나 이미지를 이용한 디자인 요소가 웹/앱개발에서 비중이 대폭 축소되었으며, (CSS의 발전과 심플한 페이지의 유행 등) 하나의 언어로 모든것을 개발 할 수 있는 환경이 생겨나고, 구글의 수많은 정보는 쉽게 접근가능하여 기술을 가져다 사용하는데 거칠 것 이 없어지게 되었고, 앱 개발은 아이오닉/react native 등의 웹 개발 지식이 있으면 손쉽게 접근할 수 있는 선택지도 있으며 마지막으로 복잡한 서버 인프라에 대한 유지,관리를 소프트웨어로 유기적으로 관리 할 수 있는 아마존 AWS 같은 서비스의 등장으로 스탓업의 유행과 맞물려 풀스택 개발자/디자이너의 필요성이 넓어지고 있습니다.

'누구나 풀스택 할 수 있고 누구나 파트에 집중 할 수 있다.'

물론 누군가가 "빠른시간내에  모두 다  꼼꼼히 하도록 해" 라는 허무맹랑한 소리를 하더라~~ 라는 도시전설은 그냥 흘려 들으면 됩니다. 풀스택 개발이 파트별 개발 정도의  꼼꼼하거나 다채로우며 같은 속도로 하는것은 불가능하며 그런걸 요구하는 사람은 명치를 존나 씨게 (일명 '명존쎄')  맞아야죠. :-<


마지막으로 풀스택 개발자를 고용하고자 하는 분들에게 한마디 하자면
풀스택 개발자를 뽑아서 사용할때 , 다양한 문제를 해결하도록 지시 하는건 정당하고, 그걸 개발자가 시도해야 하는것도 당연합니다. 예를들어 웹/응용 개발자에게 빅데이터 인프라를 갖춰라, 데이터를 분석해라, 데이터를d3.js/processing/grafana 로 가시화 하라고 요청 할 수 있습니다. 요청이 오면 해야합니다. 다만 성공률에 대해서는 계산 해야하며 (예를들어 딥러닝 안해본 개발자에게 특정 상황에서의 건물,보행자 구분 시도가 실패 했을때 의 대책 등 )  꼼꼼함과 정확성 및 속도를 "당신 머리 속으로 어림잡아서" 완수 하길 바라면 "명존쎄' 당할 각오는 하셔야...

도구가 발전 할 수록 혼자 집을 지을 수 있을 능력은 커지겠지만 규모와 한계에 대해선 인지하고 계셔야 겠지요. 
(물론 이게 참 어렵긴 합니다. ㅎㅎ) 


p.s

읽을만한 풀스택 관련 글  :  풀스택 개발자, 그것은 환상..      마지막 쯤 어딘가를  발췌했습니다. 
과격하시네요.  돈이 많은 조직이 얼마나 있다고 -.-

"좌우지간, 간단하게 이야기해서 '풀스택 개발자' 타령하는 구인광고를 보게 된다면 , 그 회사나 팀은 무엇인가 잘못된 생각을 하고 있거나, '돈' 이 없는 조직이라고 생각하면 된다. 거기에, '기술' 이나 '개발' 에 대해서는 아무것도 모르는 사람이 사장으로 존재하고 있다고 보면 된다.



 인더스터리 4.0 (링크로 가시면 전체 글을 읽을 수 있습니다)




* 인더스트리 4.0 을 제조공정의 자동화 수준을 고도화 하는것으로 오해하는 사람이 많은데 다른 이야기.

* 대량생산과 같은 수준의 비용으로 개별 맞춤형 생산 가능. 

* 부품이 스스로 주체가 된다. (cyber physical system)




C++ 만 알고 있다가 자바를 알고서 C++ 은 뒷전이되고, 파이썬을 알고 나서 자바는 뒷전이 되고.. 이제 파이썬 말고 다음 주력 언어로 무엇을 해볼까 생각하던중.. Go 아니면 Clojure 를 생각하고 있었는데  (물론 이런 언어 선택은 상황에 맞춰 선택하는게 답이지만 ) 이번에 폴리글랏 오프라인 스터디를 시작하면서 처음으로 Swift 언어로 코드를 작성해봤고, 공부를 좀 했는데 이거 맘에 들더군요.  :-)    그래서 좋은건 나누고자하는 마음으로  장/단점을 리스팅 해보겠습니다. 

(주관적 견해가 섞여있습니다.)


장점


- 파이썬과 차원이 다른 속도.

 거의 C++ 과 비교될만한 속도를 보여줍니다. 
 C++ > Swift > Go >  Scala > Clojure > 표준 Python  정도로 보시면 될거 같습니다.
 사족을 추가하자면 현대 컴퓨팅 환경에서 간단한 벤치마크로 속도를 판별하는건 좀 의미가 없지 않나 합니다.
 리얼 월드에서의 진짜 문제들을 빠르고 안정성있게 개발하는게 우선시 되고 있으니까요.

- 파이썬,루비급의 심플함 및 코딩하고 싶게 만드는 매력 

 구글의 Go언어가 C 의 진화형(?) 자식이라면 , 애플의 Swift 는 파이썬의 진화형 자식입니다.   
 그만큼 가독성 및 심플하다는 야그. import 나 include 를 할 필요도 없음.  Clojure 같은건 대중화 될 가능성이 희박하다로 보고 있으며  C 및 객체지향에 익  숙해져 있는 주변개발자들을 설득하기가 어려울거 같습니다. (이 언어를 좋아하는 사람만 뽑던가 해야함) 

- Class 의 존재 

 자바스크립트 (최근에 class 가  포함됬다고 합니다) 라든지, Go 언어는 명시적인 Class를 이용한 객체지향언어가 아니라서 좀 헦  깔리며, 가독성이 떨어집니다. Class 매니아로써 이거시 필요합니다. 파이썬 2.7 은 Class  가 있지만  self. 를 계속 써  줘야한다는 불편함이 도를 넘었습니다.

- 오픈소스로 개방 

 다행이 swift 는 자사환경에서만 돌아가는것에서 벗어나서 범용 언어로 방향을 틀었습니다. 리눅스나 윈도우에서 
 서버개발을 swift 로 할 수 있겠지요.

- 아이폰 개발 및 안드로이드 개발까지 

 아이폰개발을 할 수 있으며, 추후에 안드로이드 개발까지 할 가능성이 열려 있습니다.

- 컴파일 언어지만 , 인터프리터로도 실행시킬수 있습니다. 

 파이썬처럼 그냥 코드 고치고 실행하면 됩니다. 엄청 편하겠쥬~



단점 


- 신생언어라 써드파티지원이 약함 (치명적이죠 -.-;;) 

 파이썬의 다양한 분야의 그 어마어마한 라이브러리 지원에 비하면, 엄청 초라합니다. 너무 신생이라 당연합니다. 예를들어 급히 얼굴인식이나 OCV/R 프로그램을 만들어야 한다고 치면, 파이썬은 구글링한후에 openCV 이용해서 코  딩하면 1시간이면 만듭니다. (물론 완성도등 수준에 따라서 1년이 걸릴수도있습니다.)

- Go 에 비해 컴파일 시간이 현저히 느립니다. 

 Go가 너무 빠른것. Go 는 컴파일언어인데 컴파일&실행이 인터프리터 실행한것처럼 빠르게 진행된답니다.

- Go/Scala/Clojure 가 이미 오픈소스 병렬/분산솔루션에 채택되고  쓰이는것에 비해  앱개발 위주로 사용되고 있다. 이건 첫번째 단점과 맥을 같이하며 시간이 해결해 줄거라 봅니다.




함수형 프로그래밍이란? (1편 부작용) 

위의 한주영님의 번역글을 읽어보면 


"모든 입력이 입력으로 선언되고 (숨겨진 것이 없어야 한다) 마찬가지로 모든 출력이 출력으로 선언된 함수를 ‘순수(pure)’하다고 부른다."


이런 내용이 있는데 , 저자는 숨겨진 입력 ㅡ> 부효과(side-cause) 이라고 했는데 ,보여지는 입력 또한 side-cause 가 생깁니다. 컬렉션이 레퍼런스로 인자로 넘어가서 set 되면 말이죠. 사실 좀  아리까리합니다. 아마 멀티쓰레딩에 관해서는 염두를 안한 글이거나 , set  효과는 아예 배제한것 같으니 , 즉 모든걸 다 담은 글이 아닌점을 글 읽는 분들은 참고하십시요.




함수형 프로그래밍이란 (2편 언어에서 조망)


읽기전에 : 본 글은 블로그에서 자신의 생각을 표현한 글로써, 건조하게 문법을 설명한 글이 아니라서 좀 과격하며 

주관적인 생각도 많이 포함되 있음을 염두하고 읽기 바랍니다.

서두


첫번째 포스트에서 나는 함수형 프로그래밍을 정의했었다. 뭐 교과서적이거나 마케팅적인 관점은 아니었지만 일반 프로그래머에겐 쉽게 이해할 수 있는 상식적인 설명이었다고 본다. 어쨌거나 내가 바라는건 개발자들이 통제불능 상태로 자신의 코드를 몰고가기 전에 어떤 부작용이 있을지 고민해 봤으면 하는 바램이다.

자~!  이제  주변에서 사용되고있는  함수형 언어들에 대해 살펴보자. 

(역주 :   side-cause 와 side-effect  는 둘다 부작용으로 번역했고 그 차이는 part1 에서 참고하시구요. 다만 side-cause  경우 괄호안에 표기했습니다. 부수효과라고 번역도 많이들 하는데 너무 약하다고 봅니다. )

함수형 프로그래밍은 .. 가 아니다.


map 이나  reduce 가 아니다.

모든 함수형 언어에서 저것을 보았더라도, 저것이 언어를 함수형으로 만드는게 아니다.  단지 어떤 시퀀스 요소들에 대해서 작업할때 , 부작용을 없애기위한 노력의 산물일 뿐이다.

(역주: 전 반대로 생각해서 부작용이 없는 코드를 짜야 저런 함수합성요소들을 편히 사용 할 수 있게 됩니다.


람다 (lamda)  함수가 아니다.

일급함수에 대해 언급하는 것을 모든 함수형 언어에서  들었을 것이다. 그러나 그것은 부작용을 피하는 언어를 만드는것을 시작할때 자연스럽게 도출되는 것이다.  도와주는 요소이지 근본은 아니다. 


타입문제가 아니다.

정적 타입 검사방식은 매우 유용한 도구이다. 그러나 그것이 함수형 프로그래밍 (FP) 의 선행 요구사항은 아니다. Lisp 은 오래된 함수형 언어이자, 가장 오래된 동적 언어이다. 

정적 타입들은 매유 유용하게 될수있다. 헤스켈은 그것의 타입 시스템을 부작용을 처리하기위해 아름답게 이용한다.  그러나 함수형 언어를 만들기 위한  재료는 아니다. 

다시한번 강조하자면 !!    함수형 언어는 부작용에 대한 것이다. 


각각의 언어에서 그 의미는 무엇인가?


자바스크립트는 함수형 언어가 아니다.

함수형 언어는 당신이 컨트롤 할수있거나, 할수없는 모든곳에서 부작용이 일어나는것을 제거하는데 도움을 주는 언어이다. 자바스크립트는 이런 기준에 미흡하며  사실 자바스크립트에서 부작용을 조장하는 지점은 쉽게 찾을수있다.

가장 찾기 쉬운 부분은  this이다. 그 숨겨진 input 은 모든 함수에 존재한다.   this  에 존재하는 마법같은 일들은 주로 자신의 의미가 쉽게 바뀐다는데 있는데 , 심지어 자바스크립트 전문가들 조차도 이놈의  this 가  무엇을 가르키고 있는지 추적하는데 쩔쩔 맨다는 점이다.  함수적 관점에서 보면 이 모든 마법적으로 활용되는 일들에서 불쾌한 냄새가 나는듯 하게 느껴진다.

자바스크립트에 함수형 헬퍼 라이브러리들을 로드할수있는데  ( 예를들어 Immutable.js ) , 그것은 자바스크립트를 함수형 스타일로 개발하는데 더욱 쉽게 만들어준다. 물론 언어 그 속성 자체를 바꿀수는 없지만..  


자바는 함수형 언어가 아니다.

자바는 확실히 함수형 언어가 아니다. 자바 1.8 에서의 람다 기능의 추가는 그것에 전혀 영향을 미치지 않는다.  (역주: 람다 와 모나드 (스트림API) 를 통해서 부작용을 해소하려는 시도는 있습니다.) 자바는 함수형 프로그래밍의  반대 방향에 꿋꿋히 서있는다. 자바의 핵심 디자인  설계자는 "코드는 부작용을 일련의 지역화로  다루어야한다 " 라고 말하고있다.  메소드들은 객체의 지역 (local ) 상태를 바꾸거나 의존한다.

사실 자바는 함수형 프로그래밍 (FP) 에 적대적이다. 만약 당신이 자바코드를 부작용없이 작성하려면, (객체의 상태를 바꾸거나 읽지 않는 )  당신은 그쪽 세상에선 형편없는 프로그래머라고 불리게 될것이다. 그게 자바가 쓰여지는 방식이 아니니깐.   당신의 부작용 청정 코드는 static  키워드로 양념될것이고 , 그들은 눈쌀을 찌부리며 당신의  책상을 화장실로 옮겨놓을 것이다.  (역주 : 이부분은 조금 이해안가네요. static 을 남발하면 부작용 청정 코드가 되나요?  static 을  immutable 로 사용한다는 전제를 한거 같습니다.

물론  나는 자바가 나쁘다고 말하는건 아니다.  (흠 , 오케이~그렇게 볼수도 있겠다)  , 그러나 요점은 부작용을 보는 관점에서 전혀 다른 시각을 가지고 있다는 점이다. 자바 경우는  지역화된 부작용은 좋은 코드의 주줏돌이라고 보며 ,  함수형 프로그래밍은 그것들을 악이라고 본다. 

당신은  자바나 FP 이 부작용이라는 문제을 대응하는 관점에 대해 양쪽을  다른 각도로  볼수 있을 것이다. 양쪽 모델은 문제로서 부작용을 인지하며 , 다르게 대응한다. 객체지향의 대답은 " 그들을 '객체' 라는 바운더리 내에 포함한다 ' 이고 반면 함수형의 대답은 ' 그들을 제거한다 ' 이다. 운이 없게도 , 실제 자바는 부작용을 캡슐화하는 시도를 하지 않고 그들을 당연하게 관행으로 바라본다. 만약 상태가 있는 객체의 형식에서  당신이 부작용을 만들지 않는다면, , 당신은 결국 나쁜 자바 프로그래머가 될것이다.  사람들은 static을 남발하는 당신을 해고 할것이다. 

역자추가) 

자바 8 의 stream API 는 i/o 에서 사용되는 inputstream / outputstream 과는 완전 다른것이며 
stream API 는 함수형 세계에서 말하는 모나드 입니다. 그래서 자바에서 함수형 프로그래밍의 멋진 부분을 
가지고 놀수있게 된것이죠.스트림 API 는  순서대로 요소를 처리하는 다양한 방법을 제공하며 런타임 성능 향상에 
좋은 영향을 줄수 있습니다.

참고 : JAVA 8 Stream API 

스칼라는 큰 (역주: 허황된 )  과업을 가지고 있다.

생각해보면 스칼라는 매우 도전적인 제안을 하고있다. 만약 스칼라의 목적이 객체지향과 함수형의 두 세계를 묶는것이라면 , 부작용이라는 렌즈를 통해서 보면  " 부작용 의무화"  와   "부작용 금지" 의 차이에 다리를 놓아서 연결하겠다는 것이다.  그들이  조화롭게 뛰어노는 꽃동산을 만든다는것이 가능할지는 모르겠다. 당신이 스칼라를 사용하는데 ,    map함수를 지원하는 객체들을 만드는것에 두 사상을 통합하여 개발  할수 없을것이다. (역주 : 만약 대중화가 된다면 대부분 스칼라를 가지고 자바처럼 코딩 할 것이라 생각듭니다. 제가 쓴 함수형 프로그래밍의 대중화 될수 없는 이유에서  빈약하지만 얘기했듯이,  저는 함수형 언어의 대중화는 불가능이라  생각합니다. ) 

만약 스칼라가 그런 통합에 성공할것인지 판단하는건 각자의 몫으로 남겨둘것이다. 그러나 만약 내가 스칼라의 마케팅 담당자라면 그들을 통합하는 대신해 스칼라를 점진적으로 자바의 부작용으로부터 벗어나서 순수FP 의 세계로 이동하게 도움주는 것으로 홍보하고 싶다. (역주: 스칼라가 비록 하이브리드지만 FP 로만 사용해야 한다고 생각합니다. C++ 가 하이브리드지만 객체지향언어이듯이..


클로저 (Clojure)

클로저는 부작용에 대해 흥미로운 위치에 자리잡고 있는데  그 언어의 창조자 리치 하키는 "클로저는 대략 80% 정도 함수형 이다 " 라고 말하고있다.  나는 왜 그런지에 대해 명확히 말할수있다고 생각하는데,  시작부터 클로저는 부작용에 대한 하나의 특정 종류를 다루는것으로 디자인되있다.  :  시간   

이것을 살펴보자, 여기에 당신을 위한 자바 유머가 있다:

  • 5 더하기 2는 무엇?
  • 7.
  • 정답.  그럼 5 더하기 3은 ? 
  • 8
  • 땡~!  10 이 정답.  왜냐면 우린 5 을 7 로 바꿨거든 기억해?   (역주:  하나도 안웃겨...

오케이~인정. 뭐 훌륭한 조크는 아니었다. 그러나 포인트는 말이지. 자바왕국에서는 values 가 계속 유지되지 않어. 정당하게 5를 표현하는 어떤것을 가질수도 있는데 함수를 호출함으로서 그게 더이상 5가 아님을 알수있게되지.  (역주 :  어떤 값을 가지고 있는 변수/객체를 함수에 인자로 넘기면, 레퍼런스 값이 복사되어 넘어가므로 내부에서 어떤 행위를 하면 본질이 바뀌어 질수 있음을 말함. Call by Reference value 즉 너무 당연하게 생각해왔던것이 사실 당연한게 아니었던것이다

Integer 경우는 사소한것이고, 커다란 객체로 본다면 그 영향은 크게 증폭된다고 말할수있다. Part 1에서 말한 InboxQueue 를 떠올려보면 InboxQueue 의 상태는 시간에 따라서 달라질수 있는 value 이다. 그래서 결국 시간은 InboxQueue 의 의미에 대한 부작용(side-cause)이라고 말할수있는거지.  

클로저는 그 시간의 부작용 (side-cause) 에 빡세게  촛점을 마추고있다. 시간의 숨겨진 효과 때문에 우리가 저장한 값에 의존할 수 없게 되고, 저장한 값에 의존할 수 없으면 함수의 입력값에도 기댈수 없게 되며, 따라서 우리는 어떠한 것에도 그것이 예상가능하게 혹은 반복적으로 동작할 것이라고 의존할 수 없게 된다는 것이 바로 리치 히키의 통찰이다.  (한주영님 번역으로 수정)

만약  값이 부작용을 가지고있다면,  모든것이 부작용들을 가진다. 만약  값이  순수하지 않다면 우리의 프로그램안에는 어떠한 순수한것도 남아있지 않게 된다. 

그래서 클로저는 시간에 대한 무기를  갖는다. 모든 value 는 디폴트로 불변형 (immutable) 이다. (시간이 지나도 변하지 않음) . 만약  value  를 바꾸길 원할 경우에  클로저는 바뀌지 않는 값에 대한 래퍼를 제공하며 그 래퍼는 무거운 제약들을 갖는다. 

  • 래퍼를 사용해서 값을 바꾸기위해  강제적으로 사전동의 (opt-in)  를  구해야한다. 
  • 일시적이거나 자연스럽게 변형가능한 value 를 만드는것을 할 수 없다. 항상 언어에서 마련한 명시적인 플레그 (부작용에 대한 보호막 ) 를 이용해야한다.   
  • 자신도 모르게 변형가능 값을 소비할수없다. 항상 언어적인 보호장치를 사용해야한다. 
  • 변경가능 값 래퍼를 사용하고 , 다시 돌려 받을때는 불변형이다. 쉽게 시간-의존적인 세계 밖으로 순수한 녀석을 되돌려 받을 수 있다. 

시간 관점으로,  클로저는 매우 훌륭한 함수형 프로그래밍 언어의 예이다. 시간의 부작용에 대해 깊이 있는 적대심을 표현하고 있다. 어디에 있든지 제거하며 (디폴트로)  , 당신을 도와줄것이다. 


하스켈 (Haskell)

만약 클로저가 시간이라는 특성에  반대한다면 ,  하스켈은 평범히 (?)  부작용에 대처하는 느낌이다. 하스켈은 정말 부작용을 싫어라 하는데,  많은 노력을 그것을 컨트롤링하는데 쏟아 부었다. 하스켈이 부작용에 항거하는 흥미로운 방식중 하나는 타입들과 연관된다. 타입시스템 안에 부작용들을 모두 푸쉬 하는데 예를들어 getPerson 함수를 가지고 있다고 치고 ,  하스켈에서는 다음과 같다: 


getPerson :: UUID -> Database Person

"UUID 를 이용하여  Database 의 문맥안에서 Person 을 리턴하라" 라고 읽을 수 있다. 이건 흥미로운데 - 당신은  하스켈 함수의 타입 시그너쳐를  바라 볼 수 있으며,  어떤 부작용을 포함하고 그렇지 않은지에 대해 알수있다.  ( 역주 : 하스켈을 공부하면  알수있겠지...)  그리고 다음과 같이 보장 할 수 있다.  " 이 함수는 파일 시스템에 접근하지 않을겁니다. 왜냐하면 부작용의 종류로서 선언되지 않았어요".   즉 명시적인 타이트한 컨트롤을 한다. 

중요한 포인트는 또 있는데, 함수를 다음처럼 볼수있다.

formatName :: Person -> String

... 그리고 이것이 Person 을 가지고 String 을 리턴하는지 알 수 있다. 그 밖에 다른게 전혀없다. 왜냐하면 만약 부작용이 있다면, 당신은 타입 시그니쳐에 그것들이 가둬지는 것 (역주 : 하스켈에서는 아마 명시적으로 표현되나 보다) 을 확인할수 있기 때문이다.  아마도 가장 흥미로운것은 다음 예인데: 

formatName :: Person -> Database String

이 시그니쳐는 우리에게 formatName 의 이 버전은 데이타베이스 관련 부작용을 포함한다고 말하고 있다.  " what the hell ?? 왜 formatName 이 데이터베이스를 필요로 한거야? "  당신은  " 나는 셋업(set-up) 이나 목아웃(mock-out) 할 예정이야 단지 그 함수에 대해서 테스트 해 볼 건데.. 이 무슨.. "  라고 생각 중인데,  저건 정말 이상하다. 이 함수 시그니처를 보면 , 나는 디자인적으로 먼가 잘못된것이 보인다. 나는 코드를 볼 필요가 없이 썪은 냄새를 맡을 수 있다.  

자바의 함수 시그니쳐와 간단히 비교해보자:

public String formatName(Person person) {..}

어느 하스켈 버전이 저것과 동등한가?  자바의 경우 함수의 몸통을 조사할 필요도 없이, 당신은 저것에 대해 알 도리가 없다. 그것은  데이타베이스에 아마 접근할수도 있고  또는  파일들을 제거하고 리턴할것이며 선임을 빡치게 만들것이다.  그 타입 시그니처는 당신에게  무엇이 작동하는지에 대해 아주 조금  말한다.  

하스켈의 타입 시그니처는 ,  자바와  대조적으로 , 당신에게 디자인을 멋지게 다루는것에 대해 말한다. 그리고 그들은 컴파일러에 의해 체크되기 때문에, 당신이 아는게  맞다고 말한다. 그것은 훌륭한 아키텍처 도구들을 만든다는것 의미한다.  

하스켈은 매우 높은 레벨의 디자인 향기를 표면화 한다.  또한 코딩의 패턴 또한 표면화 한다. 나는 "함수자 (functor) " 라든지 "모나드(monad)" 라는 단어를 이번 포스트에서 제외할것이나  높은 수준의 소프트웨어 패턴들은 높은 레벨의 분석과 함께 시작한다고 말할것이다. 높은 레벨의 분석은 당신이 높은 레벨의 표기법(notation) 을 가질때 더욱더 쉽게 만들어진다.   


파이썬(Python)

근본적인 부작용에 대해 자바를 이용해서 빠르게 살펴보자. 

public String getName() {
  return this.name;
}

어떻게 우리는 이 함수를 순수하게   할까?  this  는 위에 말했다시피 숨겨진 입력이고, 우리가 해야할것은 그것을 매개변수로 올려놓는것이다:


public String getName(Person this) {
  return this.name;
}

지금 getName 는 순수한 함수이다. 기본적으로 파이썬이 이러한 패턴을 채택하고있다는것을 말하려는것인데. 파이썬에서 또한 모든 객체 메소드들은 this  를  숨겨진 첫번째 인자로 가진다.  self: 로 불려질때를 제외하고

def getName(self):
    self.name

명시적인게 암시적인거보다 훨씬 좋다. 정말로~


인자가 없다는건 부작용(side-cause) 의 신호이다. 

당신이 인자가 없는 함수를 볼때마다, 두가지 중 하나는 사실이다.  정확히 동일한 값을 리턴한다. 또는 어디에선가로 부터 입력을 받는다. (즉. 부작용을 가진다) 

예를들어, 이 함수는 틀림없이 항상 동일한 정수를 리턴한다. ( 또는 부작용을 가진다 ) 

public Int foo() {}


리턴 값이 없다는건 부작용(side-effect)의 신호이다

 리턴값이 없는 함수를 볼때마다 , 그건 부작용이 있거나 그것을 호출하는 포인트가 없다는것을 얘기한다.

public void foo(...) {...}

저 함수의 시그니처에 따르면,  이 함수를 호출할 이유가 전혀 없다. 아무것도 당신에게 해주질 않는다. 이것을 호출하는 유일한 이유는 마법적인 부작용을 아주 조용히  일으키기 위해서이다. 


요약 

부작용에 대한 직관적인 인식은 당신이 코딩을 바라보는 방식을 바꿔줄것이다. 사소한 함수에서부터 시스템 아키텍쳐의 전반적인 사항을 조망하는것까지  많은것을 바꿀것이다. 또한 툴이나 테크닉에서 부터 언어를 바라보는 시야 또한 바꾸고 넓힐것이며. 결국 모든것을 바꿀것이다. 

자~ 이제부터 부작용을 죽이는 네팔렘이 되어서,  소프트웨어 대균열에 맞서 보자. 



+ Recent posts