Lazy aKari:わぁいLazy K あかりLazyKインタプリタだいすき

Posted on

Abstract

CPUのクロック数が頭打ちになり、1コアあたりの性能を向上させるよりもコア数を増やす事による並列性能向上が行われるようになった昨今では、並列化の行い易い純粋関数型プログラミング言語が注目されています。本研究で題材とするLazyKは、トークンが4つというシンプルさでありながら、チューリング完全な純粋関数型プログラミング言語です。

一方、近年のアニメシーンにおいて、アニメ本編よりもインターネット上での罵声の浴びせかけあいやステマばかりが着目され、アンチと信者のせめぎあいがより激しくなり荒廃が進んでいます。

7/2より放送されている「ゆるゆり♪♪」の主人公赤座あかりは、彼女の必殺技「\アッカリ~ン/」によって可視光における強い参照透過性を実現し、彼女のすべてを愛す性格によって、世界に愛をもたらしています。

本研究では、LazyKに赤座あかりの愛を組み合わせた独自言語「Lazy aKari」を実装することで、LazyKの啓蒙と世界に愛を広めました。

いつも通りの「トークン変えただけのゴミ言語のお時間」です。Brainfuckでやってる事が多い気がしますが、毎度毎度Brainfuckでやるのも流石に秋田ので別のにしました。トークンが4つで調度良かったのでLazyKです。で、毎度毎度手続き型で書くのも秋田ので今回はLISPで書いて見ました。ほとんどLISPの機能を使ったので随分楽ちんな実装となっております。

Scheme処理系の一つであるgaucheを使ってます。

Unlambdaスタイルのみに対応しております。トークンの対応は次の通り

  • わぁい⇨`
  • うすしお⇨s
  • あかり⇨k
  • だいすき⇨i
$ gosh akari.scm <ソースファイル>

として、入力を入れたらCtrl+D(WindowsではCtrl+Zらしい)を押して入力を確定すると、プログラムが実行されて結果が表示されます。

サンプル:echoプログラム

入力をそのまま返します。

大好き

これを保存してgosh akari.scm echo.txtとして実行し、入力したらCtrl+D(WindowsではCtrl+Z)を押してね。入力した結果がそのまま表示されます。

% gosh akari.scm echo.akari
yryr[Ctrl+D]
yryr

大好きはエコー…。そう、人と人との絆は、相互承認なのです(鼻で笑いながら

サンプル:Hello World

Hello Worldです。メッセージ変えたかったのですがLazyKは書ける気がしなかった()のでWikipediaのをそのまま使いました

わぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁい
 あかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかり
 うすしおあかり大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁいうすしおわぁい
 わぁいうすしお大好き大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁい
 わぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかり
 うすしおあかり大好きわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁい
 あかりうすしおあかり大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁい
 わぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁい
 わぁいうすしおわぁいあかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいわぁいうすしお大好き
 大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしお
 わぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいわぁい
 うすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしお
 わぁいあかりうすしおあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいあかりわぁい
 わぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいうすしおわぁい
 わぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいわぁいうすしお大好き
 大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしおわぁい
 わぁいうすしお大好きわぁいあかりわぁいわぁいわぁいわぁいうすしおわぁいわぁいうすしおわぁい
 あかりうすしおあかりわぁいわぁいわぁいうすしおわぁいわぁいうすしお大好き大好き大好き
 わぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁいうすしお大好き大好き
 わぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしお
 わぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいわぁいうすしおわぁい
 わぁいうすしお大好き大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁい
 あかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好き
 わぁいあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお
 わぁいあかりうすしおあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしおわぁい
 わぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいうすしおわぁいわぁいうすしお
 わぁいあかりうすしおあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁい
 うすしおわぁいわぁいうすしお大好き大好き大好きわぁいわぁいうすしおわぁいわぁいうすしお
 わぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好き
 わぁいあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしお
 わぁいあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいあかりわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいわぁいうすしお大好き大好き
 わぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいわぁい
 うすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好き
 わぁいあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしお
 あかり大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしお
 わぁいあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁいあかり
 わぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好き
 わぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしお
 あかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁい
 うすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁいわぁいうすしおわぁいわぁいうすしおわぁい
 あかりうすしおあかり大好きわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお
 わぁいあかりうすしおあかり大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり
 わぁいわぁいわぁいうすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうす
 しおあかり大好きわぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいうすしおわぁいあかりわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいわぁいうすしおわぁいわぁいうすしお
 大好き大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好き
 わぁいあかりわぁいわぁいうすしおわぁいわぁいうすしお大好きわぁいあかりわぁいわぁいうすしおわぁい
 あかりわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいわぁいうすしお
 わぁいわぁいうすしおわぁいあかりうすしおあかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁい
 うすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好きわぁいあかりわぁいわぁいうすしおわぁい
 わぁいうすしお大好きわぁいあかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいわぁい
 うすしお大好き大好きわぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好き
 わぁいあかりわぁいわぁいわぁいうすしお大好き大好きわぁいわぁいわぁいうすしお大好き大好き
 わぁいわぁいうすしおわぁいわぁいうすしおわぁいあかりうすしおあかり大好き

どうしたあかり。たまにはのり塩も食べないと。

% gosh akari.scm hello.akari
[Ctrl+D]
Hello, world!

かんたんなかいせつ

LazyKは上にあるように、4つのトークン`skiをつかいます。で、そのうちskiの3つは関数です。LISPで書くとこんな関数。

(define (S x) (lambda (y) (lambda (z) ( (x z) (y z)))))
(define (K x) (lambda (y) x))
(define (I x) x)

で、`というのは関数呼び出しを表します。つまり、`ikは(I K)で、“skiは( (S K) I)のことで、`s`kiは(S (K I) )ということです。関数は必ず1つだけ引数を取ります。以下可読性を重視してLISP形式で書きます。

Iは引数をそのまま返す関数なので簡単ですね。

Kは関数を返す関数です。つまり、(K 1)の戻り値は (lambda (y) 1)という「関数」で、((K 1) 2)としてさらに戻ってきた関数を呼び出すと1(=x)という結果になります。なんでこんな面倒な事をするかというと、LazyKの世界では関数は引数を1つしか取れないのですが、Kという関数にはどうしても2つ引数を取らせたかったからです。LazyKでは関数は関数を返すことができるので、関数を返す関数を作ることで引数が1個しかとれない問題を回避しています。これをカリー化と言うそうです。

Sはちょっと厄介ですね。((S K) K)が関数Iと等しい関数であることを確かめると理解できると思います。

((S K) K) -> (lambda (z) ( (K z) (K z))) ;定義にしたがって、xとyをKに置き換えただけ
-> (lambda (z) ( (lambda (w) z) (K z))) ;最初の(K z)を展開した。内側のラムダ式でwは使われないことに注目。
-> (lambda (z) z)
-> これはIそのもの

…うーん、面倒ですね。

数字は?

LazyKは上記でみた3つの要素しかありません。数字というリテラルはありません。えっ、でも計算くらいできないと困るじゃん。

そこでLazyKでは、数をチャーチ数というので表します。数とありますがやっぱり関数で、この関数に別の関数fを渡すと、fをn回適用してくれる関数を返してくれる関数です。「関数」がゲシュタルト崩壊してきてはあ?って感じですね。たとえば、チャーチ数で3を表す関数をchurch-3は、引数として関数fをとり、「f(f(f(x)))を返す関数」を返してくれます。つまり、渡す関数を「引数に1を足して返す関数」にすると、

(define (adder x) (+1 x)) ;1を足した引数を返す
((church-3 adder) 0) -> 3

というわけです。ちなみに、チャーチ数の0は(K I)です。

リストは?

よくわからないんですけどこれも関数で表します。LISPのcar/cdrなリストと同じような構造で、具体的にはリストの関数を_LIST_とすると、(_LIST_ K)とするとcarが、(_LIST_ (K I))とするとcdrをしたことになるらしいです…。同様にconsも定義できるそうです。 終了条件がわからない(lispでのpair?はどうやるの?)ので誰か教えてください。

入出力は?

入出力は上で紹介したチャーチ数とリスト構造を使います。LazyKのプログラムは関数を組み合わせて作られる大きな大きなひとつの関数なので、これへ引数として入力値のASCIIコードをチャーチ数で表したもののリストを入れて実行されます。で、その戻り値(チャーチ数のリスト)が出力というわけです。

なので、「大好き」(=i)だけのプログラムは、入力値をそのまま返すechoプログラムになったわけですね。

というかですね

LazyKは情報がなさ過ぎます。あと実をいうと二期はまだ見てないです(

二期みた

こwwwwwねwwwwwこwwwwwwwちゃんwwwwwwwwwwwwwwwwwwwたちwwwwwwwwwwwwwwwwwww

Freenetでは、どうやって匿名性を確保しているの?

Posted on

 今日はFreenetの話です。な、なぜいきなり…。なんとなく。一足早い夏休みのじゆうけんきゅうです。

おしながき

 Freenetは、ピュアなP2Pによる、匿名通信を実現するソフトです。WinnyやShareなどよりも遥かに匿名性が高く、Wikipedia曰くあの中国ですら追跡はできないらしいです†1

 で、今回公式サイトに転がってるオリジナル論文を読んだので、論文に書いてあった事とか、論文だけでは分からなかったので実装を直接触って調べた話を書いておきます。

Freenetのコミュニケーションの基本単位は「メッセージ」

 Freenetの基本的なイメージは、ネット上にたくさんの「ノード」と呼ばれるコンピュータがあって、それぞれが他のいくつかのノードと繋がっており、全体として大きな網目状のネットワークになっている、というものです。

20100526_01.png

 これらの中では、ノード同士が「メッセージ」をバケツリレー式にやり取りすることでコミュニケーションしています。TCP/IPとかのパケット通信と似てます。

 「メッセージ」には検索や追加など、いくつか種類があるのですが、どのメッセージも必ず次の属性は持っています。

  • メッセージを区別するユニークなID
  • メッセージの送信先
  • メッセージの送信元
  • HTL(Hops to Live)

 最後のHTLは、IPのTTLと大体同じです。メッセージがノードを通過するたびに一つずつ減っていき、0になった時点でメッセージが破棄され、破棄したノードは「失敗したよ」というメッセージを送信元に送り返します。論文だとTTLと表現されているのですが、実装ではHTLとなっているのでこちらで統一します。

基本的には「多段串」とかと同じ感じ

 匿名性の基本原理は、いわゆる「多段串」とあまり変わらないようです。

 この図において、AさんはCさんが送信したメッセージを、Bさんを経由して受信しています。BさんはAさんに、Bさん自身のアドレスしか教えておらず、送信者がCさんなのか、実はBさんとつながった別のノード、Dさんなのかは教えません。

20100526_02.png

 さらに言えば、BさんはCさんがオリジナル送信者なのかも知りません。実は、CさんもBさんと同じように、他の誰か、例えばEさんのメッセージを転送してるだけなのか、Bさんには分かりません。

 これを何回も繰り返すことで誰がオリジナルの送信者なのかどんどん分かりづらくなっていく、というわけです。

隣のノードへ対してはどうやって発信者を隠すの?

 私が疑問に思ったのは、自分のすぐ隣のノードには、自分がオリジナル発信者であるメッセージはわかってしまうのでは、ってことです。もしHTLの初期値が固定なら、隣のノードが、私から発信されたHTL=初期値のメッセージを受け取った場合、そのメッセージは間違いなく私がオリジナル発信者†2であるとバレてしまう可能性があると思います。

20100526_03.png

 この図で、BさんはCさんからHTL=18のメッセージを受け取っていますが、これはFreenetのデフォルトのHTL初期値と同じです。と、言う事は、このメッセージはまだ一度もHTLが減っていないメッセージなので、Cさんがオリジナル送信者であると推測できてしまうのでは無いでしょうか…?もちろん設定は変えられるので、必ずしもCさんがオリジナル送信者であるとは言い切れませんが、その可能性は高いと言えてしまいそうです。

 これは元の論文には何も書いていなかったので、実際にソースコードを読んで調べてみました。

とりあえず読む

 まず、HTLの初期値はどこで決定されるのでしょうか。freenet/node/Node.java内のこちらです。

        maxHTL = nodeConfig.getShort("maxHTL");

 設定値で固定です。…やはり、私の指摘したような問題が起こるのでしょうか?

 HTLを減らしているのは、同じソース内の、

    public short decrementHTL(PeerNode source, short htl) {
        if(source != null)
            return source.decrementHTL(htl);
        // Otherwise...
        if(htl >= maxHTL) htl = maxHTL;
        if(htl <= 0) {
            return 0;
        }
        if(htl == maxHTL) {
            if(decrementAtMax || disableProbabilisticHTLs) htl--;
            return htl;
        }
        if(htl == 1) {
            if(decrementAtMin || disableProbabilisticHTLs) htl--;
            return htl;
        }
        return --htl;
    }

 このメソッド。自分がオリジナル送信者の時はsource==null、転送するメッセージの場合はsource != nullみたいです。

 自分がオリジナル送信者のとき、source==nullなので処理は移譲されずこのメソッドが最後まで実行されます。decrementAtMaxがtrueな時に予めHTLがひとつ減らされる処理が入っているようです(自分が送信者なので、HTL=maxです)。これは、自分がオリジナル送信者であるメッセージについて、HTLを一つあらかじめ減らしておくことで「いえ、これは他の誰かから転送したメッセージですよ」と言っている事になります。

 ただし、このdecrementAtMaxはFreenetの起動時に決定されるパラメータ†3で、それ以降変更がないので、これだけではあまり十分であるとは言えないように思います。もしこれがFalseになった場合、やはり自分がオリジナル送信者であるメッセージだけ、HTL=最大(初期値)のメッセージを送る事になってしまうのでは?

 そこで、source != nullの時の移譲先を見てみましょう。freenet/Node/PeerNode.javaにあります。

    /**
    * Decrement the HTL (or not), in accordance with our
    * probabilistic HTL rules.
    * @param htl The old HTL.
    * @return The new HTL.
    */
    public short decrementHTL(short htl) {
        short max = node.maxHTL();
        if(htl > max)
            htl = max;
        if(htl <= 0)
            return 0;
        if(htl == max) {
            if(decrementHTLAtMaximum || node.disableProbabilisticHTLs)
                htl--;
            return htl;
        }
        if(htl == 1) {
            if(decrementHTLAtMinimum || node.disableProbabilisticHTLs)
                htl--;
            return htl;
        }
        htl--;
        return htl;
    }

 びっくりするくらい同じコードでした†4。このコードでは、隣のノードから受信したメッセージにおいても、HTLが最大(初期値)だったときに、HTLを減らさずに送る事がある事を示しています。つまり、隣のノードから来たHTL=最大(≒隣のノードがオリジナル送信者である可能性がある)のメッセージについて、HTLを最大値のままにしておくことで「いえ、これは私がオリジナル送信者なんです!」と言ってることになります。また、このパラメータは接続先ノードごとに設定しなおされるため、十分に掻き乱されると考えられます。

 このふたつが組み合わさることで、HTL=最大のメッセージを受け取った場合でも、そのノードがオリジナル送信者かどうか、もはやわからなくなります。他の誰かのメッセージをHTLを減らさずに転送したものかもしれないし、自分のかもしれないし、ということですね。

 さらに、隣のノードもそのように詐称するので、HTL=最大のメッセージを送ってきたノードがオリジナル送信者かかどうかはもちろん、直接隣接するノードかすらも分かりません†5。十分そうですね。

まとめ

 Freenet上では「メッセージ」をやり取りすることで情報が交換される。各メッセージはHTLという、IPのTTLに似たパラメータを持つが、オリジナル送信者の匿名性を守るため、最大HTLのメッセージについてはランダムで減らしたり減らされなかったりする。

まだ残る謎

 htl=1(メッセージが破棄される瀬戸際)のときにもランダムで減らしたり減らさなかったりしてるみたいですが、なぜ…??

どっちかっていうと鍵探索の方が技術的には面白いかも

 今回は論文を読んでいて疑問に思ったのが匿名性の部分だったのでそちらを書きましたが、論文全体を読んだ印象としては、どうやって中央サーバのないP2P上で検索するための「キー」とそれに対応した「情報」を結びつけ、検索させるのか、の方が技術的に面白いな、と思いました(小学生レベルの感想)。

その他

  • System.err.print()を使うとrun.sh consoleで起動したときのログで表示されるよ。
  • †1: 公式サイトの言語が中国語、フランス語、スペイン語なのがなんとなく印象的…
  • †2: 誰かのを転送したわけではないってこと
  • †3: maxHTLの設定を読み込む次の行でrandを使って決定されています
  • †4: でもちょこっとだけ変数名違う…。
  • †5: そのノードも詐称してるかもしれないからです

さきゅばす 2.0β1を公開しました。

Posted on

 先日のニコニコ超会議でお話した「さきゅばす2」ですが、やっと一般公開するための作業が終了したので公開します。

 DLはさきゅばす公式サイトから

大きく変わったフィーチャーをご紹介

ニワン語へ一部対応しました。

 ニコニコ動画内で使える謎の言語ニワン語に対応しました。対応命令はまだ限られていますが、ニコニコ超会議でもご紹介したProjectNivaや、Nicocococococo!などの動作は確認しております。

正式に投稿者コメントに対応しました。

 上記の変更に合わせ、投稿者コメントにも対応しました。

フルスクラッチで書き直し、Javaのインストールが不要になりました。

 従来のJavaとCを廃止し、すべてをC++とPythonで開発しなおしました。このため、従来必要だったJavaのインストールが不要になっています。(配布サイズが絶望的ですが…)。

動画の変換設定やNGの設定が非常に柔軟になりました。

 Pythonスクリプトで記述するようになったため、非常に複雑なルールも記述できるようになりました。ng-scriptフォルダやrecipeフォルダのサンプルをご覧ください。

フレームレートの低い動画でもスムーズに変換できるようになりました。

 元の動画のFPSが極端に低い(1FPSとか)場合、従来のさきゅばすではそのFPSに合わせてでしか変換できず、カクカクになってしまいましたが、今回書きなおしたため、任意のFPS(デフォルトで25FPS以上)で変換されるようになりました。上記のNicocococococo!でもこの機能が使われています。

変換する動画を一度に指定してしておけるようになりました。

 もうauto.batはいりません。複数の動画IDを登録しておけば、勝手に最後まで変換してくれます。

ありそうでなさそうでやっぱりありそうな質問と答え

使い方は?

 やっつけですが一応使い方を書いておきましたので参考にしてください。サポートは2chのスレでお願いします。

「***」っていう動画が変換できないんだけど…

 バグの投稿はこちらで受け付けています(匿名でできます)。Blogとかメールとか2chに投稿していただいても大丈夫ですが、たぶんこっちに登録した方が早く対処されると思います(他のチャンネルまで目を通しきれてないです、すいません…) その時は__download__内のログの内容とか教えてください(ユーザーIDやパスワードとかは消しておいてね!)。

なんか「変換エラー」が出るんだけど…

 __download__フォルダの中身を全部消してみてね。

ニコニコ動画がアップデートして動画がDLできなくなったんだけど…

 上記でも報告を受け付けてますが、Pythonに詳しい場合はext\SaccubusFront内のスクリプトを書き換えても対処できます。対処したらパッチにしてぜひ投稿してくださいね!

「***」っていう機能ないの?

 バグと同じところで受け付けてます。でもせっかくだから一緒に開発しましょう!! もちろん新機能パッチの投稿もウェルカムです†1

なんかアンチウイルスソフトが反応するんだけど

 「『ffmpegを起動する』バッチファイルを生成して起動」する、という二段構えの形式のため、反応するようです。「さきゅばす」が信用できると思う場合は切ってから実行してみてください。

というわけで私はしばらくこもる

 他にやらなきゃいけない事があるのに随分これに時間を費やしちゃったので、バグとかあってもしばらく対応できません。すいません。その時は2chで配ってる旧バージョン使ってね!!!

  • †1: 私の独断でリファクタの可能性や激しい変更の場合は一時保留の可能性もあります、ご了承下さい…。

昨日超会議で話した時のスライドをうpしておきます

Posted on

昨日の超会議ニコニコ学会βはお疲れさまでした。なんか顎のあたりが青々としててすいませんまだタイムシフトは誰でも見れます!

タイムシフトで見てたら「あとでうpお願いね」って書いてあったのでうpしておきます!

[全画面/表示されない場合]

なお、さきゅばすへのパッチ受付などはメールの他、公式サイトのチケット一覧などに投げていただいても対処できます。

あとサポートを2ちゃんスレッドに投げといて言うのも憚られるものがあるのですが、2ちゃんねるはISP規制のせいで書けないです、ごめんなさい…(一応読んでます)。

 

5月下旬まではあんまり開発できない感じです。。。あとはGUIのラッパーがあればなんとかって感じなんですが…んー。ニワン語の仕様書起こしとかソースコードガイドとかも書きたい…。

明日話す「さきゅばす2.0」、α版のソースコードを公開しました

Posted on

 昨日宣言したので、一応公開します!超会議の準備があるので、ちょっと説明を書いてる暇はなさそうですorz

 明日、ニコニコ超会議内の「第二回ニコニコ学会β」の1コーナー、「コメントアートセッション」にて、このプログラムについてお話します。

 なお、ライセンスはGPLv3です。

Saccubus

 本体です。ffmpegのフィルタとして動作し、コメントを実際に動画に焼き込みます。C++製。

git clone git://git.sourceforge.jp/gitroot/saccubus/Saccubus.git

SaccubusFront

 GUIなどを備えるフロントエンドです…と言いたいところですが、まだありません。現状では上のさきゅばす本体から呼ばれて、動画やコメントのダウンロードなどを行なっています。Pythonです。

git clone git://git.sourceforge.jp/gitroot/saccubus/SaccubusFront.git

Nekomata – The Open Niwango

 ニコニコ動画で使える特殊な言語「ニワン語」のフリーでオープンな実装です。C++製です。

git clone git://git.sourceforge.jp/gitroot/saccubus/Nekomata.git

 いよいよ明日です、正直なんだかドキドキしてきました。。。おて柔らかに、お願いしますm(_ _)m

今度の日曜日「ニコニコ学会β」で話します!

Posted on

 こんにちは!

 ついに明日からニコニコ超会議ですね!

 今回、このニコニコ超会議内で開催される、第二回ニコニコ学会βで登壇者として話すことになりましたので、唐突ですがお知らせします!

さきゅばす2.0

 5年くらい前に作って割りと放置してた、ニコニコ動画コメント付き動画保存ソフト「さきゅばす」ですが、今回、1からC++とPythonで書き直し、ニワン語ニコスクリプトにも対応したバージョンを作成しました。

 ニワン語はニコニコ動画で動く特殊なスクリプト言語で、ニコ動で動く音ゲーLispインタプリタなどの「謎の技術」の謎を支えています。

 比較動画はこちら。どうしても重くなりがちな「謎の技術」動画も、変換してしまえばサクサクと見れてしまいます。

 また、このプレイヤでは、1フレームずつコマ送りにしながら操作しつつ変換が出来るため…TASも作れます!

 この新しいさきゅばすについて、紹介と技術的な話ができたらな~と思ってます。

「コメントアート」セッションにてお話します!

 二日目(日曜日)の12:50から行われる、「コメントアート」セッション内でお話することになっています。生放送もやるそうなので、当日これない方もぜひ!アレげな格好するから生放送のが細かいところが見えなくて都合がいい

 私はソフトウェア技術の話をしますが、セッション全体としてはニコニコ動画の「コメントアート」がメインです。原稿の下書きを見せて頂いたのですが…凄まじいですよ…!コメントってこんなに進化してたんですね~。

ソースコードは明日公開。

 ちょっとまだバイナリ配布できるようなUIなどが未実装なのですが、ニワン語のインタプリタ「ねこまた」と動画変換部分のコアについて、明日、ソースコードを公開します。

C言語標準ビルトイン型で関数オーバーロードしてはいけない

Posted on

可変長引数とva_listでオーバーロードしてみた

 Cの標準関数には、可変長引数を取るsprintfと、その引数をva_listで受け取るvsprintfの2つがあります

int snprintf(char *str, size_t size, const char *format, ...);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

 Cにはオーバーロードの仕組みがありませんから、引数の渡し方が違うだけの関数の名前が変わってしまうのもやむなしです。でも、C++ならオーバーロードをサポートしていますから、これらをこんな感じで一緒にできたら嬉しいな~と思いました。

std::string format(const std::string& format, ...);
std::string format(const std::string& format, va_list ap);

 が、しかし。このオーバーロードは64bit環境でのみ、*たまたま*うまく行きますが、32bit環境では失敗します。

 サンプルを使ってしらべましょう

#include <string>
#include <cstdarg>
#include <cstdio>


std::string format(const std::string& format, ...)
{
	printf("FORMAT 1 CALLED\n");
	return ""; /* サンプルなので未実装 */
}
std::string format(const std::string& format, va_list ap)
{
	printf("FORMAT 2 CALLED\n");
	return ""; /* サンプルなので未実装 */
}

int main(){
	va_list list;
	
	format("format: %s", "Hey!");

	return 0;
}

 さて、main内でのformat呼び出しに注目してください。第二引き数はconst char*なので、最初の可変長引数の関数が呼ばれるのを期待したいところです。

 実際、Fedora 16 x64(gcc 4.6.3)でビルドして実行すると

% ./test 
FORMAT 1 CALLED

 パチパチパチ。

 ところがしかし。Windows Vista32bitのMinGW(gcc 4.6.1)やUbuntu 11.04 32bit(gcc 4.5.2)で同じものをビルドすると…。

$ ./test.exe
FORMAT 2 CALLED

 後者が呼ばれてしまいます…な、なんで…?

ビルトイン型の実際の型を調べるには?

 オーバーロードがうまくいっている、という前提で考えて有り得そうなのは、va_listの実際の型が違う…のかもしれません。

 というわけで、調べてみましょう。g++のEオプションを用いると、プリプロセッサをすべて展開できます。

$ g++ -E test.cpp | grep typedef | grep va_list
typedef __builtin_va_list __gnuc_va_list;
typedef __gnuc_va_list va_list;

 むむむ、ヘッダファイルだけでは解決できませんね…。

 そこで、gdbの出番です。ptypeというコマンドを使うことで、実際の型を調べることができます。

 まずは、Fedora 16 64bitでやってみます。

% gdb test 
(gdb) break main
(gdb) run <- mainの一番上でストップします
(gdb) n <- 一行進めないと、スタック上にlistが定義されない
FORMAT 1 CALLED
22		return 0;
(gdb) ptype list <- main関数で定義されているva_list list;の定義を調べる
type = struct typedef __va_list_tag __va_list_tag {
    unsigned int gp_offset;
    unsigned int fp_offset;
    void *overflow_arg_area;
    void *reg_save_area;
} [1]

 というわけで、何かよくわかりませんが、va_listは構造体らしいです。なるほど、それならちゃんと可変長引数の方が呼ばれるのは納得です。

 64bit環境では構造体なら、32bit Windowsだと…。

(gdb) ptype list
type = char *

 期待通り!va_listの本当の型はchar*でした。だからオーバーロードでchar*を渡したらこっちが呼ばれてしまったんですね。

 これらの違いはなぜ起きるのでしょうか?

 おそらく、64ビットと32ビットでの呼出規約の違いに依るのだと思います。32bitのgccでは、引数をすべてスタック上(=アドレスがある)に格納されるため、va_listは単純なchar*で良いのですが、64bitのGCCでは基本的にレジスタ上に置くため、上記のように構造体で管理しているのでしょう。

 こういった事がありますから、型が明確でない場合はオーバーロードに使うのはやめたほうが良いですね…。

Visual Studio 2010だとどうなんのさ?

 同じリポジトリ内に入れておきました。私は32bit版windowsしかないので、そちらで試したところ、

20110325.png

 …どうやら、同じみたいです…。64bit版は知らないので、誰か試したら教えてね。

達人プログラマー:10年前の最先端と今の普通

Posted on

一部では有名?な「達人プログラマ」を、大学の図書館で見つけたので読んでみました。

職業プログラマ向けの啓蒙書、と言ったところ。純粋なプログラミングの話だけでなく、お客さんや同僚と一緒に仕事をしていく事に関してや、(私の好きではない)人生論っぽいところもあります。

で、ぶっちゃけなのですが、2012年となった今、特に読まなくて良いと思います

10年前の最先端は、今の普通になった

読んでもしょうもない、くだらない内容が書いてあるというわけではなくて、もはや今すでに十分浸透していて、インターネットで当然のように言われてる内容が多いからです。

「ソフトウェアの直行性」、つまり「ソフトウェアの一部は他の部分と独立してるべき」というのは本当によく言われてることですし、「DRY(Don’t Repeat Yourself)」も、Ruby on Railsという有名かつ優秀なサンプルがあります。本で自然言語で読まなくても、コードを書きながらDRYってどういう事なのか、理解してる方は多いのでは。「テスト駆動開発」もどこかで聞かれた事があるでしょう。

奥付をめくってみると、2000年11月の出版でした。ITバブルとか、あの頃です。20世紀です。もう11年以上経っています。当時はきっと最先端だったのでしょうが、今は十分に浸透した考えばかりです。11年前の本でわざわざ読むより、インターネットで検索して、実例をたくさん見たり、いろいろな人の意見を検索して調べていった方が、きっと新鮮で刺激的で、何より「Pragmatic†1」な情報が手に入ると思います。

エディタの紹介も、Brief(DOSのエディタ!)だったり、ソースコード管理システムの例がCVSやらSCCSだったり…2000年はSVNがやっと出来た頃(!)なので、すごーく隔世を感じます。ITバブルでどっと混むでIT革命†2な、あの頃へ戻るタイムマシンに乗るような楽しさは有るかも知れませんね。

10年前は最先端だったけど、今でもそんなに…

表明プログラミングEiffleSatherアスペクト指向プログラミングは未だにメジャーでは無いですね…。このあたりは読んでいて面白かったです。

あと「どこでもいつでも何でも自動化」というコンセプトは、理解はされてても、まだそこまでメジャーじゃないきがします(私の周りだけかな?)。確かに自分でスクリプトを書いて自動化すると楽なのですけど、最初書くのが結構おっくうなんですよね…。もっと楽に自動化できるようなツール出来ないかなー!

  • †1: 原題:Pragmatic Programmer
  • †2: 本当に聞かなくなったワードばっかり!

「モンティ・ホール問題」を実際に実験して確かめよう

Posted on

 モンティ・ホール問題というのをご存知でしょうか?

 大学受験生をやっていた時に、確か東工大の問題か何かで題材となっていて、それで知ったお話です。今日は国公立大学の入試日ですし、ついでなので公開しちゃいます!…懐かしいですね、去年はエア東大合格(?)して胴上げされてました。あれから1年かー。

モンティ・ホール問題

 モンティ・ホール問題は、こんな問題です。

 とある視聴者参加ゲームショー番組のハイライト。

 あなたは3つのドアの中から、一つを選び、開けることができます。3つのうち、一つのドアの先にだけ景品がありますが、残りの2つの先には…残念、ヤギが居ます。

 さて、まずあなたはこの3つの中から一つを選びました。次に、どのドアが正解かを知っている番組の司会者が、残った2つのドアのうち、「正解でない方」を一つ開けました。

 残るドアは一つ。あなたは、今のドアから、この残ったドアに変える自由があります。

 この場合、変える方がトク(当たりやすい)でしょうか?それとも、最初の選択を貫いたほうが得でしょうか?

 「結局2つのうちから一つ選ぶんだから、どっちも一緒でしょ?」と当時の私は思ってしまったのですが…実は変えたほうがトクです!

なっとくいかない!!

 よろしい、ならば実験だ!JavaScriptだっ!

 JavaScript版のメルセンヌ・ツイスタのライブラリがあったので、これを使ってみました。乱数は完璧です。

「モンティホール問題」実験プログラム

 20120225.png

 ご納得頂けたと思います(ドヤ

な、なんで?

 冷静に考えれば、わかります。

 最初にハズレを選んでいた場合、最後に残るドアは正解のドアですから、「変えた」場合に景品がもらえます。

 逆に、最初アタリを選んでいたら、「変えない」場合だけ景品がもらえます。

 よって、「変える」場合にもらえる確率は最初ハズレる確率で2/3。「変えた」場合にもらえるのは最初アタる確率で1/3。

 実験通りですね!

えーやっぱり納得いかない

 「100個のドアのうち、最初に1つのドアを選びます。残った99個のドアのうち、一つを残して他98個のハズレのドアを司会者が開けてくれます。あなたは変えますか?」と、数を増やしてみるという説明で私は理解しました。いかがでしょう?

 こういった話に興味がある場合は、「ベイズ推定」について調べてみてくださいね。

派生クラスへの「変身タイミング」のC++とJavaの違い

Posted on

恥ずかしながら、知らなかったので投稿です。めちゃ細かい話です。ソースはこちらZIPはこちら。

まとめると

派生クラスの初期化の際には、原則的に基底クラス部分を初期化した後に、派生クラスに「変身」して、派生クラス部分が初期化されるのですが、

  • C++は、基底クラスのコンストラクタが終わるまで派生クラスに「変身」しない。
  • Javaは、基底クラスのコンストラクタの実行開始時からいきなり派生クラスに「変身」済み(ただしフィールドを除く)

オブジェクト指向といえばクラス、クラスといえばオブジェクト

オブジェクトといえば初期化、初期化といえばコンストラクタ!

というわけで、こんなオブジェクト継承関係を考えてみましょう。(Sample1.java)

public class Parent{
	public Parent(){
		System.out.println("親コンストラクタだよー。");
	}
	public void method(){
		System.out.println("親のメソッドだよー。");
	}
}
public class Child extends Parent {
	public Child(){
		super();
		System.out.println("子コンストラクタだよー。");
	}
	public void method(){
		System.out.println("子供のメソッドだよー。");
	}
}

ええと、特に意味のある例が思い浮かばなかったので、安直にParentとChildです。すいません。

この状態で

public class Launch{
	public static void main(String args){
		Child child = new Child();
		child.method();
	}
}

とすると、

% java Sample1 
親コンストラクタだよー。
子コンストラクタだよー。
子供のメソッドだよー。

と表示されます。これは予想通りですよね。親クラスのコンストラクタで、親クラスのフィールドが初期化されたあとに、その派生クラスの子クラスのコンストラクタが呼ばれて、子クラスが初期化されます。入門書通りです。

コンストラクタ中に子クラスのメソッドを呼ぶ…?

さて。親クラスを元にした派生クラスを色々と作って、それらの種類で処理を分ける…というのが、一般的なケースです。

とするなら。もしかすると、基底クラスの初期化の最中に子クラスの初期化をして、その結果を使いたいと思うかもしれません。

そう思ったら、こんなコードを書くかも。(Sample2.java)

abstract class Parent{ //抽象クラスになりました。
	public Parent(){
		System.out.println("親コンストラクタだよー。");
		/* 全派生クラス共通の初期化処理がこの間に入ってる(という気持ち) */
		final int result = doInit(); //派生クラスごとで違う初期化処理
		/* ここも全派生クラス共通の初期化処理が入ってる(という気持ち) */
		System.out.println("親コンストラクタ終わりだよー。結果は"+result+"だったよー。");
	}
	public void method(){
		System.out.println("親のメソッドだよー。");
	}
	protected abstract int doInit();
}
class Child extends Parent {
	public Child(){
		super();
		System.out.println("子コンストラクタだよー。");
	}
	public void method(){
		System.out.println("子供のメソッドだよー。");
	}
	protected int doInit(){
		System.out.println("子供が初期化してるよー。");
		return 184; //特に意味はない
	}
}

実行すると、

% java Sample2
親コンストラクタだよー。
子供が初期化してるよー。
親コンストラクタ終わりだよー。結果は184だったよー。
子コンストラクタだよー。
子供のメソッドだよー。

というわけで、Javaでは、親クラスのコンストラクタを実行中でもすでに「this」は子クラスに「変身」しており、親クラスのコンストラクタから、子クラスのメソッドを呼ぶことができます。

インスタンス変数は二回初期化される

ただし。インスタンス変数は違います。先程のソース、親子両方にfieldというフィールドを入れて、親クラスを0、子クラスを1と宣言時に初期化すると…。(Sample3.java)

% java Sample3 
親コンストラクタだよー。fieldは0だったよー。
子供が初期化してるよー。
親コンストラクタ終わりだよー。結果は184だったよー。
子コンストラクタだよー。fieldは1だったよー。
子供のコンストラクタだよー。

親クラスの値0で初期化されたあと、子クラスのコンストラクタ実行前に再度初期化されます。

親クラスのコンストラクタを呼ぶ前は「何者でもない」

また、親クラスのコンストラクタ実行前は「何者にもなっていない透明な存在」です。え?どういう事かって?

こういうことはできません。

abstract class Parent{ //抽象クラスになりました。
	public Parent(int param){
		/* なにか処理 */
	}
}
class Child extends Parent {
	public Child(){
		super(getParam()); // <= 残念、コンパイルできない!
	}
	protected int getParam(){ /* 基底クラスの初期化に使う値を、事前に計算したいなあ、と */
		return 184; //特に意味はない
	}
}

「スーパータイプのコンストラクタの呼び出し前は this を参照できません。」と言われてエラーでした。

結局、それぞれの実行タイミングはどうなってるの?

クラスのフィールドの初期化は、コンストラクタ内だけでなく、フィールドの宣言時にも行うことができます。大方予想はついていると思いますが、一応調べておきましょう。(Sample4.java)

abstract class Parent{
	protected static final int log(String msg){
		System.out.println(msg);
		return 0;
	}
	protected int field = log("親クラス・フィールド宣言時");
	public Parent(){
		log("親クラス・コンストラクタ");
	}
}
class Child extends Parent {
	protected int field = log("子クラス・フィールド宣言時");
	public Child(){
		super();
		log("子クラス・コンストラクタ");
	}
}

とすると、

% java Sample4
親クラス・フィールド宣言時
親クラス・コンストラクタ
子クラス・フィールド宣言時
子クラス・コンストラクタ

というわけで、原則的に「フィールド宣言→コンストラクタ」が親から子にわたって続く感じです。コンストラクタ内からフィールドにはアクセスできますから、まあ想像通りですね。

ただし、Javaの場合、親クラスのフィールド宣言開始時ですでに子クラスに「変身」していて、メソッドがオーバーライドされていた場合、子供クラスのコンストラクタが呼ばれる前でも、そちらが呼ばれてしまいます。複数人で開発していた場合、この仕様が思いがけないバグになるかもしれません。

明確に子クラスの責任としたいメソッドじゃない場合は、コンストラクタから呼ばれるメソッドはfinal宣言したほうがいいかもですね。

ソビエトロシアC++では親クラスが子クラスに変身する!

最後のJavaと似たようなコードを書きました。(Sample.cpp)

#include <iostream>
#include <string>

using namespace std;

int log(const string& msg){
	cout << msg << endl;
	return 0;
}

class Parent{
	protected:
		int attr;// = log("親クラスフィールド初期化"); //これはできないんでした。
	public:
		Parent():
		attr(log("親クラスフィールド初期化"))
		{
			log("親クラスコンストラクタ開始");
			doInit();
			log("親クラスコンストラクタ終了");
		}
		virtual ~Parent(){
		}
		virtual void doInit(){
			log("親クラスの初期化処理");
		};
		virtual void method(){
			log("*親メソッド*");
		}
};

class Child : public Parent{
	protected:
		int attr;
	public:
		Child():
		Parent(),
		attr(log("子クラスフィールド初期化"))
		{
			log("子クラスコンストラクタ");
		};
		virtual ~Child(){
		};
		virtual void doInit(){
			log("子クラスの初期化処理");
		};
		virtual void method(){
			log("*子メソッド*");
		}
};

int main(){
	Child child;
	child.method();
	return 0;
}

そもそもフィールド宣言時に初期化できなくて、コンストラクタの初期化子に書くんでしたね。さてコンパイルです。

% ./test 
親クラスフィールド初期化
親クラスコンストラクタ開始
親クラスの初期化処理 ← 子供のdoInit()じゃなくて、親のdoInit()が呼ばれてる!
親クラスコンストラクタ終了
子クラスフィールド初期化
子クラスコンストラクタ
*子メソッド*

コードの位置からすぐわかるように、Javaと同じように「フィールド初期化→コンストラクタ」の流れなのは同じです。

が、C++は親クラスの初期化が終わるまでは「厳密に親クラス」で、子クラスではないので、子クラスのメソッドを呼ぶことはできません。

子クラスに勝手にメソッドがオーバーライドされて…という事はなくなるため、この仕様はこの仕様で合理的な気がします。

まとめ

えっと、まあ、その、普通に使ってても結構気づかないことって多いんだなあ…って感じです…。