これは日本での出来事である.
ある男が銃刀法によって逮捕された.電車の中でナイフを出し、
自分の腕を切ったと報道された.その後、てきとうなコメンテーター
がお笑い事としてコトの粗筋を説明したことには、
男はこの一ヶ月で20回ほど、夜中、閑散とした電車内にて
少ない乗客にふらりと立ち寄り、その姿はフーテンの酔っ払いの
ようだったという. 突然ナイフを出し、その刃渡りをぎらりと
見せると、自分の腕にそれを当て、「切ってほしくなかったら、
2000円出せ」という脅迫をした.少額であることから19回成功して
おり、通報を受けた鉄道員により逮捕されたという.
Ubuntu 11.4 を使ってたけど、アップグレードする理由もないけれど
古いのをずっと使ってる意味もないので、11.4->11.10->12.4->12.10
と3段階アップグレードすることにした.飛ばしてアップグレードは
できないと聞いたので.(ほんとうかな)
そしたら11.10->12.4のアップグレードが完了して、再起動したら
12.4が起動するはずの画面でずっとマウスポインタが点滅して、
結局起動しなかった.仕方ないので、12.10をUSBからインストールした.
昨日全く寝ずに、そのまま東京Node学園祭に行き(はっきりいってくだらなかった.同じ学科の連中の発表のほうがもっと刺激的だ) その帰りの足でアキバで小林ゆうを見た.遠近法の効果により 豆粒ほど小さかった.
今年の目標を決めた.あと1ヶ月半だけなのだから具合がいい. 人とあまり馴れ馴れしくしないこと. 自立すること.
ググっても正規表現マッチしか出てこないけれど、関数型言語 におけるパターンマッチのはなし.
type Nat = Zero | Succ of Nat
match n with
| Zero -> "zero"
| Succ(Zero) -> "one"
| _ -> "takusan"
これは switch 文以上につよい. 入れ子にできるから. これをjavascriptでどう表現するか. 1.8の構文を用いれば、 let
による分割代入ができて、
パターンマッチだ!とか書いてる記事があるけれど,
私の知ってる パターンマッチ
という言葉遣いと違う.
その記事がいうのは,
let [[a,b],c,d] = [[1,2],3,4]
が可能という意味で、それはOCamlでは
match [[1;2];3;4] with
| (a::b::[])::c::d::[] -> ...
| _ -> failwith ""
とまあ確かにパターンマッチでよくやる.
私がやりたいパターンマッチは最初にかいたやつ. どう表現するか.
私が昨日一昨日発見して今気に入ってるのはオブジェクトのキーにパターンを列挙するもの. パターンマッチのパターンは有限に列挙するのだからこれは可能だ.
match["Zero"] = "zero"
match["Succ(Zero)"] = "one"
if (n in match) match[n];
else "takusan";
ほら、できた. 問題は、オブジェクトのキーは処理系によって文字列型にキャストされることであり、 上の例なら Succ(Zero)
というデータ構造(これももちろんjavascriptでうまく定義しなければならない) がどう文字列にキャストされるかを知って置かなければならないこと. いっそのこととして私はデータ構造を文字列で持っておくことにした.
これはまた変態的と言われるかもしれないが、パターンマッチの when
も表現できる.
match n with
| _ when n%2=1 -> "odd"
| _ -> "even"
を次のように
match[true] = "odd"
when = n%2 == 1;
if (when in match) match[when];
else "even";
はじめ、
exp == Int ? exp.val :
exp == Float ? exp.val :
みたいに書いててそれをパターンマッチだと言い張ってたのだけれど、 ただ switch 文を式として書いてるだけだと気づいたし見た目が最悪なので先のものを思いついた.
if
を使わずに fizzbuzz
を書く、などというお題に対して使ったトリックだ.
今は体験版として公開されていて、簡単なランキングがある. ただ名前と得点を送信して更新されるだけのものだけど. 一時的に10位に入ったんだけど、すぐに13位までに落とされた. 時間を含めたリソースの制限も何もないので、ランダムネスを取り入れるのなら速度勝負だ. F#で書いてみたんだけど、C++なんかに比べて20倍は遅い.というかC++が速すぎるんだよね. 並列計算とかさせたら6,7倍にはなるかもだけど、それでも素直にC++ に敵わないんだからはじめからC++で書けばいいってことになっちゃうって考えたらもうなんか嫌になった. 189万点とれたヤツのソースは /fsharp/codevs2.fs.txt
として公開します.
「だから関数型なんて遅いんですよ」
と言われてしまった.
バイトをやめてあっという間に1ヶ月たった. 忙しいことをやめた人間は新たに忙しくなることを探すのかと思えば ただダメ人間になるだけだった.何度も経験してきたことではあるが.
バイトのやめ方がひどい. 大学に入って2年間で3つのサークルに入ったがどれも1ヶ月でやめた. やめるとまともに宣言したのは一つで、それも迫られて初めて言った ことだった.終わらせ方が分からない.そんな、唐突にやめますだなんて 言えるわけがないじゃないか.冗談を言える人間なら、大したことじゃ ないんだよという雰囲気を作りながら簡単に言えるのだろうか. バイト先には退院の目処のつかないという入院をしたことにした.
考えてみりゃ、関数型だなんて、しかもF#だなんて、そんな簡単にかける言語で処理まで速いなんて夢を見過ぎなんだ.
// test.fs
open System
let fib i =
let rec fib' a b i =
if i=0 then a else fib' b (a+b) (i-1)
in
fib' 0 1 i
for _ in 0 .. 99 do
ignore <| fib 40
// F# Compiler for F# 3.0 (Open Source Edition)
// real 0m0.018s
// user 0m0.012s
// sys 0m0.004s
出力はしてないけどちゃんと計算してるらしい. fsharpc には三種類ほど最適化オプションが用意されているが、入れても入れなくてもタイムは変わらなかったので、全く効果がないか、何も指定しなければ自動的にオンになるかだ.
// test.cpp
##include <cstdio>
int fibsub(int a, int b, int i) {
if (i==0) return a;
else return fibsub(b,a+b,i-1);
}
int fib(int i) {
return fibsub(0,1,i);
}
main() {
for (int i=0;i<100;++i) fib(40);
return 0;
}
// /usr/lib/gcc/i686-linux-gnu/4.7/lto-wrapper
// real 0m0.002s
// user 0m0.000s
// sys 0m0.000s
素晴らしい.
; test.scm
(define (fib i)
(define (fib* a b i)
(if (= i 0) a (fib* b (+ a b) (- i 1))))
(fib* 0 1 i))
(let loop ((i 0))
(if (< i 100) (begin (fib 40) (loop (+ i 1))) ))
; Gauche scheme shell, version 0.9.1 [utf-8,pthreads], i686-pc-linux-gnu
; real 0m0.013s
; user 0m0.008s
; sys 0m0.004s
F#をコンパイルしたより速い. そういうこともあるかもしれない.
# stalin でのコンパイル
real 0m0.001s
user 0m0.000s
sys 0m0.000s
バカみたいに速い.本当に計算されてるのか、つまり計算結果は捨ててるのでもしかして除去されてるのではないかと毎回 display させてみると、時間は二倍になってC++とやっと並んだ.
スクリプトとしてそのまま動かしても速いし、Scheme で書くことにしよう.
一限に出席するために学校に泊まってるのに、それでも出席できないようになってしまった.
お金が振り込まれてた.キモチが随分軽くなった.天まで届きそうだ
http://karetta.jp/book-node/gauche-hacks/007051
schemeで、C言語のprintfに相当する format
では "~a"
なんていうのが "%A"
に相当するらしい. なんだか可愛い. それよりも関数型で文字列フォーマットというのはどうやって実現してるんだろう.
gosh> (format "~a ~a ~a" 1 2)
*** ERROR: too few arguments for format string: "~a ~a ~a"
F# は OCaml の拡張にも見えるけど独特の関数適用、関数合成の演算子が面白い.
let (|>) x f = f x
let (<|) f x = f x
let (>>) f g = fun x -> g (f x)
let (<<) f g = fun x -> f (g x)
上の2つは順次パイプライン演算と呼ばれ, 下の2つは順次合成演算という. 入力のしやすさよりも見た目の良さを優先させている観がある.
()
で括った二項演算子は中間記法を前提としていて、つまり x |> f
と書いて f x
を表す.
(<|)
, (|>)
は左結合(<<)
は右結合(>>)
は左結合結合優先順位の強さは次の通り (上に行くほど強い)
(|>)
(<<)
(>>)
(<|)
左結合右結合は慣れればだいたい見た目通りに思える. 結合優先順位が若干曲者
let sq x = x * x
let add1 x = x + 1
(sq (add1 2))
これを次のように書く.
2 |> add1 |> sq
次はエラー
sq <| add1 <| 2
これは
(sq add1) 2
と解釈される.
括弧を補うならば
sq <| (add1 <| 2)
とするしかない. 左向きのパイプラインは関数合成を組み合わせるのが前提で(たぶん)
sq << add1 <| 2
とすると、
(fun x -> sq (add1 x)) 2
と解釈される.
右向きのパイプライン結合と左向きのパイプライン結合がキレイに左右対象になっていない.ちょっと嫌だ.
f
, g
, h
という3つの関数に x
という数を通す
f (g (h x))
このことをパイプラインで書くことを考えると
x |> h |> g |> f
これは x
を h
して、 g
して、 f
する、と考えたことと文章が一致していてとてもいい. Haskell になどには (簡単に定義できるにしても) ない演算だ.
左向きのパイプラインでは
f << g << h <| x
となる.記号が左右対象にならない.