2010/06/18


githubの中の人が書いた、node.js上で動くHTTP対話コンソールアプリ「http-console」をGoで書いてみた。
mattn知ってる人なら、だいたい名前が想像出来るかな...
そう。

http-gonsole


かっこ悪い?しるか

mattn's http-gonsole at master - GitHub

peak HTTP like a local. (the simple, intuitive HTTP console, golang version)

http://github.com/mattn/http-gonsole
使い方はhttp-consoleと同じにしてあります。node.js版も試してみたんですが、いかんせんインストール面倒くさい(コンパイル重い)し、http-consoleくらいの機能の割にはnode.jsは規模が大きすぎる。
最近go言語にハマってるってところもあったので書いてみました。
インストールにはreadline-goが必要です。
あと、コンテンツがJSONだった場合での色付き表示はサポートしていませんが、フツーに便利です。
http-gonsole
接続元の情報でCookie作ってるかもしれないので、一応隠しとく。
Cookieにも対応してます。
あと、あれだな。「POST!」コマンドあたりでPOSTデータをエディタで編集出来る機能が欲しいな。
Posted at by



2009/12/31


最近、GoのGTKバインディングを作ってるのだけど、先日ようやく簡易twitterクライアントを作れるまでに至ったのだが、そのtwitterクライアントの「タイムライン更新」ボタンを押した時に画面をブロックさせずに画面を更新する方法を考えてた。
Perl-GTKならばCoroを使ってこうするだろうか。 use strict;
use warnings;
use Gtk2 '-init', '-threads-init';
use AnyEvent;
use Coro;
use Coro::Timer;

my $window = Gtk2::Window->new('toplevel');
$window->signal_connect(
    destroy => sub {
        exit;
    }
);
my $vbox = Gtk2::VBox->new;

my $button = Gtk2::Button->new('click me');
$button->signal_connect(
    clicked => sub {
        $button->set_sensitive(0);
        async {
            Coro::Timer::sleep 3;
            Gtk2::Gdk::Threads->enter;
            $button->set_sensitive(1);
            Gtk2::Gdk::Threads->leave;
        };
    }
);
$vbox->add($button);

$window->add($vbox);
$window->show_all;

Gtk2->main;
ここでCoro::Timer::sleepしているのは、生のsleepを使うとCoroがスケジュール割り当てとして介入出来なくなるのを回避する為。非同期で動作はするものの、スレッド動作になるので「Gtk2::Threads->enter」と「Gtk2::Threads->leave」でUIに関連する処理をブロックしなければなりません。つまり例えばボタン押下時の処理が5段階あり、その都度UIの何かしらを変更しなければならない場合、「Gtk2::Threads->enter」と「Gtk2::Threads->leave」でそれぞれブロックしなければならない事になります。
平たく言うと、スレッドを使う予定の無かったUI処理を、スレッド対応にするには多少のコード修正が必要になるという事です。
今回、goで作った簡易twitterクライアントの「タイムライン更新」ボタンを非同期対応するのに費やしたコードはたった2行。元のコード
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        button.SetSensitive(false);
        if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
            // タイムライン取得、表示処理
        }
        button.SetSensitive(true);
    }, nil);
となっていたコードに「go」で実行されるブロックを追加しただけ。
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        button.SetSensitive(false);
        go func() {
            if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
                // タイムライン取得、表示処理
            }
            button.SetSensitive(true);
        }();
    }, nil);
何が言いたいのかというと、groutineはgo組み込みであって、Coroがsleepやhttp_getを置き換えようとしている(CORE::GLOBAL上書きもあるけど)物とは違い、言語として非同期をサポートしているという事。もちろんgoにおいてもボタンクリック時にsleepを呼ぶとUIはブロックされるのだけど、実際sleepなんて使わないし、そもそもgoroutineはスレッドじゃない。
そう、goroutineはスレッドじゃない。
A Tutorial for the Go Programming Language

Go has its own model of process/threads/light-weight processes/coroutines, so to avoid notational confusion we call concurrently executing computations in Go goroutines.

http://golang.org/doc/go_tutorial.html
実際にはgoが内部でscheduleを行って実行する処理連鎖で実現している。なのでgtkを呼び出す場合においてもgdk_threads_enter/gdk_threads_leaveを呼び出す必要もない。なぜなら並行とは言えど同時実行ではないのでコンテキストスイッチも意識しなくて良い。
故に上記の様な 「go func(){}()」ブロックだけで非同期処理が書けるという訳。例えばcursesでCUIアプリを書いても画面が崩れる事は無いだろう(未確認)。

UIの開発に有利な言語にはある程度、共通する物があると思う。
  • 匿名関数をコールバックとして扱える
  • サードパーティでも良いので非同期処理の仕組みがある
  • GCが効く
この内、2個か3個あてはまるとUIを扱う上で有利になると思う。Javascriptはこの点(組み込みで非同期が扱える:というかそういうものしか扱えない?)素晴らしいし、jQueryを使えばAjax/UIとして作り手側はかなりの恩恵が得られる。Perlは「sub {}」で匿名関数が使えるがので優秀だが非同期となるとスレッドに頼らざるを得ないのが難点。rubyも同様。pythonは匿名関数が使えないのが少し悲しい(でもpygtkは好きだ)。結局の所、メインループと非同期処理をどう扱うかがネックになっている。例えば各言語でCOMを扱う場合、STA(Single Thread Apartment)しか対応していないCOMは、言語側でメインループをグルグル回すしかない。まぁ言語として用途が違うんだ(JavascriptはそもそもUI記述言語だろ...とか)から、比べちゃならんだろ...とも言えるし。
goにおいては上記3点あてはまるが、デストラクタが無いのが一番の難点。
これからのUIはどんどん非同期な物になって行くんだろうな。

10年程前のUIと言えば、非同期なUIはあまり見られなかった。VB6においても定期的に「DoEvents」を実行してプログレスバーを更新するという、なんちゃって非同期処理が横行していた頃だ。最近はVB.NETでコンポーネント自身がスレッドサポート(実際にはデリゲート)しているので簡単に非同期処理が書ける様になったが、これら全てにおいてもやはり
非同期対応するつもりの無かったUI処理を、非同期対応にするには多少のコード修正が必要になる
には違い無いし、その点でgoには期待している。
Posted at by



2009/12/22


Go binding for GTK GoのGTKバインディングを現在作業中なのですが、ようやく簡単なTwitterクライアントくらいならば作れる程度に出来上がってきたのでご報告。
mattn's go-gtk at master - GitHub

Go binding for GTK

http://github.com/mattn/go-gtk
イベントハンドラがJavaScriptっぽく書けるので、結構見通し良いコードになります。
package main

import "gtk"
import "gdk-pixbuf"
import "unsafe"
import "http"
import "json"
import "io"
import "os"
import "strconv"
import "strings"
import "reflect"

func url2pixbuf(url string) *gdkpixbuf.GdkPixbuf {
    if r, _, err := http.Get(url); err == nil {
        n, _ := strconv.Atoi64(r.GetHeader("Content-Length"));
        t := r.GetHeader("Content-Type");
        b := make([]byte, n);
        io.ReadFull(r.Body, b);
        var loader *gdkpixbuf.GdkPixbufLoader;
        if strings.Index(t, "jpeg") >= 0 {
            loader, _ = gdkpixbuf.PixbufLoaderWithMimeType("image/jpeg");
        } else {
            loader, _ = gdkpixbuf.PixbufLoaderWithMimeType("image/png");
        }
        loader.SetSize(24, 24);
        loader.Write(b);
        loader.Close();
        return loader.GetPixbuf();
    }
    return nil;
}

func main() {
    gtk.Init(&os.Args);
    window := gtk.Window(gtk.GTK_WINDOW_TOPLEVEL);
    window.SetTitle("Twitter!");
    window.Connect("destroy", func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        gtk.MainQuit();
    }, nil);

    vbox := gtk.VBox(false, 1);

    scrolledwin := gtk.ScrolledWindow(nil, nil);
    textview := gtk.TextView();
    textview.SetEditable(false);
    textview.SetCursorVisible(false);
    scrolledwin.Add(textview);
    vbox.Add(scrolledwin);

    buffer := textview.GetBuffer();

    tag := buffer.CreateTag("blue", map[string] string {
        "foreground": "#0000FF", "weight": "700" });
    button := gtk.ButtonWithLabel("Update Timeline");
    button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
        if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
            n, _ := strconv.Atoi64(r.GetHeader("Content-Length"));
            b := make([]byte, n);
            io.ReadFull(r.Body, b);
            j, _ := json.Decode(string(b));
            arr := reflect.NewValue(j).(*reflect.SliceValue);
            for i := 0; i < arr.Len(); i++ {
                data := arr.Elem(i).(*reflect.InterfaceValue).Elem().(*reflect.MapValue);
                icon := data.Elem(reflect.NewValue("user")).(*reflect.InterfaceValue).Elem().(*reflect.MapValue).Elem(reflect.NewValue("profile_image_url")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                var iter gtk.GtkTextIter;
                buffer.GetStartIter(&iter);
                buffer.InsertPixbuf(&iter, url2pixbuf(icon));
                name := data.Elem(reflect.NewValue("user")).(*reflect.InterfaceValue).Elem().(*reflect.MapValue).Elem(reflect.NewValue("screen_name")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                text := data.Elem(reflect.NewValue("text")).(*reflect.InterfaceValue).Elem().(*reflect.StringValue).Get();
                buffer.Insert(&iter, " ");
                buffer.InsertWithTag(&iter, name, tag);
                buffer.Insert(&iter, ":" + text + "\n");
            }  
        }
    }, nil);
    vbox.PackEnd(button, false, false, 0);

    window.Add(vbox);
    window.SetSizeRequest(300, 400);
    window.ShowAll();
    gtk.Main();
}
実行すると以下の様な画面になります。
go-gtk-twitter-client
まだ、完成じゃないです。がんばります!
Posted at by