레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JavaScript인 게시물을 표시합니다. 모든 게시물 표시

2018-05-20

2018년 20번째 주

이 포스팅은 그냥 지난 한 주간 읽었던 것들을 정리하는 포스트입니다. 그냥 예전에 봤던 글 중 나중에 필요한데 뭐였는지 기억 안 나는 글들이 있어서 쓰기 시작했습니다.
 보통 하는 일과 관련된 글들이 올라오겠지만 딱히 정해둔 주제는 없고, 그때그때 관심 있었던 것을 읽었기 때문에 지난주에 쓰인 글일 수도 있고 몇 년 전에 쓰인 글일 수도 있습니다.

Polkadot: Vision for a Heterogeneous Multi-chain Framework

Cosmos - A Network of Distributed Ledgers

블록체인이 쏟아져 나오면서 다른 블록체인과 통신을 어떻게 할 수 없을까에 대해 고민하는 사람들이 나왔다.
예를 들어 지금은 Alice의 비트코인과 Bob의 이더리움을 교환하기 위해서는 양자가 신용하는 Ted가 필요하다. Alice는 비트코인을 Bob은 이더리움을 Ted에게 보내고, 양쪽에게 받은 트랜잭션을 확인한 Ted는 Alice와 Bob에게 이더리움과 비트코인을 보내주는 식이다. 지금은 거래소가 이 역할을 해주고 있다. 하지만 trustless를 가정하고 설계된 블록체인에서 거래소는 가장 약한 고리가 된다. 그래서 이 거래소에 해당하는 역할을 블록체인으로 구성하자는 제안이 나왔고, PolkadotCosmos가 대표적이다.

How I targeted the Reddit CEO with Facebook ads to get an interview at Reddit

어떤 사람이 공개된 페이스북 프로필을 이용해서 레딧 CEO를 타겟으로 광고를 했다고 한다. 결국, 10$만에 레딧 CEO에게 광고하는 데 성공했다고 한다. 마케터들은 페이스북이 이렇게 유용하다고 생각할 것이다. 근데 사용자 입장에서 반대로 내 신원이 이 정도로 추적된다는 것인데 이런 것을 감수하고 쓸 정도로 페이스북이 매력적인 서비스인지 이해가 안 된다. 사실 사람들이 개인 정보 보호에 그다지 관심 없는 게 아닌가 싶다.

To Type or Not to Type:Quantifying Detectable Bugs in JavaScript

JavaScriptTypeScriptflow를 사용하여 정적분석을 하는 것 만으로 15%의 버그를 예방할 수 있다는 연구다.

How a Rust upgrade more than tripled the speed of my code

Parity 팀에서 지난 5월 10일에 발표한 Rust 1.26의 128 bit 정수 타입을 이용해서 실제 성능 향상을 확인했다. 이는 이더리움이 256 bit 정수와 512 bit 정수를 많이 사용하기 때문이고, 이더리움 외의 소프트웨어에서는 128 bit 정수를 쓸 일이 없으니 크게 영향 없을 수도 있다.

Sia: Simple Decentralized Storage

Sia는 블록체인을 이용하여 분산 서비스를 구축하자는 프로젝트이다. 분산 스토리지라고 해도 데이터 전체를 블록체인에 올리는 것은 비현실적이기 때문에 블록체인에는 어떤 데이터를 어떤 노드가 가졌는지에 대한 증명과 그에 대한 보상만 블록체인에 기록한다. 결국, 실제 데이터를 원하는 클라이언트에게 서비스하지 않아도 데이터를 가지고 있다는 증명만 하면 보상을 받는다. 이 문제를 oracle problem이라고 하는데 이 문제를 어떻게 풀었는지 궁금해서 찾아봤는데 아직 못 찾았다.

2018-04-01

2018년 13번째 주

 이 포스팅은 그냥 지난 한 주간 읽었던 것들을 정리하는 포스트입니다. 그냥 예전에 봤던 글 중 나중에 필요한데 뭐였는지 기억 안 나는 글들이 있어서 쓰기 시작했습니다.
 보통 하는 일과 관련된 글들이 올라오겠지만 딱히 정해둔 주제는 없고, 그때그때 관심 있었던 것을 읽었기 때문에 지난주에 쓰인 글일 수도 있고 몇 년 전에 쓰인 글일 수도 있습니다.


정규 표현식에서 \d가 의미하는 것이 언어마다 다 다르다고 한다.

언어가 지원하는 문자열이 single byte문자열이면, \d가 [0-9]를 의미하는 것이 맞지만, 유니코드라면 [0-9] 이외의 문자열도 처리할 것을 고려했어야 한다. 그런 의미에서 파이썬 2의 string literal은 유니코드가 아니기 때문에 [0-9]를 처리하는 것이 이상하지 않지만, Java나 JavaScript처럼 유니코드 string literal을 지원하는 언어에서 \d를 [0-9]에만 대응하는 건 조금 안일한 결정이 아니었나 싶다


자바스크립트 디자인 패턴: RORO

RORO는 Receive an Object, Return an Object의 약자로, 이름 그대로 함수에 넘기는 인자와 함수가 넘기는 인자를 object로 하자는 것이다. 함수의 인자로 object를 넘기자는 것은 꽤 옛날부터 있었던 주장이다. 최소한 내가 처음 웹 개발을 했던 2009년경에는 이미 함수의 인자로 객체를 넘기는 패턴이 유행했다. 함수의 인자로 객체를 넘겼을 때의 장점은 두 가지로 정리할 수 있었다.

하나는 default parameter를 구현하기 쉽다는 것이다. EcmaScript 5까지는 default parameter가 없었다. Default parameter를 흉내 내기 위해서 Arguments를 이용해야 했는데, 몇 번째 인자가 무슨 의미를 가지는지 코드에 표현할 방법이 없기에 새 인자를 하나 추가할 때 실수할 확률이 높았다. 이를 object를 이용하면, property 이름이 있기 때문에 Arguments를 이용하는 것보다 읽고 수정하기 쉬운 코드를 만들 수 있었다. 이는 EcmaScript 6에서 default parameter가 추가됐기 때문에 지금은 크게 장점이 아니다.

두 번째 장점은 EcmaScript에 없는 named parameter를 구현할 수 있다는 것이다. EcmaScript에는 named parameter가 없기 때문에 인자 개수 차이에 따라 몇 번째 인자가 무엇을 의미하는지 다르게 해석하는 방법을 많이 사용한다. 하지만 이는 버그를 만들기 쉬운 코드이므로 좋은 방법은 아니다. 이것도 object를 사용하면, 손쉽게 구현할 수 있다.

인자를 객체로 넘기자는 주장은 많이 있었지만, 함수의 리턴 값을 객체로 하자는 주장은 과거에는 그리 흔한 주장이 아니었다. 함수의 종류에 따라 객체를 리턴하는 값이 특정한 객체로 묶기 쉬운 경우도 있었다. 그러나 이 값을 하나의 값이라고 부를지 리턴을 위해 객체로 묶었다고 부를지는 사람마다 다를 것이다.
하지만 EcmaScript 6 이후로는 전혀 관계없는 값을 하나로 묶어서 리턴하는 코드도 자주 보인다. EcmaScript 6에 들어온 destructuring assignment 덕분으로 보인다.


Coding is bad for you

코딩하다 보면 문제가 생길만한 상황이나 너무 사소한 것에 집착하게 되기 때문에 정신 건강에 해롭다는 글.

경험적으로 잘 짠 코드는 예상치 못한 상황에 대비할 수 있도록 짜인 코드였다. 그렇기 때문에 당연히 문제가 생겼을 때를 대비하는 프로그래머가 잘 하는 프로그래머일 것이고, 그들은 사소한 문제가 될만한 상황도 잘 잡아낸다. 이걸 못 하는 사람들은 아무리 빠르게 스펙에 맞게 코드를 짜더라도 절대 잘 한다는 소리를 들을 수 없다.

근데 이게 코딩을 하다 보면 사람이 그렇게 생각하게 되는지 원래 그런 사람들이 코딩을 잘하는 건지는 잘 모르겠다.


Avoid else early return

억지로 single point return을 만들기 위해 if/else를 쓰지 말고, early return이 가능한 지점에서는 최대한 빠르게 return 하자는 글이다. 경험상 single point return이 early return보다 장점을 가지는 경우는 C를 사용할 때밖에 없었다.
Early return이 문제가 되는 경우는 결국 함수 종료 시 특정 로직을 실행해야 하는데, return 문이 많이 있어서 실수로 로직을 빼먹는 경우다. 하지만 요즘 나오는 언어는 대부분 RAII가 되거나, finally block을 지원한다. 따라서 요즘 나온 언어를 사용하면 early return을 사용할 때의 문제점을 언어 차원에서 해결책을 제공하는 것이다.

아쉽게도 C에는 아직 RAII나 finally block에 해당하는 기능이 존재하지 않는다. 그래서 함수가 끝날 때 특정 코드를 실행시킬 방법이 없고, 이런 문제로 C에서는 single point return을 쓰는 게 더 안전할 수 있다. 그래서 지금도 리눅스 커널에서는 예외 처리나 리소스 해제 등을 위해 goto를 이용하여 single point return을 만드는 방법이 권장된다.

하지만 이는 C를 쓸 때의 예외적인 상황이고 대부분의 경우에는 early return이 더 좋다.


LG launches open source version of webOS

LG 스마트 워치나 스마트 티비에 들어가는 webOS의 오픈 소스 버전이 공개됐다는 소식.
webOS는 원래 Palm에서 만든 OS로 리눅스 커널 기반이라는 점에서 안드로이드나 iOS보다 잘 되기를 바랐지만 망한 OS다.
한 5년쯤 전에 LG가 인수했다는 말은 들었는데 LG가 이것저것에 사용해본 듯한데 결과적으로 점유율이 0%인 것을 보면 확실하게 망한 듯하다. 이제 와서 오픈 소스 버전을 공개한다고 하는데 딱히 기대는 안 된다. 오픈 소스라는 게 단순히 소스를 공개한다고 끝나는 것이 아니라 사용자로부터 꾸준한 피드백을 받으며 이루어지는 커뮤니티가 핵심인데, webOS에 피드백을 줄 사용자가 있을지, LG가 그런 커뮤니티를 운영할 능력이 있을지도 의문이다. 솔직히 webOS를 사는데 든 돈이 아까워서 손절하지 못 하고 있는 게 아닐까 싶다.


Key words for use in RFCs to Indicate Requirement Levels

영어를 못 해서 스펙 문서를 작성할 때 어떤 조동사를 써야 할지 헷갈릴 때가 있는데, 그때를 위해 Network Working Group에서 규정한 가이드라인이다.

  • MUST, SHELL, REQUIRED - 반드시 해야 하는 경우
  • MUST NOT, SHELL NOT - 절대 하면 안 되는 경우
  • SHOULD, RECOMMENDED - 근거가 있으면 안 해도 되는 경우
  • SHOULD NOT, NOT RECOMMENDED - 근거가 있으면 해도 되는 경우
  • MAY, OPTIONAL - 하든 안 하든 전체 동작에 문제가 없어야 하는 경우

2018-02-09

[CoffeeScript] 왜 커피스크립트를 사용하지 않는가

 아랫글은 2016년에 썼던 글인데 왜인지 모르게 아직 publish 안 하고 있었다.
 그 사이에 ES2015(ES6)의 변경을 추가 한 커피스크립트2가 나왔다. 하지만 이미 ES2015를 넘어 ES2017도 나왔고, 브라우저들도 ES2016는 네이티브로 지원하고 있기 때문에 앞으로도 커피스크립트를 쓸 일은 없을 것 같아 발견한 김에 publish 한다.


 커피스크립트는 자바스크립트 코드를 간결하게 만드는 것을 목표로 만들어진 언어다. 2009년 첫 버젼을 릴리즈 하였고, 2010년 12월 1.0이 릴리즈 되었다.
 내가 커피스크립트를 처음 썼던 것은 1.0이 릴리즈 된 지 조금 뒤인 2011년 경이었던 것 같다. 지금도 자바스크립트 코드가 다른 언어에 비해 간결하지는 않지만, 당시 자바스크립트 코드는 지금보다도 verbose 하였기 때문에 꽤 애용하였었다. 그러다가 웹 말고 다른 일을 하다 보니 자바스크립트를 사용하지 않게 되었고 자연스럽게 커피스크립트도 안 쓰게 되었다. 그러다가 2014년경 잠시 웹 개발을 하게 되었는데 이때 습관적으로 다시 커피스크립트를 사용하였었다. 하지만 그것도 잠시였고, 그 뒤로는 사용하지 않게 되었다. 더 이상 커피스크립트를 쓰지 않게 된 이유는 크게 2가지였다.

 일단 커피스크립트의 문법은 너무 애매했다. 커피스크립트가 가장 중요하게 생각하는 요소 중 하나는 자바스크립트로 일대일로 매칭되는 것이다. 하지만 처음 커피 스크립트를 보면 자바스크립트 같은 느낌이 전혀 들지 않는다. 이는 커피스크립트 코드에서는 괄호를 거의 사용하지 않기 때문이다. 사실 일부 기능을 제외하고 대부분의 커피스크립트 코드는 적절한 위치에 괄호를 추가하는 것으로 자바스크립트 코드로 변환할 수 있다. 이는 커피스크립트의 설계자가 자바스크립트의 괄호가 자바스크립트 코드를 복잡하게 만든다고 생각했기 때문이다.
 하지만 실제로 사용해보면 이는 딱히 편하지 않다. 물론 괄호가 없기 때문에 타이핑은 많이 줄어든다. 하지만 코드를 작성해본 사람은 알겠지만 적은 타이핑은 딱히 간결한 코드를 만드는데 중요한 요소가 아니다. 게다가 이 정도 타이핑은 사실 좋은 에디터를 쓰면 해결될 문제다. 실제로 커피스크립트를 사용하면 타이핑이 줄어서 드는 이득보다 코드를 읽기 힘들어져서 생기는 문제가 더 크다. 물론 익숙해지면 어느 정도 해결되는 문제지만, 엣지 케이스에 대해서는 꾸준히 밟는 지뢰가 생긴다. 게다가 문제가 발생했을 때 컴파일 에러가 발생하는 것이 아니라 예상치 못했던 방식으로 컴파일돼서 실행되는 것이 더 큰 문제다.

 또한 당시에는 ECMAScript 6(a.k.a. ES6)를 ECMAScript 5로 변환시켜주는 babel이라는 기술이 나오고 있었다. ES6를 사용하면 커피스크립트만큼은 아니지만, 어느 정도 간결한 코드를 사용할 수 있었다. 그리고 ES6에는 이미 커피스크립트의 arrow 문법(-> 대신 =>를 사용하기는 한다)과 class가 추가됐다. 게다가 변수 선언이 암시적으로 이루어지는 커피스크립트의 특성상 ES6에 추가 된 let이나 const같은 장점을 전혀 사용할 수 없다.

 뭐 이런저런 이유로 더 이상 커피스크립트를 사용하지는 않을 것 같다. 하지만 커피스크립트의 노력이 헛되다고 생각하지는 않는다. arrow를 함수로 사용하기 시작한 것도, 자바스크립트에 class를 도입한 것도 내가 알기로는 커피스크립트가 처음이었고, 자바스크립트를 조금 더 쓰기 좋은 언어로 만드는데 커피스크립트가 기여한 바는 결코 작지 않다. 다만, 이미 커피스크립트가 해결하고자 하는 문제는 거의 다 자바스크립트에 반영됐고, 자바스크립트는 다음 문제로 넘어가고 있다. 따라서 앞으로 커피스크립트를 쓸 일은 없을 것 같다.

2017-03-05

JavaScript와 IEEE 754

JavaScript는 표준에서 숫자 타입은 IEEE 754-2008, 64 bit format을 따른다고 명시돼있다. 따라서 숫자 타입의 연산도 IEEE 754-2008을 따를 거로 생각했다. 하지만 ECMAScript 명세에서 NaN이나 Infinity를 포함한 연산에 대해 다른 결과를 내도록 정의한다.

그 대표적인 경우가 1-1이다. IEEE 754-2008는 지수를 계산하는 방법을 3가지 정의한다. 첫 번째는 pown으로 지수가 정수인 경우에 대해 정의돼있고, 두 번째 pow는 밑과 지수가 모든 실수인 경우 사용할 수 있도록 정의돼 있고, 마지막은 powr으로 밑이 0 이상의 실수인 경우에 대해서 정의돼 있다. 우선 첫 번째인 pown는 지수가 가 되지 못하므로 관심 대상이 아니다. 다른 두 지수 함수인 powpowr1에 대해 다른 결과를 내도록 정의한다. pow1을 리턴하고, powrinvalid operator exception을 발생하도록 정의했다. 사실 이는 양쪽 다 이상한 것은 아니다. limx1x1에 수렴하지만 1 자체는 부정이기 때문에 1인 경우와 invalid operator exception이 발생하는 경우 양쪽 모두 말이 되기 때문이다. 그렇다면 IEEE 754-2008은 -1를 어떻게 정의할까?

이 경우 밑이 0보다 작으므로 pow만이 유효한 함수고, 이에 대해서 1을 리턴하도록 정의했다. limx-1x는 발산하고, -1는 부정이기 때문에 이는 수학적으로 올바른 정의가 아니고, IEEE 754에서 어떻게 이렇게 정했는지는 모르겠다. 역사적인 이유이거나 이렇게 할 경우 구현이 편해지기 때문일 것인데 혹시 정확한 이유를 알고 있는 사람이 있으면 알려주기 바란다.

ECMAScript에서는 1-1의 연산에 대해서 NaN을 리턴하도록 정의했다. 이는 ECMAScript는 1997년 만들어졌다. 즉, 아직 IEEE 754-2008이 나오기 전이었고 그 당시는 가 지수인 것에 대해 어떻게 계산해야 하는지 표준이 없었다. 그래서 ±1x일 경우 부정이라고 하는 수학적 성질을 따라 NaN이라고 정하였고, format은 IEEE 754-2008을 쓰기로 한 지금도 NaN을 리턴하도록 정해진 것이다.

또 한 가지 다른 점은 나머지 연산이다. 나머지는 xy로 나눴을 때, x-q*y로 정의된다. 이는 xy가 둘 다 양수일 경우 명확하지만, 둘 중 하나라도 음수가 되면 q를 정하는 다양한 수가 나올 수 있다. IEEE 754-2008은 이에 대해서 단순한 솔루션을 제시한다. round(x / y)q로 삼는 것이다. 이는 간단하게 구현할 수 있지만, 일반적으로 예상하는 나머지가 나오지 않는 경우가 많아 많은 언어에서 float division은 다른 방식으로 정의하여 사용한다. 이는 ECMAScript도 마찬가지다. ECMAScript는 floor(abs(x / y))를 절댓값으로 가지고, xy의 sign이 같으면 양수, 다르면 음수인 값을 q로 삼는다.

2016-08-18

[Web] SpeechSynthesis - TTS API

 SpeechSynthesis는 Web Speech API의 하나로 주어진 텍스트를 소리로 바꿔주는 TTS API이다. SpeechSynthesis 이전에도 TTS 서비스가 있었지만, 이들은 유료이거나 웹에서 사용하기 불편한 경우가 대부분이었는데 SpeechSynthesis의 경우 브라우저에 내장되는 API이므로 무료로 쉽게 사용할 수 있다는 장점이 있다.
 물론 Web Speech API 자체가 아직 draft에 해당하기 때문에 브라우저가 지원해야만 사용 가능하다는 문제는 있지만, 2016년 8월 15일 현재 데스크탑에서는 크롬과 사파리, 안드로이드 기본 브라우저인 크롬, 아이폰의 기본 브라우저인 사파리에 지원하고, 파이어폭스는 9월에 지원할 예정이므로 대부분의 모던 브라우저에서는 사용할 수 있다.

 speechSynthesis는 5개의 함수를 가지고 있다. 그중 4개는 speak, cancel, pause, resume이다. 이를 이용해서 재생할 음성을 추가하거나 취소하거나 일시 정지할 수 있다.  이 중 speak 함수는 SpeechSynthesisUtterance를 인자로 받는다. speak 함수를 호출했을 때, 이미 재생 중인 utterance가 없고 speechSynthesis가 pause 되어 있지 않으면, 요청된 utterance는 즉시 재생된다. 하지만 이미 재생 중인 utterance가 있거나 speechSynthesispause 되어 있다면, utterance는 바로 재생되지 않고 queue에 저장되었다가 후에 재생된다.
 따라서 실제로 언제 재생되는지는 utterance의 콜백을 통해서만 알 수 있다. 이벤트의 종류에 따라서 onstart, onend, onerror, onpause, onresume 등의 콜백을 등록할 수 있다.

 또한, SpeechSynthesisUtterance는 6개의 속성을 가지고 있다.
 첫 번째 속성은 text로 읽을 텍스트를 지정한다. 객체를 생성할 때 생성자에 넘겨줄 수도 있고, 생성한 뒤 text attribute에 값을 할당할 수도 있다.
 두 번째 속성은 lang으로 어떤 언어로 읽을지를 결정해준다. 만약 아무런 값도 지정하지 않으면 기본적으로는 html 태그의 lang 값을 이용한다.
 세 번째 속성은 voice다. voiceSpeechSynthesisVoice 객체를 값으로 설정할 수 있는데, 아무런 값도 할당 안 하면 default voice를 사용하게 된다.
 네 번째 속성은 volume이다. volume은 최소 0에서 최대 1의 값을 가질 수 있다. 아무런 값을 주지 않으면 기본값은 1이 된다.
 다섯 번째 속성인 rate는 소리의 속도를 결정한다. 기본값은 1이고, 값이 커지면 빠른 속도로, 값이 작으면 느린 속도로 발음한다.
 마지막 속성인 pitch는 소리의 전체적인 높이를 결정한다. 기본값은 1이고, 0에서 2 사이의 값을 가질 수 있는데 최저와 최고일때 어떤 음을 가질지는 브라우저의 구현에 따라서 다르다.

 speechSynthesis에 있는 마지막 함수는 getVoices다. 이 함수는 SpeechSynthesis가 사용할 수 있는 SpeechSynthesisVoice 목록을 반환한다. 하지만 처음에는 아무런 값도 없을 수도 있다. 사용할 수 있는 voice 목록이 초기화되면 voiceschanged 이벤트가 호출되는데, 이 이벤트가 호출되어야 getVoices 이벤트가 정상적으로 사용할 수 있는 voice 목록을 리턴한다.  이렇게 가지고 온 SpeechSynthesisVoicename, lang, voiceURI, localService, default 5개의 속성을 가진다. 이때 lang은 해당 목소리가 어느 나라의 언어를 표현하는지를 나타내고, name은 목소리의 이름을 나타낸다. 하지만 이 둘 다 유일할 것이 보장되지 않는다. 어떤 나라의 언어를 표현하는 목소리가 여러 개 있을 수 있고, 같은 이름의 서로 다른 목소리가 존재할 수도 있다. 하지만 현재 안드로이드 크롬에는 버그가 있어서 voice를 설정해도 목소리가 변하지 않고, lang을 설정해야 목소리가 변한다. 따라서 한 언어에 여러 개의 목소리가 있다고 해도 한 가지 목소리밖에 쓰지 못한다.

 SpeechSynthesis는 데스크탑과 모바일 등 다양한 플랫폼에서 사용할 수 있고, 사용하기도 편하다. 하지만 현재 크롬 구현체에는 약간 문제가 있다. 현재는 크롬은 60개 이상의 문자를 말하고 나면 그다음 utterance에 대해서 소리가 끝나도 end 이벤트도 error 이벤트도 발생하지 않는다. 라이브러리의 버그로 보이는데 작년 6월부터 이슈가 나왔던 문제인데 올해 6월 드디어 원인이 나왔다. 가비지 컬렉팅과 관련된 문제로 보이는데, utterance를 전역변수로 보관하고 있으면 문제가 발생하지 않는다. 원인이 나왔으니 곧 고쳐질 것으로 보이지만, 그동안은 utterance를 전역변수에 저장하는 식으로 우회해야 한다.

2016-01-18

Flow vs TypeScript - 왜 나는 아직 타입스크립트를 쓰는가

 나는 개인적으로 동적 타입 언어보다 정적 타입 언어를 선호한다. 아니 동적 타입 언어를 싫어한다. 정확히 말하면 동적 타입 언어이기에 생기는 실수에 의한 버그들과 그로 인해 사고에 걸리는 부하를 싫어한다. 그래서 웹 개발을 몇 년을 했지만 여전히 동적 타입 언어인 자바스크립트로 코딩하는 것은 싫어한다. 그 때문에 어지간히 간단한 일이 아니라면, 자바스크립트를 사용할 때 반드시 다른 툴을 붙여 정적분석을 한다.
 자바스크립트의 정적 분석을 위해 사용되는 도구는 크게 2가지로 나뉜다. 마이크로소프트에서 만든 타입스크립트와 페이스북에서 만든 플로우다. 한때는 구글이 타입스크립트를 기반으로 만든 AtScript라는 것도 있었지만, 이는 다시 TypeScript로 합쳐지면서 사라지게 되었다.

 타입스크립트와 플로우는 둘 다 자바스크립트를 정적 타입 검사가 가능하도록 만들어주었다. 하지만 접근하는 방향은 완전 다른 방향으로 접근하였다.
 우선 타입스크립트는 2012년에 첫 버전이 나왔다. 타입스크립트의 목표는 자바스크립트로 변환되는 더 쓰기 편하고 안전한 언어를 만드는 것이었다. 어디까지나 자바스크립트로 변환되는 언어를 만드는 것이었기에 문법적 기반을 자바스크립트에 두었다. 다시 말해서 타입스크립트의 문법은 자바스크립트 문법의 슈퍼 셋이다. 자바스크립트의 문법을 기반으로 하여 그 위에 추가적인 기능을 더했다. 추가된 기능에는 classenum처럼 사용성을 올리기 위한 기능도 있고, private이나 타입 어노테이션 처럼 안전한 코드를 만들기 위해 추가된 기능도 있다.
 반면에 페이스북의 플로우는 새로운 언어를 정의하지 않는다. 플로우는 ECMAScript 6(a.k.a. ES6)의 타입 검증 도구일 뿐이다. 이미 타입스크립트의 많은 기능이 ES6에 표준으로 들어왔기 때문에 새로운 언어를 만들 필요가 없었다. 플로우는 새로운 언어를 정의하지 않기 때문에 이미 자바스크립트로 작성된 프로그램을 바로 분석할 수 있다. 타입이 모호한 경우에는 어노테이션을 추가하여 타입을 선언할 수도 있다. 이런 경우 바벨 등을 이용해서 어노테이션을 없애야 올바른 자바스크립트 코드가 되는 불편함이 있었지만, 0.4.0 이후부터는 어노테이션을 주석으로 처리할 수도 있다.

 2016년인 지금은 타입스크립트의 기능 대부분이 ES6에 추가되었다. 오히려 지금은 타입스크립트가 ES6나 ES7의 기능을 타입스크립트로 가지고 오고 있다. 게다가 ES6를 ES5로 변환시켜주는 바벨이 널리 사용되고 있고, 플로우가 ES6의 중요한 기능들은 대부분 처리할 수 있다.1) 따라서 더는 ES6 + 플로우를 사용하는 것에 비해 타입스크립트를 사용해서 얻는 장점은 없다.
 게다가 플로우가 타입 추론을 더 잘해준다. 물론 타입스크립트도 타입 추론을 한다. 하지만 함수형 언어들이 많이 사용하는 힌들리-밀러 타입 시스템처럼 추론이 강력하지 않다. 변수를 선언하는 시점에서 타입을 추론할 수 있어야 한다. 추론할 수 없는 변수는 any 타입으로 선언된다.
 반면에 플로우는 그 값이 사용되는 부분의 타입까지 고려하여 타입을 추론해준다. 그를 보여주는 좋은 예가 아래의 코드이다.
 위와 같이 함수를 선언한 경우, 플로우에서는 코드를 사용하는 부분에서 타입 에러를 검사한다. 따라서 length 함수의 인자로 numberboolean을 넣으면, numberboolean이 x가 되기 적절한지, 다시 말해 length 프로퍼티를 가지는지를 확인하기 때문에 잘못된 타입을 사용했다는 것을 알 수 있다.
 하지만 타입스크립트는 length 함수를 선언하는 순간 이 함수의 타입이 any -> any로 결정되기 때문에 length 프로퍼티가 없는 값을 넘겨도 타입 에러인지 알 수 없다. 이런 경우를 막기 위해 타입스크립트에서는 함수를 선언할 때 타입 어노테이션을 붙이는 것을 추천한다. 즉, 위의 length 함수는 아래와 같이 선언하여 사용하는 것이 좋다.

 지금까지 말한 것만 보면 ES6가 나온 지금은 플로우를 쓰는 것이 더 올바른 방식으로 보인다. 하지만 그럼에도 나는 아직 타입스크립트를 사용한다. 단순히 쓰던 것이 익숙해서가 아니다. 그저 플로우가 아직 미완성이기 때문이다. 플로우가 미완성이라는 것은 단순히 아직 1.0이 나오지 않았다는 것만을 말하지 않는다. 플로우는 사용하는 데 중요한 몇 가지 부분이 아직 미완성이다.
 우선 기본 API에 대한 타입 정의가 완전하지 못하다. 우선 DOM API의 타입 선언이 어떻게 되었는지 보자. 타입스크립트는 13,813줄로 선언되어 있지만 플로우의 경우 1,306줄의 파일로 선언되어 있다. bom.jscssdom.js를 합쳐도 2천 줄밖에 되지 않는다. ES6와 관련된 타입은 플로우는 614줄밖에 정의되지 않지만, 타입스크립트는 5,174줄로 정의되어 있다. node.js API는 타입스크립트의 타겟이 아니라서 DefinitelyTyped에 정의된 선언을 사용해야 해서 공정한 게임은 아니지만, 이 경우에도 플로우보다 타입스크립트가 더 상세하게 타입을 정의하고 있다. 플로우도 해당 이슈를 알고 있고 해결하려고 하지만 아직 해결되지 않았고, 내가 보기에 1.0이 나오기 전에 해결되면 다행일 것으로 보인다.
 게다가 아직 써드파티 라이브러리 지원이 완전하지 않다. 플로우는 타입 어노테이션이 없는 함수도 타입을 추론해준다. 따라서 플로우의 타입어노테이션을 사용하지 않은 라이브러리를 사용해도 플로우가 자동으로 타입 추론을 해준다.
하지만 자바스크립트처럼 오버로딩이 섞여 있는 언어를 분석하여 추론하는 경우 타입 추론은 쉽게 틀린다. 그래서 멀쩡한 라이브러리를 타입 에러라고 검사하기도 하고, 라이브러리를 잘못 사용한 경우를 잡지 못하기도 한다. 이런 경우는 라이브러리의 타입을 선언해놓은 인터페이스 파일을 사용하면 된다. 하지만 아직 플로우의 인터페이스 파일은 만들어진 것을 찾기 힘들어서 대부분의 경우 직접 만들어서 써야 한다. 2012년부터 기록을 쌓아서 이제는 1천 개가 넘는 라이브러리의 타입선언을 보유하고 있는 타입스크립트와는 상대되지 않는다.
 이에 대해서 타입스크립트의 타입선언을 읽을 수 있게 하는 방향으로 진행되고 있지만, 아직 완성되지 않았다.
 마지막 문제는 타입 추론의 완성도에 대한 문제다.
 위의 자바스크립트에서 많이 사용되는 object에 프로퍼티를 추가하는 코드이다. 새로운 프로퍼티를 추가하는 것이기 때문에 문제없어야 할 것 같지만, 플로우는 이를 { bar: number } 타입으로 추론하기 때문에 해당 코드를 타입 에러로 처리한다.
 위의 코드는 정반대의 경우라서 문제가 되는 경우이다. {}는 어떤 값이라도 키로 받을 수 있도록 해석하기 때문에 실제로는 obj.foo가 nullable한 ?number 타입이 되어야 하지만 number 타입으로 받아도 타입 에러라고 검사하지 못한다.

 다음 문제는 더 심각하다. 플로우는 리터럴 한 값 그 자체를 타입으로 사용할 수 있다. 이를 이용해서 쉽게 다른 언어의 enum에 해당하는 타입을 만들거나 tagged union을 만들 수 있다. 이는 자바스크립트의 관습적인 사용법에 맞기 때문에 enum타입을 새로 추가한 타입스크립트에 비해서 올바른 접근이라고 생각한다. 하지만 이에 대한 타입 추론이 완벽하지 않다는 것이 문제다.
 위와 같은 코드를 보면 인자로 올 수 있는 "red", "green", "blue"를 전부 처리하였기 때문에 모든 경우를 처리한 것이다. 하지만 플로우는 모든 경우를 처리하였다고 생각하지 않고, undefined가 반환될 수 있다는 에러를 출력한다.
 더 심각한 문제는 위와 같은 경우다. 위의 코드의 무엇이 잘못되었을까? case 문에서 "green"의 스펠링을 "greeen"으로 잘못 사용하였다. color 타입은 "red", "green", "blue"만 가능하므로 이는 있을 수 없는 경우다. 하지만 플로우는 이것을 타입 에러로 검사해주지 못한다. 심지어 작년 10월에 리포트되었는데 아직 아무런 피드백조차 없다.

 이런 예제들은 하나하나 들자면 끝이 없다. 하나하나는 사소한 사례로 보일 수 있지만, 타입 추론이 핵심인 툴에서 타입 추론을 못 하는 부분이 있다는 것은 플로우에 대한 신뢰도와 사용성을 떨어뜨린다.
 그렇다고 앞으로 계속 타입스크립트만을 사용하겠다는 것은 아니다. 앞에서도 말했듯이 설계 자체만을 놓고 보면 더는 타입스크립트가 ES6 + 플로우에비해 얻는 이득은 없다. 따라서 플로우가 기본적인 도구들을 갖추고, 많이 사용되는 패턴들에 대해 안전하게 타입 추론을 하게 된다면 플로우로 넘어갈 것이다. 일단 지금은 이것들이 1.0 릴리즈에는 해결될 것으로 보고 기다리고 있다.


1) ES6의 let/const를 플로우가 처리할 수 있게된게 2015년 9월의 일이다. 2014년 11월 플로우가 처음 공개된 이후 거의 1년 가까이 플로우는 ES6의 기본적인 기능인 스코프 단위 변수 할당을 하지 못했던 것이다.

2016-01-10

[ECMAScript 6] class 선언하기

 JavaScript는 훌륭한 객체 지향 언어다. 하지만 프로토타입 기반 객체지향이라는 독특한 개념과 특유의 verbose한 문법 때문에 다른 언어에서 넘어온 사람들은 쉽게 적응하지 못하였고, 객체 지향스럽지 않은 코드를 작성하였다. 그럼에도 프로토타입은 다른 객체 지향 언어가 제공하는 class에 비해서 더 유연한 확장성 지원하기 때문에 많은 JavaScript 개발자들은 class가 필요 없다는 입장을 고수해왔다.
 하지만 프로토타입이 코드를 verbose 하게 만들고, 가독성을 떨어뜨린다는 주장은 꾸준히 제기되었고, 결국 ECMAScript 6에 드디어 class 키워드가 추가되어 보다 쉽게 객체 지향적 코드를 작성할 수 있게 되었다.  여기서 중요한 것은 class 키워드가 ES6에 추가되었다고 해서 클래스 기반 객체지향이라는 개념이 추가된 것은 아니라는 것이다. ES6도 여전히 프로토타입 기반의 객체 지향 언어이다. class는 프로토타입 기반 객체를 만드는 syntactic sugar일 뿐이다. 즉, 위와 같은 코드는 ES5를 기준으로 보면 아래와 같이 해석된다.1) class 스타일의 간결성과 prototype의 유연성을 동시에 갖기 위한 선택이었다.
1) 완전히 일치하는 것은 아니다. ES5에는 생성자로 쓸 수 없는 함수가 존재하지 않기 때문에 method를 완벽히 재현할 수 없다.

2016-01-09

[ECMAScript 6] property 선언하기

 property란 사용하는 코드는 member data에 접근하듯이 사용하지만 실제로는 함수가 호출되도록 하는 프로그래밍 언어의 기능을 말한다. C#, Python, Ruby 등 객체를 중요시하는 언어에는 대부분 존재하는 기능이며 당연히 기존의 JavaScript에도 있었다. 하지만 Object.defineProperties 함수를 이용해야 해서 코드가 복잡해진다는 문제가 있었다.  ES6에서는 property를 쉽게 선언할 수 있는 문법을 도입하였다. 메소드의 이름 앞에 get이나 set을 붙이는 것만으로 property를 선언할 수 있다.

2016-01-08

[ECMAScript 6] method 선언하기

 ECMAScript 5에는 메소드에 해당하는 개념이 없었다. 그저 함수가 first-class citizen이기 때문에 객체의 멤버변수로 함수를 할당하는 방식으로 메소드를 만들었다.

 ECMAScript 6에는 method를 만들기 위한 문법이 추가되어 메소드를 선언할 수 있게 되었다.

 이는 크게 보면 ES5에서 사용하던 함수를 멤버변수에 할당하는 방식과 다를 것 없다. 하지만 사소한 부분에서 약간 다르다.  메소드는 이름을 가지지만 new를 통해서 객체를 만들어낼 수 없다.

 이는 메소드만이 가지는 특징이다. 일반적인 함수는 모두 new를 통해 객체를 만들어낼 수 있다. 반면에 람다 함수는 new를 통해서 객체를 만들 수 없지만, 이름을 가지지 않는다.

2015-09-18

[ECMAScript6] 성공적인 Promise는 중첩되지 않는다.

 ES6 Promise에는 독특한 특징이 있는데, 지난번 글에서는 설명할 타이밍을 잡지 못해서 그냥 넘어갔었다. 이번에 그 특징에 관해 설명하도록 하겠다.

 전에 모나드에 관해서 설명하면서 모나드의 가장 기본적인 operator 중 하나인 bind operatorM[T] 타입의 모나드가 T 타입의 인자를 받아서 M[U] 타입의 값을 리턴하는 함수를 인자로 받아서 M[U] 타입의 모나드로 타입을 진행시킨다1)고 하였다. 하지만 ES6 Promisethen 함수에 관해서 설명하면서 then 함수가 받는 콜백이 값을 리턴하면 resolved 된 Promise가 리턴되고, 값을 throw 하면 rejected 된 Promise가 리턴된다고 하였다. 즉, then 함수만으로는 모나드를 리턴하는 함수를 통해서 타입을 전진시키는 bind operator를 구현할 수 없으므로 완전한 모나드를 구현하지 못한다. 그렇다면 ES6의 Promise는 어떻게 Promise를 전진시킬까?

 간단하다. 그냥 then 함수가 인자로 받는 콜백은 Promise를 리턴해도 된다.

 사실 Promise가 모나드라는 것을 생각하면, 이쪽이 올바른 사용 법이다. 하지만 ES6뿐 아니라 다른 모나드 구현체에서도 bind operator뿐 아니라, 모나드가 아닌 값을 리턴하는 함수. 즉, (M[T], T => U) => M[U]에 해당하는 함수도 구현한다. 이는 사실 내부적으로 unit operator와 bind operator를 호출하기 때문에 굳이 필요한 함수는 아니다. 그러함에도 이 함수가 존재하는 이유는 실제로 이 구현을 사용하는 경우가 일반적인 bind operator를 사용하는 경우보다 많아서 사용자의 편의를 위해서 제공되는 것일 뿐이다.
 그래도 보통은 둘을 같은 이름의 함수로 구현하지는 않고, 다른 이름의 함수로 구현한다. 그렇지만 ES6에서는 then 함수가 두 가지 일을 한다. 동적 타입 언어의 특징을 최대한 활용한 것이다.

 하지만 then 함수처럼 콜백 함수가 리턴하는 것이 Promise인지 아닌지에 따라서 동작이 달라지면 실수하기 쉽다. 이것을 ES6는 애초에 Promise가 중첩되지 못하도록 함으로써 동작에 일관성을 취했다. 즉, PromisePromise를 값으로 지니지 못한다.

 Promise가 중첩되지 못하게 하는 역할은 Promise.resolve 함수가 한다. resolve 함수의 스펙을 보면 resolve 함수는 받은 인자가 Promise이고 같은 생성자를 가진다면, 받은 인자 그 자체를 리턴한다고 되어 있다.2) then 함수는 내부적으로 실행된 결괏값을 이용하여 resolve 함수를 호출한 뒤 그 결괏값인 Promise를 돌려준다.


 resolve 함수는 then 함수뿐이 아니라 Promise.all 함수에서도 사용된다. 지난번에 all 함수를 설명할 때는 아직 resolve 함수에 대해서 제대로 설명하지 않아서, Promise의 iterable을 받아 하나의 Promise로 zip 한다고 하였다. 하지만 all 함수도 내부적으로 아이템들을 전부 resolve 함수에 넘기기 때문에, iterable에 Promise가 아닌 다른 값을 넣어서 호출할 수 있다.


1) 말로 풀어쓰려니 복잡한데, 기호로 표현하면 아래와 같다.
(M[T], T => M[U]) => M[U]
2) 스펙 문서 3번 스텝

2015-09-11

[ECMAScript 6] Promise - 비동기 코드 작성하기

 모든 언어가 마찬가지겠지만, 기존의 JavaScript에서는 비동기적 코드를 작성하고 관리하는 것은 크게 어려운 일이었다. node.js에서는 콜백을 이용하는 방식을 사용했지만, 이는 콜백 헬이라는 새로운 문제를 만들어냈다. 이를 해결하기 위해 step이나 async 같은 다양한 라이브러리가 나왔지만 이런 라이브러리로도 콜백 방식이 가지는 복잡도는 해결하지 못했고, 여전히 비동기 코드를 작성하는 것은 어려운 문제였다. 그래서 ECMAScript 6에서는 비동기 코드를 쉽게 작성할 수 있도록 Promise를 표준 라이브러리에 도입하였다. Promise는 그 이름에서도 알 수 있듯이 비동기적인 코드를 작성할 수 있도록 도와주는 promise monad의 일종이다.

 Promise는 기본적으로 생성자를 통해서 만들어진다.

 이렇게 생성된 Promise는 pending state가 된다. pending state는 아무 값도 가지지 않은 상태다. pending인 Promise는 후에 resolved state(혹은 fulfill state)가 되거나 rejected state가 될 수 있지만, 이 상태로는 아무것도 할 수 없다.

 Promise의 상태를 바꾸기 위해서는 콜백 함수를 이용해야 한다. Promise의 생성자는 한 개의 콜백 함수를 받는다. 이 콜백은 executor라고 불리는데, Promise 객체를 생성하는 중에 호출된다. executor가 호출될 때는 2개의 함수가 인자로 넘어간다. 첫 번째는 resolver라고 불리고, 두 번째는 rejecter라고 불린다. pending state인 Promiseresolver가 호출되면 이 Promise는 resolved state가 되고, resolver의 인자를 값으로 지닌다. 반대로 pending state인 Promiserejecter이 호출되었다면 이 Promise는 rejected state가 되고, rejecter의 인자를 Promise가 reject 된 이유로 가지게 된다. 중요한 점은 pending state인 Promise만 resolve 하거나 reject 할 수 있고, 이미 resolved 되었거나 rejected 된 Promise는 다시 resolve 하거나 reject할 수 없다.

 혹은 함수를 실행시키지 않고, 이미 결정된 값을 가지고 콜백 없이 Promise를 만드는 방법도 있다. resolvereject 함수를 이용하는 것이다. resolve 함수를 이용하면 resolved 된 Promise가 만들어지고, reject 함수를 이용하면 reject 된 Promise가 만들어진다.

 이렇게 생성된 Promise는 2개의 멤버 함수를 가진다. 우선 기본적인 멤버 함수는 then()이라는 함수다. then()은 promise monad의 map과 map_error operation을 합쳐놓은 함수이다. then()은 2개의 콜백을 받는다.


 첫 번째 받은 콜백은 Promise가 resolved일 때 호출되는 함수이고, 두 번째 콜백은 Promise가 rejected일 때 호출되는 함수이다.
 정확히는 상태가 변하면 바로 호출되는 것은 아니다. 만약 then() 함수가 호출될 때 Promise가 pending 되어 있었다면 상태가 변할 때 job queue에 추가되고, 상태가 resolved나 rejected였다면 그 즉시 job queue에 추가되었다가, job queue에서 다음 일을 꺼내질 때 실행된다.
 이때 콜백 함수는 값을 하나 인자로 받는데, 첫 번째 콜백의 경우 Promise가 resolve 되었을 때의 값을, 인자로 받고, 두 번째 콜백은 Promise가 reject 된 이유를 받는다.


 then method는 Promise를 리턴하기 때문에 체이닝 할 수도 있다.
 이때 반환되는 Promise가 가지는 값은 실행되는 콜백의 결과에 따라 달라진다. 만약 콜백 함수가 정상적으로 실행되고 값을 반환했다면, 리턴되는 Promise는 함수의 결괏값을 resolve 한 값이 리턴되는 Promise의 값이 된다. 반대로 만약 콜백이 에러를 throw 했으면 throw 한 값을 reason으로 하는 rejected state인 Promise가 리턴된다.


 하지만 then() 함수만 있으면 rejected 된 Promise에 대해서만 값을 진행시키고 싶을 때 약간 불편하다. 그래서 catch 함수가 존재한다. Promise.prototype.catch(onRejected) method는 Promise.prototype.then(undefined, onRejected)와 완전히 같은 동작을 한다. 따라서 아래의 코드의 promise1promise2는 같은 의미의 Promise가 된다.


 ECMAScript 6의 Promise는 여러 개의 Promise를 join 할 수 있는 Promise.all이라는 함수도 제공한다. all() 함수는 인자로 iterable 한 ArraySet 같은 것을 받는다. 그리고 받은 iterable의 모든 값이 resolved 되면, 그 값들이 resolve 된 값의 배열을 리턴한다. 이를 통해서 여러 개의 독립적으로 돌아가는 비동기적인 코드의 실행 결과를 하나로 모을 수 있다.


 혹은 Promise.race() 함수를 이용해서 여러 Promise 중에서 먼저 완료된 Promise를 얻어올 수도 있다.

 사실 기존에도 JavaScript를 위해 Promise를 제공하는 라이브러리는 많이 있었다. node.js가 나오기 전에 유행했던 jQuerydeferred object도 사실상 Promise monad의 일종이었고, node.js나 브라우저 양쪽에서 돌아가도록 pure JavaScript로 작성된 Qbluebird 같은 라이브러리도 존재했다. 하지만 이는 다들 독립적인 동기로 작성된 것으로 서로 간에 API가 호환되지 않아 사용법이 전부 달랐다. 하지만 이제 표준 라이브러리에 Promise가 들어왔으므로 그냥 표준 라이브러리에서 제공하는 Promise를 사용하면 된다.

2015-08-25

[ECMAScript 6] block 안에서 함수 만들기

 JavaScript 함수 선언의 가장 큰 특징은 함수의 선언 위치에 상관없이 언제나 코드의 가장 위에서 함수를 선언한 것처럼 코드가 실행된다는 것이다. 따라서 아래와 같은 코드는

 사실은 아래와 같은 코드라고 보아도 된다.

 이를 function hoisting이라고 한다. 이 덕분에 함수 선언문보다 앞에서 함수를 사용할 수 있다. 하지만 함수 선언은 언제나 스코프의 가장 윗부분으로 hoisting 된다. 따라서 함수 안에서 선언된 함수는 함수 내에서 언제나 같은 함수를 의미했고, 특정 block 안에서는 다른 함수를 의미하도록 사용할 수 없었다.

 하지만 ECMAScript 6에서는 block 단위의 함수 선언을 허용한다.

 즉, 위와 같이 if block 안에서만 다른 값을 의미하도록 하는 것이 가능하다.

 하지만 아쉽게도 이는 아직 대부분 브라우저나 node.js에서는 구현되지 않았다. 따라서 블록 단위 함수 선언을 사용하려면 babel.js를 사용해야 한다.

2015-08-22

[ECMAScript 6] 함수 이름 가져오기

 자바스크립트는 두 가지 방식으로 함수를 선언할 수 있다.
 평범하게 방법은 함수를 선언하여 사용할 수도 있고, 익명함수를 만들어 사용할 수도 있다.

 ECMAScript 5까지는 어떻게 만들어지든 둘 사이에는 차이가 없었다. 만들 수 있는 위치나, 함수의 선언 및 할당이 실제로 이뤄지는 위치가 다르기는 하지만, 어쨌든 만들어지고 난 다음에 둘은 아무런 차이가 없었다.

 위와 같은 코드는 사실 아래와 같은 코드에 syntax sugar일 뿐이다.


 하지만 ECMAScript 6 이후로 둘은 name property라는 다른 점을 가진다. 첫 번째 방식으로 만든 함수는 named function이라고 불리며 name이라는 프로퍼티를 가진다. 반면에 두 번째 방식으로 만들어진 함수는 anonymous function이라고 불리며 길이가 0인 문자열("")을 name property로 가진다.

2015-07-19

[ECMAScript 6] const - 상수 선언하기

 constlet과 같이 ECMAScript 6에 도입된 block 단위 상수 선언문이다.
 중복으로 선언할 수 없고, 선언 전에 사용할 수 없다는 것은 let과 같다. 거기에 const는 추가적인 제약이 더 붙는다.

 우선 const로 선언된 이름에는 값을 재할당할 수 없다. 이는 문법적으로 에러로 처리한다. 따라서 const에 값을 할당하는 구문을 실행할 때 발생하는 것이 아니라 const에 값을 할당하는 구문이 있는 함수가 선언될 때 에러가 발생한다.

 또한, const를 이용해서 상수를 선언할 때는 언제나 값을 초기화해주어야 한다. const로 선언된 상수에 값을 할당하지 못한다는 것을 생각하면, 당연한 일이다. 이 또한 문법 에러로, 초기화하지 않는 const를 선언할 때가 아니라, 선언하는 구문이 있는 함수를 선언할 때 에러가 발생한다.

 하지만 const도 상수 선언을 위한 완벽한 해결책은 아니다. const로 선언한 상수에는 값을 재할당할 수 없지만, 상수임에도 불구하고 값을 변경시킬 수 있기 때문이다. const로 선언한 상수는 어디까지나 값의 재할당을 막을 뿐, 그 값을 보호해주지 않는다.

 const가 완벽한 해결책인 것은 아니지만, 한계를 알고 적절하게 사용하면 좀 더 안정적이고 가독성 있는 코드를 작성할 수 있다.

2015-07-18

[ECMAScript 6] let - block 단위 스코프

 ECMAScript 6에서는 기존의 function scope였던 var 이외에 let이라는 block scope 변수 선언을 지원한다. let을 통한 변수 선언은, var를 통한 변수 선언과 다르게 hoisting 하지 않는다. 즉, 변수가 선언된 이후부터 변수가 유효하고 그전에는 해당 변수를 사용할 수 없다.


 hoisting을 없앤 것뿐 아니라 그 외의 실수하기 쉬운 부분을 에러로 처리하여 좀 더 안전한 코드를 작성할 수 있도록 하였다.

 예를 들면, ECMAScript 5에서는 hoisting 된 변수를 실수로 선언 전에 사용할 경우 그 변수는 undefined가 된다. 하지만 let을 사용하면, 변수를 hositing 하지 않을 뿐 아니라, 변수를 선언한 블록 안에서 선언 전에 해당하는 이름을 사용하는 것을 에러로 처리한다.

 또한, 이전에는 같은 스코프, 다시 말해서 같은 함수 안에서 변수의 선언문이 여러 개 있는 것이 정상적인 구문으로 처리되었다. 하지만 let을 사용한다면 같은 스코프에서 중복으로 선언하는 것이 에러로 처리된다.

 아쉬운 점은 위와 같은 에러가 함수의 선언에서 발생하는 것이 아니라, 실제로 그 구문을 실행할 때 발생한다는 것이다. 따라서 여전히 높은 커버리지의 테스트를 작성해야 안전한 코드라고 보장할 수 있다. 하지만 예전처럼 예상하지 못한 undefined가 나와서 문제가 발생한 부분이 아닌 다른 곳부터 추적해가야 할 일은 없어졌다.

 또 다른 문제는 babel에서 다르게 동작한다는 것이다. 파이어폭스나 크롬 등 모던 브라우저나 io.js는 ECMAScript 6를 지원하지만, 오래된 버전의 IE나 node.js 등에서는 아직 let을 지원하지 않는다. 그런 환경에서는 babel을 사용해야 하는데, babel에서는 중복된 선언이나, 선언 전에 사용하는 것을 에러로 처리하지 않는다.
 하지만 조만간 node.js에서도 let을 지원할 것이고, 오래된 버전의 IE도 없어지고 있으니 이런 문제는 곧 해결될 것이다.

2015-07-14

[JavaScript] undefined 이해하기

 JavaScript에는 3가지 undefined가 존재한다.

타입으로서의 undefined

 우선 undefined는 타입이다.

 ECMA Script 5까지는 다음과 같은 6가지1) Built-in type이 있었다.
  1. number
  2. boolean
  3. string
  4. object
  5. null
  6. undefined
 undefined는 그중 하나다.

값으로서의 undefined

 undefined는 undefined 타입의 유일한 값이다.

 우선 값이 할당되지 않은 변수 혹은 값이 할당되지 않은 프로퍼티는 undefined가 된다.

 또한, 리턴문이 없는 함수나 리턴하는 값이 없는 리턴문으로 끝나는 함수의 실행 결과도 undefined가 된다.

 혹은 전역 프로퍼티인 undefined를 통해서 undefined 값을 얻을 수 있다.

전역 프로퍼티 undefined

 global context에는 undefined라는 이름의 프로퍼티가 설정되어 있다. 이 undefined는 undefined인 값을 가진다.
 ECMA Script 5 이후로 이 프로퍼티는 non-configurable이고, non-writable로 설정되어 있다. 따라서 변경할 수 없다.



1) ECMA Script 6에는 Symbol type이 추가되었다.

2015-07-07

[npm] publish 하기 전에 테스트하기


 npm publish라는 명령어를 통해 내가 만든 라이브러리를 npm을 통해 배포할 수 있다.

 보통의 경우라면 문제없다. 하지만 TypeScriptCoffeeScript를 이용하여 컴파일된 라이브러리를 배포하거나, webpack같은 것을 이용해서 라이브러리들을 패킹해서 배포할 경우 npm publish를 하는 것은 마음 놓고 할 수 있는 작업이 아니다.
 배포 전에 명령어를 수행하기 위해 prepublish에 스크립트를 저장하거나, npmignore에 배포하지 않을 파일들을 추가하거나 하는데, 이런 것들이 제대로 되어 있는지 실제 npm에 올리기 전에는 알 수 없기 때문이다.

 그럴 때 사용하기 좋은 커맨드가 npm pack이다. npm pack을 이용하면, 제대로 된 파일들을 배포할지 확인할 수 있다. npm pack을 실행하면 prepublish를 실행시키고, npmignore에 들어있는 파일들이 빠진 파일들이 {라이브러리 이름}-{버젼}.tgz 라는 이름의 압축파일이 만들어진다.

 그러면 해당 라이브러리를 사용하는 프로젝트를 만들고, npm install {압축파일 경로}를 실행하면, 실제로 publish된 라이브러리를 설치한 것처럼 라이브러리를 설치하여 테스트할 수 있다.

2015-07-05

sfuture - JavaScript에서 concurrent한 프로그램 작성하기

 sfuture는 JavaScript에서 사용할 수 있는 컨커런트한 프로그램을 쉽게 작성할 수 있도록 도와주는 라이브러리다. 이름에서 알 수 있듯이, Scala의 Future를 JavaScript로 포팅한 것으로, 내부적으로 ECMA Script 6 promise를 사용하고 있어서, promise가 구현된 환경(node.js 0.12.0 이상, 대부분의 모던 브라우저)에서는 아무런 디펜던시 없이 바로 사용할 수 있다.

 만들게 된 이유는, 전에 rhino engine을 이용해서 Scala로 작성한 beyond라는 게임 서버 엔진이 있었는데, 여러 가지 문제가 있어서 이것을 node.js로 포팅하게 되면서 필요하게 되었다.
 Rhino를 사용할 때는 Java class를 그대로 재사용할 수 있었기에 인터페이스만 수정하는 수준이면 가능했는데, node.js로 포팅해오면서 그럴 수 없게 되었다. 처음에는 Future를 포팅해올지, async등 기존의 라이브러리들을 이용하도록 할지 고민했다.
 하지만 async를 실제로 사용을 해보니, 이것도 결국 콜백 헬을 없앨 수 있는 건 아니고, 오히려 코드가 길어질수록, 가독성만 떨어뜨리는 느낌이라서 결국 새로 구현하였다.

 구현은 타입스크립트로 되어 있지만, publish 전에 컴파일하여 자바스크립트 파일만 배포한다. 원본 소스를 보고 싶으면, 깃헙 리파지토리를 보길 바란다.

2015-06-26

타입스크립트의 단점

 지난번 글에서 너무 타입스크립트를 사용하면서 얻게 되는 장점만 말한 것 같아서 이번 글에서는 타입스크립트를 사용하면서 맞게 되는 단점들을 말해보도록 하겠다.

 타입스크립트의 단점은 명확하다. 타입스크립트와 자바스크립트를 섞어서 쓸 수 있다는 점이다.

 타입스크립트로 컴파일한 코드는 자바스크립트가 되기 때문에 타입스크립트로 작성한 모듈을 자바스크립트에서 불러올 수 있다.
 하지만 이렇게 사용한다면, 지난번 글에서 말한 타입스크립트를 사용하는 장점 중 하나인 타입 체크를 위한 verbose 한 코드를 작성하지 않아도 되는 장점이 사라진다. 자바스크립트에서 사용될 것을 가정하고 코드를 작성할 경우는 여전히 verbose 한 타입 체크 코드를 작성해야 한다. 이것은 내가 자바스크립트 코드를 타입스크립트를 포팅하면서 딱히 좋은 점을 느끼지 못했던 이유이기도 하다.
 하지만 이는 자바스크립트를 사용했다면 언제나 발생했을 문제가 타입스크립트를 사용할 때 다시 발생하는 것뿐이다. 자바스크립트를 사용해야 하는 환경에서 타입스크립트를 사용하지 않을 이유는 되지 않는다.

 이번에는 반대로 타입스크립트를 사용하면서 자바스크립트로 작성된 모듈을 불러오는 경우를 보자. 타입스크립트에서는 자바스크립트의 모듈을 그대로 가져다 쓸 수 있다. 덕분에 타입스크립트 생태계는 크게 노력하지 않고 자바스크립트의 생태계를 흡수할 수 있었다. 하지만 이것은 동시에 단점이 되기도 한다. 자바스크립트로 작성된 모듈은 타입 추론을 할 수 없어서 모든 API가 any 타입이 되고, 결국 이 부분이 unsafe 한 부분이 되기 때문이다.

 이런 문제를 해결할 수 있도록 타입스크립트는 모듈의 타입만 분리해서 선언하는 선언 파일을 사용할 수 있게 해놓았다. 선언 파일과 함께 사용하면 자바스크립트로 작성된 모듈을 사용할 때도 잘못된 타입을 사용하면 타입 에러를 내준다.
 물론 선언 파일을 만드는 것도 비용이다. 하지만 이는 큰 문제가 되지 않는다. 유명한 라이브러리에 대해서는 이미 DefinitelyTyped에 많은 선언 파일이 만들어져 있다. DefinitelyTyped에 만들어지지 않은 라이브러리도 있고, 버전이 맞지 않는 경우도 있다. 이에 대해서는 직접 만들거나 수정해서 사용해야 한다. 하지만 이는 큰 문제가 되지 않는다. 어차피 외부 모듈을 사용하려면 어떤 함수가 어떤 인자를 통해서 실행되고 어떤 값을 리턴하는지 파악을 해야 한다. 그렇지 않으면 (특히 자바스크립트같이 동적 타입의 언어들에서는) 무언가 문제가 발생하기 십상이다. 따라서 그저 파악한 API의 시그니쳐를 코드로 옮겨 적으면 된다.


 다시 한 번 강조하지만, 타입스크립트의 단점은 다른 정적 타입 언어들보다 불안한 부분들일 뿐이다. 자바스크립트보다 못하는 점은 없다. 타입어노테이션이나 선언 파일이라는 약간의 비용을 들여서 타입 세이프한 코드를 만든다는 큰 이득이 있어서, 자바스크립트를 사용해야만 하는 환경에서 타입스크립트를 사용하지 않을 이유는 전혀 없다.
 그래서 나는 앞으로도 자바스크립트를 사용할 것이다. 아니 타입스크립트 대신 Google의 AtScript나 Facebook의 flow를 사용할 수는 있겠지만, 정적인 타입 분석이 붙지 않은 상태의 자바스크립트를 이용해 코딩할 생각은 없다.

2015-06-22

TypeScript와 함께 한 4개월

 내가 타입스크립트를 처음 쓰게 된 것은 올해 3월이었다. 당시에 알바를 하고 있던 회사에서 작성하던 서버 사이드 자바스크립트 코드의 안정성을 향상하기 위해 타입스크립트로 포팅하는 일을 시작하였고, 당시 그 팀의 구성원은 전원 너무 바빴기 때문에 다른 일을 하던 내가 불려가서 포팅하게 되었다.

 사실 처음에 포팅을 하기로 했을 때는 흥미로운 일이라고 생각하지만, 딱히 의미 있는 일이라고는 생각하지 않았었다. 당시 코드는 이미 뼈대에 해당하는 부분이 대부분 완성되어 있었고, 그 대부분은 이미 타입의 개념이 없이 짜인 코드였었다. 그래서 단순히 타입스크립트로 옮겨도 별 이득이 없을 것이고, 완벽하게 포팅하는데 들어가는 노력에 대비해서 안정성을 확보할 수 있을지에 대해 확신이 없어서였다.

 그래도 돈을 받고 하기로 한 일이었으므로 작업은 시작하였다. 우선 이미 구현되어있는 코드가 너무 많았기 때문에 초반에는 손에 닿는 파일부터 타입스크립트로 변환하며 작업을 하였다. 그 뒤로 한 3개월은 코드를 바꾸는 일만 했다.

 하지만 이때까지는 타입스크립트의 장점을 딱히 느낄 수 없었다. 변환하는 과정에서 몇몇 버그를 잡았지만, 이미 타입이 중요한 부분은 underscore.js를 이용해서 타입체크를 하고 있었기에 타입스크립트가 추가로 해주는 일이 거의 없었다. 변환하는 과정에서 몇몇 버그를 발견하기는 했지만, 충분한 유닛 테스트로 잡을 수 있는 버그들이었기에 딱히 타입스크립트를 사용해야 한다고 느끼지 못했다. 오히려 리팩토링에 너무 많은 시간이 들었기 때문에 비용 대비 효용이라는 측면에서 비효율적이라고 느꼈다. 그러다가 타입스크립트에 대한 인식이 바뀌게 된 것은 다음 프로젝트를 시작하면서부터였다.

 다음 프로젝트를 시작하게 된 것은 지난 5월 말이었으니 거의 한 달 정도 전이다. 하게 된 일은 기존의 스칼라로 작성되었던 beyond 프레임워크node.js에서 돌도록 포팅한 beyond.ts를 만드는 것이었는데, 이번 프로젝트는 처음부터 타입스크립트를 이용해서 작성하였다.

 타입스크립트를 제대로 이용하면서 느껴진 타입스크립트의 장점은 크게 4가지이다.

 우선 자바스크립트를 이용할 때 귀찮은 부분인 타입 체크하는 코드나 테스트를 작성하지 않아도 된다는 것이다. 자바스크립트뿐 아니라 파이썬이나 루비 같은 동적 타입 언어들도 가지는 문제인데 자바스크립트는 타입점검을 하거나 타입을 테스트하기 위해 코드가 verbose 해지기 쉬운데, 타입스크립트를 사용하면 이럴 이유가 전혀 없다.

 두 번째로 안심하고 코딩할 수 있다. 기존에 자바스크립트를 사용할 때는 장황한 테스트를 작성하여 높은 커버리지를 유지하여야 했다. 그렇지 않으면 사소한 실수건 의도적인 수정이었건 간에 코드를 잘못 변경하여 문제가 생기는 경우가 가끔 발생하는데, 타입스크립트는 이런 문제들을 컴파일 타임에 점검해주기 때문에 사전에 막을 수 있었다.

 세 번째로 ES6의 기능들을 사용할 수 있었다. 사실 이건 타입스크립트의 장점이라고 하기는 뭐하지만, 타입스크립트는 대부분의 ES6를 ES5나 ES3로 변환시켜준다. 덕분에 fat arrow, class, const와 let, 다양한 parameter 등 많은 기능을 사용할 수 있다.

 마지막으로 내가 사용하는 라이브러리를 더 쉽게 파악할 수 있다. TypeScript에서는 기존의 JavaScript로 작성된 npm 라이브러리를 그대로 사용할 수 있다. 대신에 타입 추론을 해주기 위해서 선언 파일이 있어야 한다. 하지만 사용하는 모든 모듈에 대해서 선언 파일을 직접 만들 필요는 없고, DefinitelyTyped를 통해서 다른 사람들이 이미 구현해둔 파일을 받을 수 있다. 이것은 사용할 API를 이해하는 데 꽤 많은 도움을 준다.

 물론 타입스크립트에 단점이 없는 것은 아니다. 하지만 타입스크립트를 사용하여 얻을 수 있는 장점이 더 크다고 생각된다. 정확히는 타입스크립트를 사용함으로 인해 들어가는 비용에 비해 타입스크립트를 통해 얻을 수 있는 이득이 훨씬 크다고 생각한다.
 그래서 다음에도 자바스크립트를 사용할 일이 생긴다면, 타입스크립트를 사용할 것이다. 아니 타입스크립트가 아닌 Google의 AtScript나 Facebook의 flow를 사용하게 될 수는 있지만, 최소한 자바스크립트를 동적 타입 언어인 그대로 사용하지는 않을 것이다.