Web API (Wac ? TIzen ?) 와 CPS (Continuation-passing style)

주말에 지인들과 예기를 나누다가 node.js 의 CPS(Continuation-passing style) 문제에 대해 예기가 나왔는데요.

이 CPS는 Ajax가 자바스크립트에서 쓰인 이후로 자바스크립트가 인기를 끌게 한 이유 이기도 하고,

자바스크립트를 처음 쓰는 사람이 자바스크립트에 욕을 하게 만드는 이유 이기도 합니다.

전 저 대화를 하면서, 저희 web api를 사용하면서 복잡도가 올라가는 이유 또한 CPS 때문이라고 생각 했는데요.

이 메일에서는 이 복잡도가 올라가는 CPS 방식을 어떻게 벗어날 수 있는지에 대해 말해보고, 의견을 물어보려고 합니다.

CPS라는건 caller에게 return할 값을 caller가 넘겨준 callback을 통해 하는 방식을 말합니다.

// normal function
function id(x) {
    return x ;
}

// CPS function
function id(x,cc) {
    cc(x) ;
}

위 코드와 같은데요. 리턴으로 콜백을 사용하는 방식이 바로 CPS 입니다.

CPS가 문제인 이유는 복잡도 때문인데요,

자바스크립트에서 CPS 방식이 많이 쓰인 이유는 자바스크립트가 단일 스레드만 지원하는 언어이기 때문입니다.

Ajax 를 쓰던 시절에 XMLHttpRequest에서 데이터를 가져오는 간단하게 다음 fetch 함수와 같이 2가지 방법이 있었습니다.

// blocking
varsomeName = fetch("./1031/name") ;
document.write ("someName: "+ someName +"<br>") ;

// non blocking
fetch("./1030/name",function(name) {
    document.getElementById("name").innerHTML = name ;
}) ;

전자의 blocking 방식은 간단해 보이지만, 자바스크립트 처럼 스레드가 하나밖에 없는 방식에서는

값을 가져오기 전까지 브라우져가 멈춰있어야 하는 단점이 있었습니다. 그래서 모두 비동기 방식인 두번째 방식을 사용하게 된 것입니다.

이 CPS 방식은 장단점을 다음과 같이 가지게 됩니다.

장점

    1. javascript의 one thread 구조에서 전체 프로그램을 block 시키지 않게 해준다.

    2. 기존 ajax 를 많이 사용하던 javascript user들에게 매우 익숙한 사용경험을 제공한다.

단점

    1. 복잡도가 증가하게 된다.

    2. javascript를 잘 사용하지 않던 다른 개발자들은 매우 생각하기 힘든 방식이다.

    3. 프로그램의 규모가 커지면 syntax에 실수가 많아질 수 있으며, 생각이 점점 힘들어 진다.

CPS 자체가 70년대에 생겨서 8~90년대에 컴파일러의 중간표현을 위해 만들어진 방식으로 사람이 쓰기엔 애초에 이해하기 힘든 방식인게 맞기 때문에, 복잡한게 맞는것 같습니다.

아직도 Stackoverflow등을 보면 javascript 질문 중 다음과 같은 질문이 많습니다.

function getY() {
    var y;
    $.get("/gety", function(jsonData){
        y = jsonData.y;
    });
    return y;
}
var x = 5;
var y = getY();
console.log(x + y);
// why doesn't it work???

당연히 get에서 가져오는 jsonData가 비동기로 오기 때문에 y는 그냥 undefined 입니다.

위 예제는 다음과 같이 CPS로 바뀌어야 합니다.

function getY(continueWith) {
    $.get("/gety", function(jsonData) {
        continueWith(jsonData.y);
    });
}

var x=5;
getY(function(y) {
    console.log(x + y);
});

물론 이 방식은 열거 하는 방식이 어렵고, 병렬방식으로 생각 하는 것도 어렵고, 에러를 받는것도 힘듭니다.

web API가 User를 늘리기 위해서는 기존 javascript user 뿐만 아니라 다른 개발자들도 포용해야 하는데, CPS는 이 점에 있어서 명백한 단점으로 작용할 수 있습니다.

기존 web 언어가 아닌 언어들은 동기 방식을 많이 사용합니다. 

Console.WriteLine("Whatisyourname?");
stringname = Console.ReadLine();
Console.WriteLine("Hello,"+name);

var fileNames = Directory.EnumerateFiles("C:\\");
foreach (var fileName in fileNames)
{
    using (var f = File.Open(fileName, FileMode.Open))
    {
        Console.WriteLine(fileName + "" + f.Length);
    }
}

이런경우 block 되는 것을 멈추기 위해 Thread를 사용하게 되고, Thread를 너무 많이 사용하는 것을 막기 위해 Thread pool 등을 사용해야 합니다.

하지만 javascript에서는 스레드가 하나밖에 없기 때문에, 위에서 설명한 것과 같이 CPS 방식을 사용하게 되는 것입니다.

그렇다면 이 문제를 어떻게 해결 해야 하나 생각 해보면.

사실 CPS를 해결하는 방법은, <Thread>, <Coroutine> 정도가 있을 수 있습니다.

Thread는 이미 많이 아실것이지만, javascript에서 지원을 하질 않는다는 문제가 있습니다.

worker 라는 w3c 스펙이 만들어져 있지만, 지원 여부조차 잘 모르는 사람도 많으며, worker에서는 browser DOM context 혹은 현재 web API context를 사용할수 없기 때문에, 결국 같은 문제가 부분적으로 일어날 것입니다.

그렇다면 thread를 위한 다른 방식을 만들거나, coroutine을 지원해야 합니다.

thread 방식은 이미 모두 알것 같고, thread 를 지원 한다고 해서 CPS 방식보다 코드가 간단해 지지 않으며, context switching 때문에 성능도 떨어질 가능성이 큽니다. (스레드 풀링이나, context control등 또한 CPS보다 쉽다고 말하기 힘듭니다.)

그래서 coroutine에 대해 이야기 해 보겠습니다.

coroutine은 context를 정지 시켜서 다른 context로 이동했다가 다시 정지시켰단 context부터 다시 시작 할 수 있는 방법입니다.

// Continuation Passing Style
 var documentsDir;
 filesystem.resolve('documents', 
    function(dir){ 
        documentsDir = dir;
        dir.listFiles( function(files){
            for(var i = 0; files.length; i++) {
                console.log("File Name is " + files[i].name);
            }
            var testFile = documentsDir.createFile("test.txt");
            if (testFile != null) {
                testFile.openStream("w", function(fs){ 
                    fs.write("HelloWorld");
                    fs.close();
                }, function(e){
                    console.log("Error " + e.message);
                }, 
                "UTF-8"
                );
        }}, function(error) {
            console.log("The error " + error.message);
        });
    }, function(e){
        console.log("Error" + e.message);
    }, 
    "rw"
 );
 
 
// Blocking (or coroutine inside??)
var dir = filesystem.resolve('documents', 'rw');
// pause
if (dir != undefined){
    var files = dir.listFiles();
    // pause
    for(i=0; files.length; i++){
        console.log("File name is " + files[i].name);
    }
 
    var testFile = dir.createFile("test.txt");
    // pause
    if (testFile != null) {
        fs = testFile.openStream("w", "UTF-8");
        // pause
        if(fs == undefined){
            console.log("Error " + e.message);
        }
        else {
            fs.write("HelloWorld");
            fs.close();
        }
    }
}
else {
    console.log("Error " + e.message);
}

이 코드는 모 플랫폼의 FileSystem 에 있는 Example 입니다. 첫번째 것은 CPS로 짜여져 있고,

두번째 코드는 block 스타일로 짜여져 있습니다. (API가 block 일때 저렇게 나올 것입니다.)

당연히 두번째 코드가 좀더 문법적 오류가 적게 나올 수 있고, 생각하기도 쉽다는 것을 알수 있습니다.

하지만 두번째 코드의 문제는 pause 라고 적힌 부분에서 block 될 수 있다는 것입니다.

하지만 만약 coroutine이 지원된다면 이 부분에서 다른 루틴으로 점프해서 다른 연산을 하다가 자연스럽게 다시

pause했던 부분으로 돌아갈 수 있습니다. 그래서 스레드나, CPS 방식 없이 아래 처럼 코딩을 하지만 blocking 되지 않을 수 있는 것입니다.

문제는 이러한 coroutine을 언어 차원에서 지원 해야 한다는 것인데요.

v8에서는 http://code.google.com/p/js-coroutine/ 같은 방식이 개발되고 있습니다.

다음과 같은 예제를 작동 하면:

     function foo(a) {
       
print("foo: " + a);
       
return coroutine.yield(2*a)
     
}

     
function test(a) {
           
print("co-body: a=" + a);
           
var r = foo(a + 1);
           
print("co-body: r=" + r);
           
var s = coroutine.yield(a + r);
           
print("co-body: r=" + r + ", s=" +s);
           
return "end";
     
}

     
var co = coroutine.create(test);

     
print("main: " + coroutine.resume(co, 10));
     
print("main: " + coroutine.resume(co, "r"));
     
print("main: " + coroutine.resume(co, "x"));
     
print("main: " + coroutine.resume(co, "x"));

이러한 결과가 나오게 됩니다.:

co-body: a=10
foo
: 11
main
: 22
co
-body: r=r
main
: 10r
co
-body: r=r, s=x
main
: end
main
: undefined

즉 저희가 web platform으로써 API를 유행 시키려면 이러한 단점을 넘어설 수 있는 방법을 찾아야 하는데,

어떤 방식이 좋을까요? 전 web platform에서 CPS를 넘어서지 못하면, 안드로이드 처럼 흥할 수 없다고 생각 합니다.

This was posted 3 months ago. It has 0 notes.

C++ framework인 meego는 version up을 하면서 binary compatibility를 맞추기가 쉬울까?

framework의 version이 올라가게 되면, class에 늘어나는 method들이 생기게 마련이고, 이러한 method들을 내부에서 호출 할 가능성도 있다.

자 다음 예제를 한번 보자.

물론 예제가 허접하지만.. man.h와 man.cpp라는 파일로 만들어진 shared object가 있다고 하자.

testing.cpp는 이 so를 사용하여 빌드 된다.

자 다음은 man.h와 man.cpp가 version up되어 virtual function이 하나 늘어났다고 생각하자.

근대 testing.cpp로 만들어진 binary는 새로 만들어진 so에 맞춰서 새로 빌드되지 않았다.

그럼 이 binary는 실행하면 죽게 된다.

meego는 완벽한 프레임워크라 중간에 virtual function이 생길 염려는 없는걸까??

C는 같은 이유로 structure의 member를 추가/삭제 하는 경우에 binary compatibility가 깨질 수 있다.

대부분 mobile platform들이 update를 꾸준히 하고 있는데… 

C와 C++로 만드는 mobile platform은 Android처럼 java와 같은 언어로 만드는 platform보다 하위호환을 맞추는게 쉽지 않아 보인다…

This was posted 1 year ago. It has 1 note.

tumblr를 시작 합니다.

This was posted 1 year ago. It has 2 notes.