カテゴリー : ioLanguage

このカテゴリーの登録数:42件 表示 : 1 - 10 / 42

2009/11/15

scala の RemoteActor を IoLanguage でそれっぽく

ポスト @ 23:09:06 | , , ,     

今回はいつもと逆で Io でやってみる
ターゲットは scala の RemoteActor

Ioには透過的なactorがあるのに、remote化をするのが大変。例えば scala だとこんなに簡単

(server)

import scala.actors.Actor
import scala.actors.Actor._
import scala.actors.remote.RemoteActor
import scala.actors.remote.RemoteActor._

class Hoge extends Actor {
    def act() {
        RemoteActor.alive(9001)
        RemoteActor.register('hoge, self)
        Actor.loop {
            react {
                case s:String => Console.println(s)
            }
        }
    }
}

object Server {
    def main(args: Array[String]){
        val hoge = new Hoge;
        hoge.start();
    }
}

(client)

import scala.actors.remote.RemoteActor._
import scala.actors.remote.Node

object Client {
    def main(args: Array[String]) {
        val c = select(Node("127.0.0.1", 9001), 'hoge)
        c !? ("hoge")
    }
}

んで、これを Io で簡単な実装をやってみる。古き良きXML-RPC的な感じで

(server)

Socket
RemoteServer := Object clone do (
    Logger := Object clone do (
        appender := nil
        debug := method(msg,
            appender writeln(msg)
        )
        init := method(
            appender = Lobby getSlot("Object")
        )
    )
    NopLogger := Logger clone do (
        init := method(
            appender = Object clone do (
                writeln := method()
            )
        )
    )

    port ::= 10101
    debug ::= false
    init := method(
        self target := Map clone
        self logger := if(self debug, RemoteServer Logger clone, RemoteServer NopLogger clone)
    )
    register := method(obj,
        self target atPut(obj type, obj)
    )
    start := method(
        handler := RemoteServer Handler clone
        handler setLogger(self logger)
        handler setTarget(self target)

        server := Server clone
        server setPort(self port)
        server handler := handler
        server handleSocket := method(socket,
            handler @handle(socket)
        )
        server start
    )
    Handler := Object clone do(
        target ::= nil
        logger ::= nil
        parse := method(request,
            string := request betweenSeq("<request>", "</request>")
            targetString := string betweenSeq("<target>", "</target>")
            messageString := string betweenSeq("<message>", "</message>")
            result := Object clone
            result targetString := targetString
            result messageString := messageString
            result
        )
        handle := method(socket,
            while(socket isOpen,
                if(socket read,
                	request := socket readBuffer asString
                    if(request isZero, socket close; return nil)
                    e := try (
                        p := parse(request)
                        logger debug("request:" .. p targetString .. ",message:" .. p messageString .. " ")

                        t := target at(p targetString)
                        result := t doString(p messageString)
                        logger debug("result:" .. result .. " ")

                        buf := Sequence clone asBuffer
                        buf appendSeq("<response>")
                        buf appendSeq("<serialize>", result asString, "</serialize>")
                        buf appendSeq("</response>")
                        socket write(buf asString)
                        logger debug("response:" .. buf asString .. " ")
                    )
                    e catch(Exception,
                        buf := Sequence clone asBuffer
                        buf appendSeq("<exception>")
                        buf appendSeq("<error>", e error, "</error>")
                        buf appendSeq("</exception>")
                        socket write(buf asString)
                        logger debug("response:" .. buf asString .. " ")
                    )
                )
                socket readBuffer empty
            )
        )
    )
)

(client)

RemoteClient := Object clone do (
    Queue := Object clone do (
        init := method(
            self values := List clone
        )
        push := method(value,
            self values append(value)
        )
        pop := method(
            self values removeFirst
        )
        size := method(
            self values size
        )
    )
    Logger := Object clone do (
        appender := nil
        debug := method(msg,
            appender writeln(msg)
        )
        init := method(
            appender = Lobby getSlot("Object")
        )
    )
    NopLogger := Logger clone do (
        init := method(
            appender = Object clone do (
                writeln := method()
            )
        )
    )

    host ::= "localhost"
    port ::= 10101
    target ::= nil
    debug ::= false

    socket := Socket clone
    queue := Queue clone
    coroutine := nil
    init := method(
        //self queue := Queue clone
        //self socket := Socket clone
        //self coroutine := nil

        self logger := if(debug, RemoteClient Logger clone, RemoteClient NopLogger clone)
    )
    connection := method(
        if(self socket isOpen not,
            self socket setHost(self host)
            self socket setPort(self port)
            self socket connect
            //self socket = socket
        )
        self socket
    )
    request := method(
        if(self coroutine isNil not) then (
            if(self queue size == 0, self coroutine resumeLater)
        ) else (
            self coroutine := coroDo(requestCoroutine)
            Coroutine yieldingCoros atInsert(0, self coroutine)
        )
    )
    requestCoroutine := method(
        loop (
            while(future := self queue pop,
                conn := self connection

                buffer := Sequence clone asBuffer
                buffer appendSeq("<request>")
                buffer appendSeq("<target>", future target, "</target>")
                buffer appendSeq("<message>", future message, "</message>")
                buffer appendSeq("</request>")
                self logger debug("request:" .. buffer asString)

                conn write(buffer asString)
                if(conn read,
                    res := conn readBuffer asString
                    self logger debug("response:" .. res)

                    p := parse(res)
                    if(p error isZero) then (
                        future setResult(p serialize)
                    ) else (
                        future setResult(Exception clone setError(p error))
                    )
                )
                conn readBuffer empty
            )
            self coroutine pause
        )
    )
    parse := method(response,
       string := response betweenSeq("<response>", "</response>")
       serialize := string betweenSeq("<serialize>", "</serialize>")
       error := string betweenSeq("<error>", "</error>")
       result serialize := serialize
       result error := error
       result
    )
    setSlot("@", method(
        m := call argAt(0) code
        f := RemoteClientFuture clone setTarget(self target) setMessage(m asString)
        self request
        self queue push(f)
        f futureProxy
    ))
    setSlot("@@", method(
        m := call argAt(0) code
        f := RemoteClientFuture clone setTarget(self target) setMessage(m asString)
        self request
        self queue push(f)
        nil
    ))
)
RemoteClientFutureProxy := Object clone do (
    future ::= nil
    get := method(
        future waitOnResult
        r := future result
        if(r type == Exception type) then(
            Exception raise(r error)
        )
        r
    )
)
RemoteClientFuture := Object clone do (
    init := method(
        self target := nil
        self message := nil
        self result := nil
        self error := nil
    )
    setTarget := method(target, self target = target; self)
    setMessage := method(message, self message := message; self)
    setResult := method(r,
        self result := r
        if(self waitingCoros,
            self waitingCoros foreach(resumeLater)
        )
    )
    getResult := method(self result)
    waitOnResult := method(
        self waitingCoros append(Scheduler currentCoroutine)
        Scheduler currentCoroutine pause
    )
    futureProxy := method(
        self waitingCoros := List clone
        self proxy := RemoteClientFutureProxy clone setFuture(self)
        self proxy
    )
)

こんな感じで使う

(server)

Hoge := Object clone do (
    hello := method(
        "hello world"
    )
    sum := method(args,
        r := 0
        args foreach(a, r := r + a)
        r
    )
)
Storage := Map clone

server := RemoteServer clone
server register(Hoge clone)
server register(Storage clone)
server start

(client)

//RemoteClient setDebug(true)

client := RemoteClient clone
client setTarget("Hoge")
(client @hello) get println
(client @sum(list(1, 2, 3))) get println

client2 := RemoteClient clone
client2 setTarget("Storage")
(client2 @atPut("foo", "bar"))
(client2 @at("foo")) get println

Client側のCoroutineまわりはそのままActorと同じような実装、通常のactorと違って、@xxxの戻りは future proxyもどきだったりする

self coroutine := coroDo(requestCoroutine)
Coroutine yieldingCoros atInsert(0, self coroutine)

何より面倒なのは、justSerializeはちょっとあれな感じなので、codeを使ってserializeっぽくして値を送っている(受取側は receiver doString(request string)

call argAt(0) code
target getSlot(request message) code

ちょっと scala に近づけた気がする。
ってか、もう少し頑張れば RMI 的なこともできそう。後は、単純な並列化はActorが頑張ってくれるはずだから、スケールアウトとかもできるようになれば面白くなりそう。
ioだと逆に面倒っぽいけど...。

2009/10/05

IoLanguage の Actor を Java でやってみる。その2

ポスト @ 4:02:11 | , , , ,     

JavaはMethodそのものについて型のような概念がない。いや、一応java.lang.reflect.Methodには、Methodとして扱えるけど、Ioでいうところのmethodじゃない。

つまり、Ioだとmethodそのものがオブジェクトから取り出せる。

a := Object clone do (
  hoge := method(
    "hello world" println
  ) 
)   
    
hoge := a getSlot("hoge")
hoge 

これができるから、methodそのものを遅延させたりすることができるワケで。。。

a := Object clone do (
  hoge := method(
    "hello world" println
  )        
)          
           
hoge := a getSlot("hoge")
@hoge      
@hoge      
           
yield;yield 

ということで、今回は、methodそのものを非同期/同期にできるようなことを少し考えてみた。
といっても、javaの静的型は強力なので、一部適当

とりあえず、いつもの実装

package actor;

public interface Hoge {
    public String hello(int i);
    public Integer world(int i);
}
package actor;

public class HogeImpl implements Hoge {
    public String hello(int i){
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e){
            // nop
        }
        return "hello" + i;
    }
    public Integer world(int i){
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e){
            // nop
        }
        return new Integer(i);
    }
}

以下、書きかけ

package actor;

import java.lang.reflect.Method;

public class Actor<E> {
    private Class<E> type;
    private ExecutorService executor = Executors.newFixedThreadPool(5);
    
    private Actor(Class<E> type){
        this.type = type;
    }
    public static <E> Actor<E> register(Class<E> target){
        return new Actor<E>(target);
    }
    
    public <T> SynchronizeMethod<T> sync(Object object, String methodName){
        return null;
    }
    
    public <T> FutureMethod<T> async(Object object, String methodName) throws NoSuchMethodException {
        Method[] methods = type.getMethods();
        for(Method method: methods){
            if(method.getName().equals(methodName)){
                return new FutureMethod<T>(executor, object, method);
            }
        }
        throw new NoSuchMethodException(methodName);
    }
}
public abstract class ProxyMethod<T> {
    private ExecutorService executor;
    private Object target;
    private Method method;
    public ProxyMethod(ExecutorService executor, Object target, Method method){
        this.executor = executor;
        this.target = target;
        this.method = method;
    }
    protected ExecutorService getExecutor(){
        return executor;
    }
    protected Object getTarget(){
        return target;
    }
    protected Method getMethod(){
        return method;
    }
    public abstract Object call(Object...args);
}
package actor;

import java.lang.reflect.Method;

public class FutureMethod<T> extends ProxyMethod<T> {
    public FutureMethod(ExecutorService executor, Object target, Method method) {
        super(executor, target, method);
    }

    @Override
    public Future<T> call(final Object...args){
        return getExecutor().submit(new Callable<T>(){
            @SuppressWarnings("unchecked")
            public T call() throws Exception {
                return (T) getMethod().invoke(getTarget(), args);
            }
        });
    }
}
package actor;

import java.lang.reflect.Method;

public class SynchronizeMethod<T> extends ProxyMethod<T> {
    public SynchronizeMethod(ExecutorService executor, Object target, Method method) {
        super(executor, target, method);
    }

    @Override
    public Object call(Object... args) {
        try {
            return getMethod().invoke(getTarget(), args);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
package actor;

import java.util.ArrayList;

public class ActorTest {
    @SuppressWarnings("boxing")
    public static void main(String...args){
        HogeImpl impl = new HogeImpl();
        Actor<Hoge> actor = Actor.register(Hoge.class);
        try {
            FutureMethod<String> method = actor.async(impl, "hello");
            List<Future<String>> results = new ArrayList<Future<String>>();
            for(int i = 0; i < 25; i++){
                results.add(method.call(i));
            }
            
            try {
                for(int i = 0; i < results.size(); ++i){
                    System.out.println("result=" + results.get(i).get(2, TimeUnit.SECONDS));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            
            int i = 0;
            boolean done = false;
            while(true){
                if(done && results.size() <= i){
                    break;
                }
                if(!results.get(i++).isDone()){
                    i = 0;
                    continue;
                }
                done = true;
            }
            System.out.println("done");
            System.exit(0);
        } catch(NoSuchMethodException e){
            // nop
        }
    }
}

まとめ

うーん、とりあえず、動くけど、こんなんどうでもいい。
これだけなら、こんな感じのと何が違うのかと。。。

package actor;

import java.util.ArrayList;

public class Test {
    public void main(String...args){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        List<Future<String>> results = new ArrayList<Future<String>>();
        for(int i = 0; i < 25; ++i){
            results.add(executor.submit(new Callable<String>(){
                public String call() throws Exception {
                    return "hello world" + System.currentTimeMillis();
                }
            }));
        }
        for(int i = 0; i < results.size(); ++i){
            try {
                System.out.println(results.get(i).get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

}

うーん、型推論と静的型に勝てない。。。

2009/09/21

IoLanguage の Actor を Java でやってみる。その1

ポスト @ 7:30:13 | , , , ,     

IoLanguageにおける、@@@をjavaでやってみる。
ちなみに、io の @ は 同期メッセージ、 @@ は非同期メッセージとみなしてやってみる。
ioのActorについては↓
ref - http://iolanguage.com/scm/git/checkout/Io/docs/IoGuide.html#Concurrency-Actors

まずは、Ioのコード

こんな感じ

Hoge := Object clone do (
    index ::= 0
    hello := method(i,
        msg := "hello: " .. i .. "(" .. index .. ")"
        System sleep(1)

        msg println
        index = index + 1
        msg
    )
    world := method(i,
        msg := "world: " .. i .. "(" .. index .. ")"
        System sleep(1)

        msg println
        index = index + 1
        msg
    )
)

hoge := Hoge clone
for(i, 0, 4,
    writeln("result=", hoge @hello(i))
)
while(Scheduler yieldingCoros size > 0, yield)

for(i, 0, 4,
    writeln("result=", hoge @@world(i))
)
while(Scheduler yieldingCoros size > 0, yield)

何をやってるかというと、Hoge インスタンスの hello メソッドに同期メッセージをバシバシ投げて、結果をそのまま受け取るようにしている。
同期メッセージなので、メッセージはmethodでブロックされる。
次に、同じ Hoge インスタンスの world メソッドに非同期メッセージをバシバシ投げて、結果を受け取っている。
こっちは、非同期メッセージなので、ブロックはされないし、戻り値もない

これの結果

こんな感じ

result=hello: 0(0)
hello: 0(0)
result=hello: 1(1)
hello: 1(1)
result=hello: 2(2)
hello: 2(2)
result=hello: 3(3)
hello: 3(3)
result=hello: 4(4)
hello: 4(4)
result=nil
result=nil
result=nil
result=nil
result=nil
world: 0(5)
world: 1(6)
world: 2(7)
world: 3(8)
world: 4(9)

result=nilが連続している箇所はnon blocking

次に Java のコード

すごく長いけど

package actor;

import actor.Act.Future;

public class Main {

    public static interface Hoge {
        public String hello(int i);
        public String world(int i);
    }
    
    public static void main(String...args){
        @Act({
            @Future(method="hello", async=false),
            @Future(method="world")
        })
        class HogeImpl implements Hoge {
            private int index = 0;
            public String hello(int i){
                String msg = "hello:" + i + "(" + (index++) + ")";
                try {
                    Thread.sleep(1000);
                } catch(Exception e){}
                
                System.out.println(msg);
                return msg;
            }
            public String world(int i){
                String msg = "world:" + i + "(" + (index++) + ")";
                try {
                    Thread.sleep(1000);
                } catch(Exception e){}
                
                System.out.println(msg);
                return msg;
            }
        }
        
        Hoge hoge = (Hoge) Actor.create(new Factory<Hoge>(Hoge.class), HogeImpl.class);
        for(int i = 0; i < 5; ++i){
            System.out.println("result=" + hoge.hello(i));
        }
        
        try {
            Thread.currentThread().join(3000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        
        for(int i = 0; i < 5; ++i){
            System.out.println("result=" + hoge.world(i));
        }
        
        try {
            Thread.currentThread().join(3000);
            System.exit(0);
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

肝心なのは、↓の部分

public class Main {
    public static interface Hoge {
        public String hello(int i);
        public String world(int i);
    }
    
    public static void main(String...args){
        @Act({
            @Future(method="hello", async=false),
            @Future(method="world")
        })
        class HogeImpl implements Hoge {
            private int index = 0;
            public String hello(int i){
                String msg = "hello:" + i + "(" + (index++) + ")";
                try {
                    Thread.sleep(1000);
                } catch(Exception e){}
                
                System.out.println(msg);
                return msg;
            }
            public String world(int i){
                String msg = "world:" + i + "(" + (index++) + ")";
                try {
                    Thread.sleep(1000);
                } catch(Exception e){}
                
                System.out.println(msg);
                return msg;
            }
        }
        :
        :
    }
}

この部分は、メソッド内にクラスを作っている。というのも、FutureProxyを作るために、java.lang.reflect.Proxyを使うんだけども、Interfaceが必要なので、実装クラスは何度でも破棄してもいいように、メソッド内のクラスを使う
また、この実装クラスに対して、同期メッセージか非同期メッセージか(Io の @@@)はAnnotationで指定するようにしている。
本来なら、実行時に指定するんだけども、まぁ、JavaのAnnotationで出来る範囲でもないので、とりあえずこんなんで

その他クラス類

アノテーションは、メソッドが非同期かどうかさえ記述出来ればいいので、こんな感じ

package actor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.LOCAL_VARIABLE, ElementType.PARAMETER, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface Act {
    
    public @interface Future {
        String method();
        boolean async() default true;
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Async {
        boolean value() default true;
    }

    Future[] value();
}

あえて、Factoryは分離したので、こんな感じ。単純にClassからnewInstanceしているだけだけ。

package actor;

import java.lang.reflect.Constructor;

public class Factory<T> {
    private Class<T> interfaceClass;
    public Factory(Class<T> interfaceClass){
        this.interfaceClass = interfaceClass;
    }
    public Class<T> getInterfaceClass(){
        return interfaceClass;
    }
    public T createInstance(Class<? extends T> type, Object...args){
        Class<?>[] clazz = new Class[args.length];
        for(int i = 0; i < args.length; ++i){
            clazz[i] = args.getClass();
        }
        try {
            Constructor<? extends T> constr = type.getConstructor(clazz);
            return constr.newInstance(args);
        } catch(NoSuchMethodException e) {
            try {
                return type.newInstance();
            } catch(Exception e1){
                e1.printStackTrace();
            }
        } catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

Proxyというか、ここではActorの実装は、こんな感じ。

package actor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Actor implements InvocationHandler {
    
    public static <T> Object create(Factory<T> factory, Class<? extends T> type){
        return create(factory, type, new Object[]{});
    }
    public static <T> Object create(Factory<T> factory, Class<? extends T> type, Object...args){
        T target = factory.createInstance(type, args);
        
        return Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[]{factory.getInterfaceClass()},
            new Actor(target)
        );
    }
    
    private ExecutorService executor = Executors.newFixedThreadPool(5);
    private BlockingQueue<Entry> queue = new LinkedBlockingQueue<Entry>();
    private Callable<Object> call = new ActorExecutor<Object>(queue);
    private Object target;
    private Actor (Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        queue.put(new Entry(target, proxy, method, args));
        
        Act act = target.getClass().getAnnotation(Act.class);
        for(Act.Future future: act.value()){
            if(method.getName().equals(future.method())){
                if(future.async()){
                    // java.lang.ClassCastException: java.util.concurrent.FutureTask
                    // return executor.submit(call);
                    try {
                        return executor.submit(call).get(0, TimeUnit.NANOSECONDS);
                    } catch(java.util.concurrent.TimeoutException e){
                        return null;
                    }
                }
                return executor.submit(call).get(10, TimeUnit.SECONDS);
            }
        }
        return null;
    }
}

大きくここでは、Proxyを使って、メッセージのやりとりを中継

    :
    :
    :
    public static <T> Object create(Factory<T> factory, Class<? extends T> type, Object...args){
        T target = factory.createInstance(type, args);
        
        return Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            new Class[]{factory.getInterfaceClass()},
            new Actor(target)
        );
    }
    :
    :
    :

と、BlockingQueueを使ったメッセージとTaskの分離部分

    :
    :
    :    
    private ExecutorService executor = Executors.newFixedThreadPool(5);
    private BlockingQueue<Entry> queue = new LinkedBlockingQueue<Entry>();
    private Callable<Object> call = new ActorExecutor<Object>(queue);
    private Object target;
    private Actor (Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        queue.put(new Entry(target, proxy, method, args));
        
        Act act = target.getClass().getAnnotation(Act.class);
        for(Act.Future future: act.value()){
            if(method.getName().equals(future.method())){
                if(future.async()){
                    // java.lang.ClassCastException: java.util.concurrent.FutureTask
                    // return executor.submit(call);
                    try {
                        return executor.submit(call).get(0, TimeUnit.NANOSECONDS);
                    } catch(java.util.concurrent.TimeoutException e){
                        return null;
                    }
                }
                return executor.submit(call).get(10, TimeUnit.SECONDS);
            }
        }
        return null;
    }
    :
    :
    :

この2つ。

Ioだと、メソッドの戻りが動的であっても構わないので、Javaでいうところの、java.util.concurrent.Futureが返せるけど、javaはそんなことやると、java.lang.ClassCastException: java.util.concurrent.FutureTaskなわけで、とりあえず、そこは別の方法にしている。他の方法は後で考えてみる

他はもろもろ

package actor;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;

public class ActorExecutor<T> implements Callable<T> {
    private BlockingQueue<Entry> queue;
    public ActorExecutor(BlockingQueue<Entry> queue){
        this.queue = queue;
    }
    @SuppressWarnings("unchecked")
    public T call() throws Exception {
        Entry e = queue.take();
        return (T) e.method.invoke(e.target, e.args);
    }
}
package actor;

import java.lang.reflect.Method;

class Entry {
    Object target;
    Object proxy;
    Method method;
    Object[] args;
    Entry(Object target, Object proxy, Method method, Object[] args){
        this.target = target;
        this.proxy = proxy;
        this.method = method;
        this.args = args;
    }
}

BlockingQueueに実行時のパラメータを全部入れておいて、ExecutorService.submitで個別に処理を行う。というとこあたりは、通常どおり

javaの実行結果

Ioとほぼ同じですが...

Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
hello:0(0)
result=hello:0(0)
hello:1(1)
result=hello:1(1)
hello:2(2)
result=hello:2(2)
hello:3(3)
result=hello:3(3)
hello:4(4)
result=hello:4(4)
result=null
result=null
result=null
result=null
result=null
world:0(5)
world:1(6)
world:2(7)
world:3(8)
world:4(9)

まとめ

JavaのAnnotationでLocalスコープが使えればもう少し別の書き方にできそう。あと、実行時に同期か非同期かを切り替えれるような透過的なインタフェースがあると便利なのに。

Ioのコードをあえて、Javaにすることで、どれだけIoが楽か、逆に、Javaでも同じような実装は可能な事、Javaはもう少し細かい実装が可能っぽいこと。などなど、色々わかった。

次回は、透過的なインタフェースを作るところを目指す。

2008/07/26

Log4Io を作ってみた。

ポスト @ 20:52:56 |     

Log4Io を作ってみた。
ref http://coderepos.org/share/browser/lang/io/Log4Io/io

使い方は、Log4** とほぼ同じで、こんな感じ

Log4Io

logger := Log4Io getLogger("sample")
logger addAppender(Log4Io ConsoleAppender with)
logger setLevel(Log4Io Level INFO)
if(logger isDebugEnabled) then (
    logger debug("debug")
)
if(logger isInfoEnabled) then (
    logger info("info")
)
if(logger isWarnEnabled) then (
    logger warn("warn")
)

これを実行するとこんな感じ。

==> 2008-07-26 20:48:50 JST INFO sample - info
==> 2008-07-26 20:48:50 JST WARN sample - warn

また、Lobby に log4ioLogger を設置しているので、こちらはそのまま使えます。

Log4Io

// lobby log4ioLogger
log4ioLogger error("hoge")
log4ioLogger info("foo")
log4ioLogger warn("bar")

こっちは、こんな感じに出力される

==> 2008-07-26 20:48:50 JST ERROR Log4Io - hoge
==> 2008-07-26 20:48:50 JST INFO Log4Io - foo
==> 2008-07-26 20:48:50 JST WARN Log4Io - bar

ほとんど log4js からパクったけど、Log4J 並みの細かいことも作れそう。
ただ、パッケージとかが無い分だけ色々と面倒なのだけど。。。

Io の clone でハマる

ポスト @ 20:42:29 | ,     

clone したときは、init メソッドが呼ばれて、そこで必ず初期化しないとダメだということ
以下のコードだと、オブジェクトの値が clone されるんだね

Hoge := Object clone do(
    values := List clone
    with := method(name,
        c := self clone
        c values append(name)
        c
    )
)

a := Hoge with("a")
b := Hoge with("b")
a values println
==> list(a, b)

これってつまり js だと、以下のコードと同じってことか。

var Hoge = function (){};
Hoge.prototype = {
    values: [],
    with: function(name){
        var c = new Hoge
            c.values.push(name)
        return c
    }
};

var a = Hoge.prototype.with("a")
var b = Hoge.prototype.with("b")
print(a.values)
==> (a, b)

ということで、Io のコード及び、js のコードを直すなら、こんな感じになる。
# って基礎中の基礎か

Io

Hoge := Object clone do(
    init := method(
        self values := List clone
    )
    with := method(name,
        c := self clone
        c values append(name)
        c
    )
)

a := Hoge with("a")
b := Hoge with("b")
a values println
==> a
b values println
==> b

js

var Hoge = function (){return this.init.apply(this)};
Hoge.prototype = {
    init: function (){
        this.values = []
    },
    with: function(name){
        var c = new Hoge
            c.values.push(name)
        return c
    }
};

var a = Hoge.prototype.with("a")
var b = Hoge.prototype.with("b")
alert(a.values)
==> a
alert(b.values)
==> b

慣れは怖い

2008/07/23

Io で curry, bind を実装する

ポスト @ 23:07:53 | ,     

prototype.js の Function.prototype.curryFunction.prototype.bind が Io にも欲しくなったので、簡単に実装してみた。

ちなみに、Function.prototype.curry は引数のカリー化を行ってくれる関数プロトタイプで、Function.prototype.bind は、スコープのカリー化もしてくれるヤツ(って説明でいいのかな)

Block apply := method(target, args,
    args = if(args isNil, list(), args)
    target = if(target isNil, scope, target)
    self setScope(target)
    self performWithArgList("call", args)
)
Block bind := method(
    args := call message argsEvaluatedIn(call sender)
    target := args at(0)
    args = args slice(1)
    b := self
    return block(
        b apply(target, args union(call message argsEvaluatedIn(call sender)))
    )
)
Block curry := method(
    args := call message argsEvaluatedIn(call sender)
    if(args size < 1) then (
        return self
    )
    /*
    target := args at(0)
    args = args slice(1)
    */
    b := self
    return block(
        b apply(scope, args union(call message argsEvaluatedIn(call sender)))
    )
)

まぁ、bindもcurryも同じようなものなので、こんな感じで動かす。

hoge := Object clone do(
    message := "this is hoge"
)
foo := Object clone do(
    message := "this is foo"
)

promp := block(a, b,
    "#{self message} at #{a}, #{b}" asMutable interpolate println
)

promp setScope(hoge)
a := promp curry("aaa")
a call("bbb")
==> this is hoge at aaa, bbb

b := promp bind(foo, "ccc")
b call("ddd")
==> this is foo at ccc, ddd

使い道は、これから考える

2008/07/20

translate posts of io mailing list

ポスト @ 1:02:45 , 修正 @ 2008/07/20 1:13:37 |     

io のメーリングリストを 超 オレオレ 訳にして、英語を読む。
via - Found something but I don't know what to make of it

[Io] Found something but I don't know what to make of it
何かみつけたんだけど、よくわからないんだ。

So I was in the process of compiling all the libs for the addons and
poking around looking for some info when I ran into a site in a
language I can only guess is Chinese:

アドオンのライブラリをコンパイルする方法とかの情報を探し回っていたら、
サイトに出くわしたんだけど、どうやら言語は中国語っぽいんだよね。

http://blog.xole.net/category.php?k=ioLanguage

Seems to be lots of Io code but I can't decipher all of what it does.
Is this a translation of existing info? Anyone know?

たくさんの io のコードがあるように思えるんだけど、それが何に関して書かれているか読めない(decipher: 解読?)んだよね。
ここに書かれているのは、何か既存の情報の翻訳なの?誰か知ってる?

投稿者C. Olson さんは、どうやら僕のところに漂着してしまったみたいだけど、中国語っぽくて読めなかったらしい。
んで、Brian さんが次の投稿で

Re: [Io] Found something but I don't know what to make of it

> So I was in the process of compiling all the libs for the addons and
> poking around looking for some info when I ran into a site in a
> language I can only guess is Chinese:
>

Japanese actually.
日本語みたいだね。

> http://blog.xole.net/category.php?k=ioLanguage
>
> Seems to be lots of Io code but I can't decipher all of what it does.
> Is this a translation of existing info? Anyone know?

Doesn't look like stuff I recognize from other posts so I imagine this
user has been doing their own investigation. The GC write up is quite
interesting (relating to tweaking the incremental step used in
collection).

うーん、どうやらそういった情報(ここでは「translation of existing info: 既存の情報の翻訳」かな)では無いみたいだよ。他の記事から私が想像するに、それらの記事は、それについて調査したことを書かれているんだと、私は思うな。
GC について書き上げているのはかなり面白いと思う(GC の収集には、増加ステップ(incremental step)の調整に関係しているってさ)

Brian さんは日本語が読めるらしい。しかも他の記事から読み解くとかスゲー人なんだな。
GC についても触れてくれてるのも嬉しい :)
そして、Steve Dekorte さんからの投稿

Re: [Io] Found something but I don't know what to make of it

> So I was in the process of compiling all the libs for the addons and
> poking around looking for some info when I ran into a site in a
> language I can only guess is Chinese:
>
> http://blog.xole.net/category.php?k=ioLanguage
>
> Seems to be lots of Io code but I can't decipher all of what it does.
> Is this a translation of existing info? Anyone know?

Here's a translation, but I'm still not very clear:
翻訳してみた。でもまだ明確にはならないなあ:

http://209.85.171.104/translate_c?hl=en&sl=ja&tl=en&u=http://blog.xole.net/category.php%3Fk%3DioLanguage&usg=ALkJrhjVqXbSXGowxw8i2ZyTgQC-b85dUA

うーん、皆さんを困らしているみたいですが、私も困惑。
だって英語できないんだもん。オレオレ訳が正しいとも限らないしなぁ。

ちなみに、"translation of existing info" については、これらかな。

今回出てきた英語

話し言葉(?)みたいな英語が多くて、意訳しかできないけど、こんな感じかな

Found something ...
なんかみつけた。
チョーそのまま。Found は find の過去形だからこんな感じかなぁ
make of it
理解する(?)
make of ... っていろんな意味があってよく分からん。英英辞典曰く(to understand or meaning character of...)
ref - GetUpEnglish: WHAT DO YOU MAKE OF IT?
poke around...
探し回る(?)
run into(ran into)...
...に偶然に会う
話し言葉っぽい感じ。英英辞典曰く(to meet by chance)
Seems to be...
...のようだ(appears to beににてる)
Seems to be... で開始されてもいいんだね。文法とかあんまり気にしなくてもいいんだ。
look like stuff...
...のように見える(かな?)
seems とか looks like はなんだ。結構曖昧なの?
ref - seems like, look likeの使い方を詳しく説明して欲しいです。それと going to be... - Yahoo!知恵袋, seem to be と It seems that の違い - 教えて!goo

話し言葉っぽいのが多くて、their own の their はどれだよ。ってのが多いなあ。うーん、難しい。

2008/07/14

io で GC を見る

ポスト @ 2:58:44 , 修正 @ 2008/07/14 3:16:07 | , ,     

io の Collector を使って、GCの動きを少し見てみる
via - Io の make test(libs/iovm/tests/run.io)

コードとしては、こんな感じのを使うらしい。

writeln("mpa \t as \t mb \t time \t mbt \t gc")

lastTimeUsed := 0.0

list(1.01, 1.05, 1.1, 1.2, 1.5, 1.7, 2, 4) foreach(as,
    list(0.01, 0.1, 1, 2, 4, 16) foreach(mpa,
        Collector setMarksPerAlloc(mpa)
        Collector setAllocatedStep(as)
        
        time := Date clone cpuSecondsToRun(test)
        mb := (Collector maxAllocatedBytes / 1000000) asString(0, 2)
        
        writeln(
            Collector marksPerAlloc asString(0, 2), "\t",
            Collector allocatedStep asString(0, 2), "\t",
            mb, "\t", 
            time asString(0, 2) , "\t", 
            (mb asNumber * time) asString(0, 2), "\t", 
            (100 * (Collector timeUsed - lastTimeUsed) / time) asString(2, 1), "%"
        )
        
        lastTimeUsed = Collector timeUsed

        // Collector showStats
        writeln("collected items: ", Collector collect)
        Collector resetMaxAllocatedBytes
    )
    "" println
)

今回は、循環参照するようにしてそれの GC を取ってみます。

aaa := method(
    Hoge := Object clone do(
        foo ::= nil
    )
    Foo := Object clone do(
        hoge ::= nil
    )

    h := Hoge clone setFoo(Foo clone setHoge(Hoge clone))
    f := Foo clone setHoge(Hoge clone setFoo(Foo clone))
    h clone setFoo(f clone setHoge(h clone))
    f clone setHoge(h clone setFoo(f clone))

    l := List clone
    for(i, 0, 100, l append(h, f, i * 0.987654321))
)

以下、全体

// Collector setDebug(true)
aaa := method(
    Hoge := Object clone do(
        foo ::= nil
    )
    Foo := Object clone do(
        hoge ::= nil
    )

    h := Hoge clone setFoo(Foo clone setHoge(Hoge clone))
    f := Foo clone setHoge(Hoge clone setFoo(Foo clone))
    h clone setFoo(f clone setHoge(h clone))
    f clone setHoge(h clone setFoo(f clone))

    l := List clone
    for(i, 0, 100, l append(h, f, i * 0.987654321))
)
test := method(1000 repeat(aaa))

writeln("time: ", Date clone cpuSecondsToRun(test), " seconds")

writeln("mpa \t as \t mb \t time \t mbt \t gc")

lastTimeUsed := 0.0

list(1.01, 1.05, 1.1, 1.2, 1.5, 1.7, 2, 4) foreach(as,
    list(0.01, 0.1, 1, 2, 4, 16) foreach(mpa,
        Collector setMarksPerAlloc(mpa)
        Collector setAllocatedStep(as)
        
        time := Date clone cpuSecondsToRun(test)
        mb := (Collector maxAllocatedBytes / 1000000) asString(0, 2)
        
        writeln(
            Collector marksPerAlloc asString(0, 2), "\t",
            Collector allocatedStep asString(0, 2), "\t",
            mb, "\t", 
            time asString(0, 2) , "\t", 
            (mb asNumber * time) asString(0, 2), "\t", 
            (100 * (Collector timeUsed - lastTimeUsed) / time) asString(2, 1), "%"
        )
        
        lastTimeUsed = Collector timeUsed

        // Collector showStats
        writeln("collected items: ", Collector collect)
        Collector resetMaxAllocatedBytes
    )
    "" println
)

これを実行すると、以下な感じの出力になる。

time: 0.511943 seconds
mpa 	 as 	 mb 	 time 	 mbt 	 gc
0.01	1.01	0.00	1.62	0.00	0.0%
collected items: 2
0.10	1.01	0.00	1.65	0.00	0.0%
collected items: 183
1.00	1.01	0.00	1.68	0.00	0.0%
collected items: 182
2.00	1.01	0.00	1.68	0.00	0.0%
collected items: 182
4.00	1.01	0.00	1.69	0.00	0.0%
collected items: 182
16.00	1.01	0.00	1.73	0.00	0.0%
collected items: 182

:
:
:

0.01	2.00	0.00	0.40	0.00	0.0%
collected items: 4879
0.10	2.00	0.00	0.40	0.00	0.0%
collected items: 2155
1.00	2.00	0.00	0.35	0.00	0.0%
collected items: 57131
2.00	2.00	0.00	0.35	0.00	0.0%
collected items: 64649
4.00	2.00	0.00	0.34	0.00	0.0%
collected items: 68050
16.00	2.00	0.00	0.35	0.00	0.0%
collected items: 70914

0.01	4.00	0.00	0.38	0.00	0.0%
collected items: 22421
0.10	4.00	0.00	0.40	0.00	0.0%
collected items: 2373
1.00	4.00	0.00	0.32	0.00	0.0%
collected items: 141619
2.00	4.00	0.00	0.31	0.00	0.0%
collected items: 145915
4.00	4.00	0.00	0.38	0.00	0.0%
collected items: 1119
16.00	4.00	0.00	0.38	0.00	0.0%
collected items: 2878

なぜか、Collector timeUsed の時間が取得できない...
今度調べる。

ちなみに、Collector setDebug(true) で実行すると、GC が sweep していく様子が見れます。

Collector_sweepPhase()
  allocated 9951
  allocatedSweepLevel 9938
Collector_sweepPhase()
  allocated 9892
  allocatedSweepLevel 9891
Collector_sweepPhase()
  allocated 9965
  allocatedSweepLevel 9964
Collector_sweepPhase()
  allocated 9892
  allocatedSweepLevel 9891
Collector_sweepPhase()
  allocated 9979
  allocatedSweepLevel 9978
Collector_sweepPhase()
  allocated 9899
  allocatedSweepLevel 9898
Collector_sweepPhase()
  allocated 9994
  allocatedSweepLevel 9993
Collector_sweepPhase()
  allocated 9910
  allocatedSweepLevel 9909
Collector_sweepPhase()
  allocated 9871
  allocatedSweepLevel 9870

データ推移

んで、これで終わりではなくて、すこしデータ推移も眺めてみた。
ちなみに、as は Collector allocatedStep で、mpa は Collector marksPerAllocとなっています。

各、as と mpa の意味として

the allocatedStep (can have a fractional component, but must be larger than 1). A collector sweep is forced when the number of allocated objects exceeds the allocatedSweepLevel. After a sweep, the allocatedSweepLevel is set to the allocated object count times the allocatedStep. Returns self
the number of incremental collector marks per object allocation (can be fractional). Returns self.

ということで、as: 1.05, 1.10, 1.20, 1.50, 1.70, 2.00, 4.00 のデータを抜粋
また、x 軸はmpa(marksPerAlloc)値、y 軸は回収されたオブジェクトの個数となっています。







ここから分かることは、as(allocatedStep) の値が小さい場合は、mpa(marksPerAlloc)の値が大きい方が 回収されるオブジェクトの個数が多いらしい。
しかし、as の値が大きくなるにつれて、mpa の値が小さい方が回収されやすくなっている。

これが、いわゆる GC 最適化って言われてるヤツかな。

他にも、as値が小さく、mpa値が大きいと GC にかかる時間が多いらしい(これはたまたまなのかもしれないけど)

また、回収された個数でみてみると、as値とmpa値が両方大きいからといって回収されるオブジェクトの個数が増えるというわけでは無いようだ。

as:1.10の時

as: 1.50の時

as: 2.00の時

as: 4.00の時

ってことで、簡単なまとめ

as(allocatedStep) 値は、回収されるオブジェクトの個数に比例する
mpa(marksPerAlloc) 値は、as 値が小さい時に、回収されるオブジェクトの個数を増やすことができるが、as 値が大きくなると、回収効率は見込めない。
as 値が小さい場合は、GC に時間がかかる場合が多いが、as 値はある一定以上になる場合は、GC の時間に比例しなくなる。

ということで、やっぱり↓の画像のとおりになりそう。

簡単なデータしか取ってないけど、Numbers から PDF に出力したモノを置いておきます。
http://blog.xole.net/resources/io_collector_low.pdf

2008/07/09

io で GoogleChart

ポスト @ 5:01:08 , 修正 @ 2008/07/09 5:06:32 |     

io languageでGoogleChartのURLデータを出力するヤツを作ってみた。
ref - http://xole.net/googlechart/sample.io

上のサンプルにもあるように、こんな画像を出力できるようにしただけ。

# 画像処理とかやってないのが、ヘタれ

こんなコードを書きます。

#!/usr/local/bin/io
doFile("GoogleChart.io")
doFile("Types.io")
doFile("Labels.io")
doFile("Colors.io")
doFile("Data.io")
doFile("Styles.io")

types := Types clone
types PieChart p3

labels := Labels clone
labels Title chtt(
"""
    GoogleChart sample
    by Io WebAPI
""")

labels Label chl("hoge", "foo", "bar")

data := Data clone
data Text chd("60", "30", "10")

styles := Styles clone
styles size(320, 200)

chart := GoogleChart URL withQuery(types, labels, data, styles)

"Content-type: text/html" println
"""
<html>
    <head>
        <title>GoogleChart with Io</title>
    </head>
    <body>
        <p>#{chart asHTML}<br />generated on #{Date}</p>
    </body>
</html>
""" interpolate println
exit

もう少しまともなヤツにしようと考えてる(もう少し表計算チックにできれば綺麗だよね)けど、とりあえずこれで。
コードはcodereposにあげました。
ref - http://coderepos.org/share/browser/lang/io/WebAPI/GoogleChart

2008/07/07

ioで、svn のようなコマンドインタフェースを簡単に作る

ポスト @ 21:42:47 , 修正 @ 2008/07/07 21:48:09 |     

svn コマンドなんかの

svn help commit

って、これって io のメッセージングにとても似てる

例えば、今日の タイムスタンプを取得するときなんかは

Date today asNumber

これってつまり、レシーバ svnに対して、helpコマンドの実行、そしてその引数としてcommitとかを指定して実行するっていうもの。
上の、Dateは、io だと、レシーバに対して doString を使ってコマンドを指定できる。もちろん、Lobbyに全てのコマンドを渡せる

Date doString("today asAtomDate")
==> 2008-07-07T00:00:00+09:00
Lobby doString("Date today asAtomDate")
==> 2008-07-07T00:00:00+09:00

これの仕組みを使って、port っぽいのを io で

#!/usr/bin/env io

// a.io
Command := Object clone do(
    type := "port"
    install := method(
        writeln(self type, " ", call message name)
    )
    uninstall := method(
        writeln(self type, " ", call message name)
    )
    search := method(
        writeln(self type, " ", call message name)
    )
    help := method(
        writeln(self type, " ", call message name)
    )
    list := method(
        writeln(self type, " ", call message name)
    )
)

Command doString(System args slice(1) join(" "))

上のは、a.ioってファイルだったりすると、こんな感じで指定できる

nowel@MacBook: ~/workspace/test/io> ./a.io install
port install

でも、これだと、各メソッドが何も返却しないので、次の動きができない

nowel@MacBook: ~/workspace/test/io> ./a.io install hoge
port install

  Exception: nil does not respond to 'hoge'
  ---------
  nil hoge                             doString 1

ということで、少し変更する

#!/usr/bin/env io

// port.io
Command := Object clone do(
    __dump__ := Object clone do(
        name ::= nil
        forward := method(
            writeln("port ", name, " ", call message name)
        )
    )
    install := method(
        __dump__ clone setName(call message name)
    )
    uninstall := method(
        __dump__ clone setName(call message name)
    )
    search := method(
        __dump__ clone setName(call message name)
    )
    help := method(
        helpList := Object clone do(
            install := method(
                "Installs one or more addons" println
            )
            uninstall := method(
                "Uninstalls one or more addons" println
            )
            search := method(
                "Lists all packages which match the search parameters" println
            )
            forward := method(writeln("no such help: ",call message name))
        )
    )
    list := method(
        AddonLoader addons foreach(v,
            v name println
        )
    )
)

Command doString(System args slice(1) join(" "))

これで、こんな感じに動く。

nowel@MacBook: ~/workspace/test/io> ./port.io install hoge
port install hoge
nowel@MacBook: ~/workspace/test/io> ./port.io uninstall foo
port uninstall foo
nowel@MacBook: ~/workspace/test/io> ./port.io help install
Installs one or more addons
nowel@MacBook: ~/workspace/test/io> ./port.io help search
Lists all packages which match the search parameters

すごく簡単な仕組みだけど、できた。

ということで、io にパッケージマネージャとかが存在しないのが、面倒だなーと思った。
なので、coderepos で作ろうかと思う。
まだ、何も作れてないけど。思い立ったが吉日