トップ 最新 追記

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2005-11-03 [長年日記]

% [OCaml] スレッドのキャンセル

前回、Event モジュールの使い方を調べただけで力尽きて肝心のものを忘れてたんだが、なんか OCaml のメーリングリストでも スレッドに外から例外を送る話題が出てるらしいんで、せっかくだから Event モジュールを使ったバージョンをば。

シグナルを使った方法はわしも考えたんだけど、シグナルを送る方法が Windows ではおそらく使えないこととか、いろいろクールじゃないと思える部分があったんで却下した。 かと言って、じゃあわしのやり方がクールなのか?という問題はとりあえず見ない事に。

% cat main.ml
let check_interrupt channel () =
   match Event.poll (Event.receive channel) with
   | Some e -> raise e
   | None -> ()

let raise_interrupt channel exp =
   ignore (Event.sync (Event.send channel exp))

let do_something () =
   ignore (7 * 6);
   flush stdout

let compute channel =
   try
      let check = check_interrupt channel in
      while true do
         do_something ();
         check ()
      done;
      assert false
   with _ -> prerr_endline "Thread interrupted!"

let main () =
   let t, ch =
      let c = Event.new_channel () in
      (Thread.create compute c, c)
   in
   print_string "Press Return: ";
   flush stdout;
   let _ = read_line () in
   raise_interrupt ch Exit;
   Thread.join t

let _ = main ()

例を一から考えるのがめんどくさかったんで、Gerd Stolpmann さんのコードをベースにした。 これを実行すると…

% ocamlopt -thread unix.cmxa threads.cmxa main.ml
% ./a.out
Press Return:
Thread interrupted!

こんな感じ。 このやり方の場合、シグナルを使うのと違って関数 compute の中で適当にイベントが送られてきているかチェックする必要があるが、後始末とかのことを考えればむしろその方が安全だろう。 なぜか send 側イベントを poll にするとうまく行かなかったんで sync にしてあるが、どうして poll だとうまくいかないのかはイマイチわからん(ヌル


2005-11-05 [長年日記]

% [雑談] 前略メーラーダエモン様(ワラタ2ッキ)

そう言えば AppleMail の差出人に戻す機能を使うとメーラーダエモンさんからメールが来る事ありますね。 同一人物でしょうか :-p


2005-11-07 [長年日記]

% [OCaml] Event モジュールの話、その3

前回、send 側を poll にするとうまくいかなかった件にも少し突っ込み入れてみるためにソースをまじめに読む。

んで、説明するのはめんどくさいんで結果だけ言うと、send で作ったイベントオブジェクトは、poll した時点で receive イベントを sync で待っていないとイベントは処理されない。 結局、send 側か receive 側のどちらかが sync で待たないと意味がないということのようだ。

と言うことは、前回のような方法でスレッドのキャンセルを実現しようとすると、キャンセルされる側のスレッドがイベントチェックに辿り着かずにロックしたりするとデッドロックになる……ね。 使えねえ...orz

前回のような方法でやるなら、Event モジュール使うより自分でイベント通信の仕組みを作った方が確実かも知れない。 送る側は投げっぱなし、受ける側は単に順番に読んでくだけ、って形ならそんな面倒でもないでしょう。 前回の例を利用してさくっと書いてみると、こんな感じかね。

type 'a channel = { queue : 'a Queue.t; mutex : Mutex.t }

let new_channel () = { queue = Queue.create (); mutex = Mutex.create ()}

let send_event channel data =
   Mutex.lock channel.mutex;
   Queue.add data channel.queue;
   Mutex.unlock channel.mutex

let receive_event channel =
   Mutex.lock channel.mutex;
   let tmp =
      try
         Some (Queue.take channel.queue)
      with
      | Queue.Empty -> None
      | e ->
         Mutex.unlock channel.mutex;
         raise e
   in
   Mutex.unlock channel.mutex;
   tmp

let check_interrupt channel =
   match receive_event channel with
   | Some e -> raise e
   | None -> ()

let do_something () =
   ignore (7 * 6);
   flush stdout

let compute channel =
   try
      let check () = check_interrupt channel in
      while true do
         do_something ();
         check ()
      done;
      assert false
   with _ -> prerr_endline "Thread interrupted!"

let main () =
   let t, ch =
      let c = new_channel () in
      (Thread.create compute c, c)
   in
   print_string "Press Return: ";
   flush stdout;
   let _ = read_line () in
   send_event ch Exit;
   Thread.join t

let _ = main ()

2005-11-08 [長年日記]

% [Ruby] Symbol#[]

目から鱗が落ちた。 アクロバティックすぎる気もするけど、Ruby だしアリか(謎

% [FreeBSD] 6.0R はとりあえず VPC に入れてみた

実機をいじるのがめんどくさいの。

とりあえず、凡人の目にも見えやすい変更点は portsnap の追加でしょう。 サーバ負荷が小さいとか、OpenSSL の署名付きで通信するとか、そういう特徴があるらしいんで、なるべく cvsup から乗り換えた方が良さそう。 わしも、次に ports tree を更新するときは使ってみよう(つまり、まだ使ったことが無い)。

あとは、今回から PPC 版の iso イメージも用意されるようになったみたいなんで、いずれ暇をみて放置されてる旧 iMac にでも入れてみるかなあ…とか思ってるところ。

% [FreeBSD] アップグレードの手順

なんかこういう…

# make buildworld && buildkernel
# make installworld && installkernel
# mergemaster

…流れを書いてる人が結構いて(こことかこことか)、自分の知識に不安を覚えるんだが、security fix ブランチでのアップグレードならともかく、メジャーバージョンアップ(とか、それに類する大きなアップグレード)のときはこうじゃないの?

# make buildworld && buildkernel
# make installkernel
  (reboot してシングルユーザモードで立ち上げ)
  (もし立ち上がらなかったら、古いカーネルに戻す)
# fsck -p && mount -a (この辺は環境に応じて適当に)
# mergemaster -p
# make installworld
# mergemaster

と思うんだけど……どうなの?

% [FreeBSD][vim] 香り屋版パッチには例の mblen 問題への修正が含まれている模様

香り屋の中の人って Vim のコントリビュータじゃないのかな(そもそも Vim の開発形態がよくわからない)。 本家に入ってくれれば楽でいいのに。 もしくは香り屋版の port を誰か作って!!とか(自分ではやる気なし。だってこれだと MacOS X が救われないし、イマイチだ)。

本日のツッコミ(全1件) [ツッコミを入れる]

% otsune [/usr/src/UPDATING に書いてある手順が公式?]


2005-11-09 [長年日記]

% [FreeBSD] 続アップグレードの手順

otsune さんからツッコミがあったけど、「え、UPDATING にそんなの書いてあった?」と思って慌てて見てみると……うわ、思いっきり下の方にあったよ。

To upgrade in-place from 5.x-stable to current
----------------------------------------------
<make sure you have good level 0 dumps>
make buildworld                                [9]
make kernel KERNCONF=YOUR_KERNEL_HERE          [8]
                                               [1]
<reboot in single user>                        [3]
mergemaster -p                                 [5]
make installworld
make delete-old
mergemaster -i                                 [4]
<reboot>

make delete-old なんて初めて知ったなあ。こんなターゲットいつできたんだ? たぶん名前が変わったり、廃止になった古いライブラリを消してくれるんだろうと思うけど。 ともあれ、わしの知識は概ね正しかったようなんで一安心。

% [OCaml] ネタにマジレス

単なる言葉遊び的なネタだと思うんだけど、これを見てまじめに回答するとどうなるか考えた。

モジュールは分けたいけどファイルは分けたくない
自分で module を定義する。
ファイルは分けたいんだけどモジュールは分けたくない
include かなあ。

% [OCaml] 続ネタにマジレス

速攻で跳ね返ってきたので、せっかくだからも少し深く考えてみる。

ネストしたモジュール名が嫌
open すれば……と思ったら、そうですか open 嫌いですか。嫌いな理由が『名前空間が汚れる』とかって理由なら、ファイルのトップレベルにはモジュール以外定義しないようにすれば良いんじゃないのかなあ、とか。
モジュールにならないファイルができるのが嫌
cmo とか cmi ファイルのことだと思うけど、必要なものをひとまとめにしてアーカイブライブラリにしてしまえば、いらないものは消せます。
% cat a.ml
include B

% cat b.ml
let p () = print_endline "hoge"

% cat main.ml
let _ = A.p ()

てな感じだとして、

% ocamlc -a -o a.cma b.ml a.ml
% rm b.cmi b.cmo
% ocamlc a.cma main.ml
% ./a.out
hoge

アーカイブにしないと、

% ocamlc a.cmo main.ml
Error while linking a.cmo: Reference to undefined global `B'

こうなる。

% [OCaml] 必要なファイルとそうでないファイル

理解するまでは結構悩む問題だったなあ。 まあわかってしまえばわりと単純で、コンパイル時に必要なのはコンパイルするファイル内で参照しているモジュールのコンパイル済みインターフェイスファイル(cmi)、リンク時に必要なのは参照解決に必要な全てのオブジェクトファイル(cmo,cma,cmx,o,cmxa,a)と憶えれば良し。

cmi ファイルの名前はモジュール名と対応してるので、変更しちゃダメ。 逆にオブジェクトファイルは名前を変えちゃっても大丈夫。 ただ、ネイティブコード版の場合は hoge.cmx に対応した hoge.o(あるいは hoge.cmxa に対応した hoge.a)ってファイルも必要なんで、対応を崩さないように(cmx の名前を変えたなら o の名前も変えるように)。

ちなみにネイティブコード版で C のオブジェクトファイルをアーカイブに含めようとすると、大変苦労するので気をつけよう。 a ファイルに埋め込んでくれれば良いのに、どういうわけかファイル名を記録するのみで、しかも使うときにライブラリパスとかを検索してくれないから、結局自分でオブジェクトファイルを指定してやらなきゃならない。 アーカイブにする意味無いじゃん。 つーことで、C のオブジェクトを埋め込んだアーカイブを作りたいときは、C の部分は共有ライブラリにしてやった方が良い。 それならライブラリパスが通ってるとこに入れとけば、ちゃんとリンク時に見つけてくれるから。

% [雑談] 今日の素敵スパム

内容はどうでも良いんだが、サブジェクトが『VIP専用、セレブ専用』。

ちょっwwwwおまっwwwww専用かよwwwwwwwwww

とか、そういう感じ?

% [PC] いろいろな言語における、ファイル名とモジュールの関係

soutaro さんとのやり取りでちょっとスイッチ入っちゃったんで、つらつらと書いてみた。 間違い、勘違いなどツッコミ歓迎。 ここで言うモジュールとは、『名前空間を区切るための仕組み』程度の意味で考える。

  • OCaml の場合
    • ファイル一つが自動的にモジュールになる。
    • ファイルごとのモジュールに加えて、明示的なモジュールの作成もできる(モジュールはネストされる)。
    • 個人的にはそれなりに良い仕様だと思う。
  • C の場合
    • モジュールに相当する機能は無い。
    • static 修飾する事で、グローバルな名前空間の汚染を最小限に抑えるくらい。
  • C++ の場合
    • namespace ってのがあるが、くわしくないので割愛。
    • 少なくともファイル名とモジュール(namespace)に相関関係は特に無いはず。
  • Java の場合
    • 純粋なモジュールという機能は無くて、static メンバだけを定義したクラスで代用される。
    • 一つのファイルに定義できる public クラスは一つだけなので、実質ファイル一個がモジュール一個とも言える。
    • クラス内クラスが定義できるので、OCaml のようにネストさせれば一つのファイルに複数のモジュール(クラス)を持つ事もできないことはない。
    • まあ、Java でファイル数が増える事を怖がってちゃ何もできないので、気にせずクラスごとにファイル作っときゃ良いと思う。
    • package システムのおかげで、名前の一意性を保つ能力はトップレベル。
  • Ruby の場合
    • 各ファイルのトップレベルはグローバルスコープ(と言うか Kernel モジュール?)。
    • 一つのファイルに好きなだけ、モジュールなりクラスなりを定義すれば良いだけ。
    • Ruby の変数は名前でスコープが区別できるから良いが、OCaml でこういう仕様にしたら名前空間汚されまくりでキツイと思われる。Ruby でもトップレベルに関数定義とかしてると require した時点でオーバーライドされちゃうし。
    • まあ、モジュールを作る側が気をつければ良いんだけども。
    • ファイル名の衝突をさけるためにディレクトリを利用して分類したりできるが、ファイルの中身(モジュール名の衝突とか)までは面倒見れないので Java の package にはかなわない。
  • Python の場合
    • ファイル一つが自動的にモジュールになる。
    • スクリプト言語なだけに、OCaml のように断り無くモジュールを使うことはできなくて、使う前に import しなくちゃならない。
    • from .. import で、モジュール内の必要なものだけ取り込めるのはカッコイイかも。ただ、これを使うと名前空間を汚すので注意が必要。特に from hoge import * とかはあまりやっちゃいけないと思う。
    • なんかファイルを分ける以外のモジュール作成方法が無い気がする。Java みたく static メンバのみのクラスを作れば良いのか?あれ、クラスメソッドってどうやって定義すんだっけ?
    • ディレクトリを使った階層構造を package というらしいが、サブディレクトリに __init__.py とかって初期化ファイルを作らなきゃならなかったりめんどくさいっぽい。
  • Perl の場合
    • よくわかんない。
    • 微妙な知識で言えば、拡張子を pm にしておいて(例えば Hoge.pm)ライブラリパスの通ったとこに置いとくと、use Hoge; とかで使えるようになる。でもこのままだと use した時点で名前空間汚しまくりでお薦めできない。
    • ちゃんと名前空間を区切ったモジュールにしたいときは、package 宣言を使う。運用法はぶっちゃけ Ruby に近い(と言うか Ruby "が" 近いのか)。

オチは無い。(最後に HSP とか持ってくれば良かったか?)

% [本日のリンク元] google 検索(めくら将棋)

『めくら』は差別用語だから今は『目隠し将棋』って言うんだってさ。 ・・・バカじゃない?(ちなみに中黒点三つはアスキーアートです。意図としては『( °д°)ポカーン』みたいな感じ)

ついでに検索結果の一番上に来てたページ見てみたけど、『めくら将棋 でたらめでへたな将棋』とか書いてあんの。 もうね、アホかと、バカかと。 捏造乙です。 きっと『イメクラ』も差別用語だからリストに加えときなよ。

差別だ差別だって殊更に騒ぐヤツが一番差別的なんだっての。

本日のツッコミ(全4件) [ツッコミを入れる]

Before...

% jijixi [そもそも Ruby のグローバル変数は名前に $ を付けなきゃいけないんで、グローバルスコープって言い方があんまりう..]

% soutaro [じゃあC#の場合を ・ファイル名とモジュール名の間には関係は無い ・namespaceは開かれている ・クラスは一つ..]

% jijixi [NET Framework は VB.NET しか使った事無いですが、多分似たようなものな気がします。 F# とかは..]


2005-11-10 [長年日記]

% [雑談] わしが OCaml に惹かれて Haskell にイマイチ惹かれない理由

FreeBSD には惹かれるけど、NetBSD にはイマイチ惹かれない……てのと似てる気がする。 なんつーか、FreeBSD は(Linux なんかに比べれば)厳格ではあるんだけど適度に力の抜きどころがあるというか。 NetBSD はマジメすぎて逆に安心できないというか。

同じように、関数型プログラミングの魅力は大きいんだけど、OCaml では適度に力を抜ける(副作用バリバリでも大丈夫)のに対して、Haskell では『副作用許すまじ』みたいな雰囲気で力が抜けなさそうな気がするわけで。 なんだよモナドって、みたいな。

原点が手続き型言語な人間が Haskell を始めるには、かなり気合が必要かと思われる。 だいたい遅延評価ってやつも直感的じゃない。 それこそ必要に応じて使う…ってんであれば何とかならなくもないが、いつでもそうなんだ…と言われたらもういろいろと困る。ぶっちゃけ脳みそがついていかない。 こんなネタとか読んじゃったら、ますます不安が募るし。 あと関数の定義なんかも気持ち悪い。 どっからどこまでが定義なんだかわかんないよ。

それから OCaml のトップレベルインタプリタみたいなのが無いのも取っ付きづらい。 Hugs ってのがそういうものなのかと思ったら、インタラクティブに実行できるのは式だけで、定義や宣言はいちいちファイルに書かなきゃならんのな。 OCaml はちょろっと実験したいときとか楽で良い。

と言うわけで、わしは FreeBSD で良いし、OCaml で良いのである。 ちなみに Linux はあんまり好きじゃないし、Lisp もあんまり好きじゃないんである。

  • 今日のまとめ(わしの独断と偏見によるイメージ)
    • FreeBSD = OCaml
    • NetBSD = Haskell
    • Linux = Lisp

% [OCaml] ラクダのジレンマ問題、わしならこうする

昨日のネタ絡みで、そういう状況になった場合に自分ならこうするだろうというのを少し具体的に書いてみよう。 人それぞれスタイルってのがあると思うけど、もしかしたら今後この問題に直面する人の参考程度にはなるかもしれない。

・ファイルは分けずにモジュールは分ける

% cat baseMod.ml
module Hoge =
   struct
      let v = "hoge"
   end
module Fuga =
   struct
      let v = "fuga"
   end

% cat main.ml
open BaseMod
let _ =
   print_endline Hoge.v;
   print_endline Fuga.v
% cat main.ml
(* 別バージョン *)
module Hoge = BaseMod.Hoge
module Fuga = BaseMod.Fuga
let _ =
   print_endline Hoge.v;
   print_endline Fuga.v

baseMod.ml のトップレベルには module 以外定義しないようにしておけば、open しても名前空間を汚される心配は少ない。 もちろん Hoge やら Fuga やらが既存のモジュール名と被らないように気をつける必要はあるが。 余計な open は入るものの Ruby で require してモジュールにアクセス…てのと似たようなもんだから、特に冗長というわけでもないだろう。

別バージョンの方は、ネストしたモジュールをそのままローカルなモジュールにしちゃってる例。 ちょっと冗長かもしれないが、これなら名前空間を汚す心配が無い。

・モジュールは分けずにファイルは分ける

これについては昨日書いたのがそのまま例になるかと。 もう少し突っ込むなら、ファイル名をちょっと工夫するかな。

% cat hoge.ml
include Hoge_stub_1
include Hoge_stub_2

% cat hoge_stub_1.ml
let p () = print_endline "hoge"

% cat hoge_stub_2.ml
let pp () = print_endline "fuga"

% cat main.ml
let _ = Hoge.p ()

みたいに、最終的にいらなくなるファイルの名前には適当にサフィックスでも付けといて、アーカイブを作った後に自動で消しやすくしてやる。

% [OCaml] ラクダのジレンマ番外編

モジュールは分けずにファイルは分ける場合に使える裏技(笑

% cat sub.ml
let print () = print_endline "hello world"

% cat main.ml
#include "sub.ml"
let _ = print ()

% ocamlc -pp "cpp -P" main.ml
% ./a.out
hello world

-pp オプションを使うとプリプロセッサでフィルタリングできるんで、そこで cpp (C Preprocessor)を使ってやる。 ちなみに cpp に -P オプションを付けないとゴミが付いちゃうんで注意(このためのオプションを思い出せなくて5分間 man とにらめっこしたのは内緒)。 幸い OCaml のソースで行頭に "#" が出てくる事は無いはずなんで、たぶん問題無く使える……と思う。

まあ、こんなやり方もあるよってことで。

% [OCaml] F# のモジュール

はい、と言うわけで案の定気になっちゃって仕方ないんで調べてみたよ。

マニュアルを見た感じだと、ファイルの頭で module 名を宣言するみたいなことが書いてあるんで、ファイル名とモジュール名は関係無いのかもしれない。 OCaml との比較ページを見てみると Functor や OCaml スタイルのオブジェクトなんかは未サポートらしい。 基本的には .NET Framework の名前空間をそのまま持ってきてる感じなのかね。

そのせいかクラス定義やもろもろの扱いが OCaml と随分違う。 逆に .NET な言語に慣れてれば、親しみやすい感じではあるのかも。

うーん、結構おもしろいかもね。 アンチ MS っぽいわしだが、.NET Framework にはわりと肯定的な印象があったりするんで、ちょっと期待してみたくなったぞ。 よし、とりあえずダウンロードしとこう。 でも、うちには VisualStudio 2002 しかなくて VS 用機能拡張はサポート外だから、ちまちまコマンドラインでいじらなくちゃならないが(いや、別にそれでも良いんだけど)。 あ、今度の VisualStudio はただで使えるようになるんだっけ?

Mono で使えるなら Mac でも試せるから楽なんだけどな。どうなんだろ?

本日のツッコミ(全2件) [ツッコミを入れる]

% soutaro [F#のVisual Studio拡張は、コード書く端から型推論してくれて、なかなか面白いですよ。あと、Visual ..]

% jijixi [> Visual Studio Expressは、アドインが使えないので、 がくっ...orz これのためにわざわざ..]


2005-11-11 [長年日記]

% [OCaml] F# 体験記

ちまちまいじってみた。オブジェクト指向絡み以外は普通に Caml だなって感じだね。 逆にオブジェクト関係は微妙に不満もある。 文法の違いは良いとしても、メソッドを関数として取り出せないのが残念。 OCaml だと、

# class hoge =
  object
    val v = "hoge"
    method get () = v
  end;;
class hoge : object val v : string method get : unit -> string end

# let o = new hoge;;
val o : hoge = <obj>

# o#get;;
- : unit -> string = <fun>

こんな風にインスタンスメソッドを関数として取り出せる。当然、カリー化もできる。

# class fuga =                               
  object (self)                              
    method p s1 s2 = print_endline (s1 ^ s2)
    method hello = self#p "hello "
    method hello_world = self#hello "world"
  end;;
class fuga :
  object
    method hello : string -> unit
    method hello_world : unit
    method p : string -> string -> unit
  end

# let o = new fuga;;
val o : fuga = <obj>

# o#hello_world;;   
hello world
- : unit = ()

# let f = o#p "This is a " in
  f "pen.";;
This is a pen.
- : unit = ()

この辺、通常の仕様と一貫性があって、とても好ましい。 んで F# の場合、カリー化はできるんだがメソッドそのものを取り出すことができないのが一貫性に欠ける気がする。

> type Hoge =                                  
  class                                        
    new() = {}          
    member self.p s1 s2 = print_endline (s1 ^ s2)
    member self.hello = self.p "hello "          
    member self.hello_world = self.hello "world" 
  end;;
val Hoge.hello.get.1 : Hoge -> unit -> string -> unit
val Hoge.hello_world.get.1 : Hoge -> unit -> unit
val Hoge.new.0 : unit -> Hoge
val Hoge.p.2 : Hoge -> string -> string -> unit
type Hoge

> let o = new Hoge();;
val o : Hoge

> o.hello_world;;
hello world
val it = null
val it : unit

> let f = o.p "This is a " in
  f "pen.";;
This is a pen.
val it = null
val it : unit

> o.p;;
toplevel(2,0): error: Syntax expr.id is may only be used with record labels, properties and fields.
stopped due to error

まあ、些細なことかもしれないが、わしは広範囲に及ぶ識別子は長い名前を付けておいて、使うときにはローカルな短い名前を付けて使う…ってなやり方を好むもんで、どうもね。

% [OCaml] 続 F# のモジュール

もう少し詳しい説明を発見。 ファイルの先頭でモジュール名を宣言しない場合は、OCaml と同様のルールでモジュール化される模様。

% cat sub.fs
module Mylib.Mymod
let p s = print_endline s

% cat main.fs
let _ = Mylib.Mymod.p "Hello World"

% fsc -o a.exe sub.fs main.fs
% ./a.exe
Hello World
% cat sub2.fs
let p s = print_endline s

% cat main2.fs
let _ = Sub2.p "Hello World"

% fsc -o a.exe sub2.fs main2.fs
% ./a.exe
Hello World

それはそうと、なんか ocamlc の -c オプションに相当するものが無い。 ……つーか、そもそもファイルごとのオブジェクトファイルを生成しないし、常にソースから実行ファイルを作るしか無いみたいね。 ライブラリを作りたいときはどうすんだ?と思ったが、-a オプションで DLL を作るのがお作法らしい。

% fsc -a sub.fs
% fsc sub.dll main.fs
% ./main.exe
Hello World

これはこれでスマートではあるな。

% [OCaml] F# の強み

なんだかんだ言って、.NET Framework の API が使えるのはかなり強力だよな。 GUI 以外の部分でも便利なものはいっぱいあるし。

レコード型にメソッドを追加した Members は意味あるのかわからんけど。 VB.NET だと構造体とクラスではメモリの使い方が違うとかなんとかって話があったが、これもそういう類いだろうかね。

% [OCaml] F# on Mono

F# の配布物の中身をごそごそあさってみると、install-fsharp.sh ってファイルがあって、その中にはこんなことが書いてある。

  1. This script is for use with CLIs on Unix, e.g. Mono

ちゃんと動くのかはわからんが、Mono で動かすことも想定されてはいるようだ。 あとで試してみよう。 つーか、Mac に Mono インストールしてあったっけな? インストールするところから始めなきゃダメかも(苦笑

% [OCaml] F# への不満

.NET API が使えることが強烈なアドバンテージになってるのは良いとして、OCaml でできることができないと微妙にストレスが溜まるなあ。

# class hoge =
  object
    method p = print_endline "hoge"
  end;;
class hoge : object method p : unit end

# class fuga =
  object
    method p = print_endline "fuga"
    method dummy = ()
  end;;
class fuga : object method dummy : unit method p : unit end

# let f x = x#p;;
val f : < p : 'a; .. > -> 'a = <fun>

# f (new hoge);;
hoge
- : unit = ()

# f (new fuga);;
fuga
- : unit = ()

こういうの。F# ではできない。

> type Hoge =
  class
    new () = {}
    member self.p = print_endline "hoge"
  end;;
val Hoge.new.0 : unit -> Hoge
val Hoge.p.get.1 : Hoge -> unit -> unit
type Hoge

> type Fuga =
  class
    new () = {}
    member self.p = print_endline "fuga"
  end;;
val Fuga.new.0 : unit -> Fuga
val Fuga.p.get.1 : Fuga -> unit -> unit
type Fuga

> let f x = x.p;;
toplevel(2,10): error: Lookup on object of indeterminate type.
A type annotation may be needed to constrain the type of the object
in order for the lookup to be resolved.
stopped due to error

> let f (x : Hoge) = x.p;;
val f : Hoge -> unit

こういう場合は interface を使わなきゃならないんだろう。 良くも悪くも .NET 色ってことか。

% [OCaml] F# のワナ

カテゴリを OCaml のまま F# の話題を続けるのはどうなのか?という気もするが、まああくまでも OCaml ネタの派生としての F# ネタだということで。

OCaml に比べて微妙に泣ける点を見つけてしまった。

> let h = Hashtbl.create 0;;

toplevel(2,4): error: FS0030: Type inference has inferred the signature
        val h : ('b,'a) Hashtbl.HashTable 
for 'h', but the definition of 'h' involves some computation, which cannot be performed before specific types have been given for the type parameters in the inferred type. Either define 'h' as a simple data term, make it a function, or add a type constraint to instantiate the type parameters.

stopped due to error

がっくり。 ちなみに OCaml だと、

# let h = Hashtbl.create 0;;
val h : ('_a, '_b) Hashtbl.t = <abstr>

こうね。もちろん、こうすれば…

> let h : (string, string) Hashtbl.t = Hashtbl.create 0;;

val h : (string,string) t

…良いわけだけど、なんか納得いかんよな。 ……ところが、ちょっと思いついてこんなんしてみた。

% cat abst_test.fs
let h = Hashtbl.create 0
let _ = Hashtbl.add h "hoge" "fuga"
let _ = print_endline (Hashtbl.find h "hoge")

% fsc abst_test.fs
% ./abst_test.exe
fuga

……な、なんだってぇ!?

まあねえ、これなら実用上は問題ないだろうけど……イマイチ納得いかない。

% [OCaml] F# のワナ、続き

'_a とかの一時的な多相型ってなんて呼べば良いんだっけね。 まあ、名前はこの際どうでも良いんだが。 ともあれ F# では、この一時的な多相値がダメなのかと思ってたんだが、実際には mutable な多相値は軒並みアウツみたいね。 バータリープログラミングの味方、option 型のリファレンスなんかも全滅だ。ババンバン。

> let a = ref None;;

toplevel(2,4): error: FS0030: Type inference has inferred the signature
        val a : 'a Option ref
for 'a', but the definition of 'a' involves some computation,
which cannot be performed before specific types have been given for the type parameters
in the inferred type. Either define 'a' as a simple data term, make it a function,
or add a type constraint to instantiate the type parameters.

stopped due to error
> let a = [| None |];;

toplevel(2,4): error: FS0030: Type inference has inferred the signature
        val a : 'a Option array
for 'a', but the definition of 'a' involves some computation,
which cannot be performed before specific types have been given for the type parameters
in the inferred type. Either define 'a' as a simple data term, make it a function,
or add a type constraint to instantiate the type parameters.

stopped due to error

まあ、さっきの例でもわかるように、後から型推論のヒントが出てくれば大丈夫なわけだけど。 ただ、多分ファイルをまたいじゃうとダメなんだろうなーと思って、念のため


% cat sub.fs
let a = ref None

% cat main.fs
let _ = Sub.a := Some "hoge"
let _ = print_endline (match !Sub.a with Some s -> s)

% fsc -o a.exe sub.fs main.fs

sub.fs(1,4): error: FS0030: Type inference has inferred the signature
        val a : 'a Option ref
for 'a', but the definition of 'a' involves some computation,
which cannot be performed before specific types have been given for the type parameters
in the inferred type. Either define 'a' as a simple data term, make it a function,
or add a type constraint to instantiate the type parameters.

main.fs(1,17): error: FS0001: Type mismatch. Expecting a
        obj Option
but given a
        string Option
.
The type obj does not match the type string

main.fs(2,51): error: FS0001: This expression has type
        obj
but is here used with type
        string

main.fs(2,23): warning: FS0025: Incomplete pattern match.

sub.fs に対して出てるエラーは fsi で出るのと同じ。やっぱりファイルをまたがるとダメ。 それはともかく、main.fs に対して出てるエラーが激しく謎。 参照しようとしてるのは 'a Option のはずなのに、なぜか obj Option なんてものになっている。 なぜ?

ってことで調べてみたところ、こんなのが。 多分、CLR 側の型名が出ちゃってるんだろう。 基本的に 'a と obj は自動的に変換されるものみたいだから、気にする必要は無いと思われる。

多相値が多相値のまま存在できないのは、CLR の制限なのかね。 まあ、ファイル一個で他の言語から利用するアセンブリの要素になりうるのに、『使用される事で型が変化(あるいは確定)し得る値』なんてものがあると迷惑だってことかも知れない。 ともかく、そう言うもんだとわかってれば別にどうってことない制限だから、この件はこんなところで良いだろう。

% [OCaml] F# を Mono で動かす計画(失敗)

動かにゃい。でもせっかくだから、何をどうしてみたのかメモっとく。 環境は、

Mac OS X 10.4.3 / iMac G5
% uname -v
Darwin Kernel Version 8.3.0: Mon Oct  3 20:04:04 PDT 2005;
root:xnu-792.6.22.obj~2/RELEASE_PPC

Mono 1.1.10 (オフィシャルサイトから落としてきた dmg 版)
% mono --version
Mono JIT compiler version 1.1.10, (C) 2002-2005 Novell, Inc and Contributors.
www.mono-project.com
        TLS:           normal
        GC:            Included Boehm (with typed GC)
        SIGSEGV      : normal

FSharp 1.1.3.2

とりあえず FSharp に付いてきた install-fsharp.sh の中身はインチキなので(ファイル名の変更に追随できてないっぽい)、ともかくそれらしい dll を登録。

% foreach i (bin/*.dll)
sudo gacutil -i $i
end
Password:
FSharp.Compiler installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
Failure adding assembly to the cache: The file specified is not a valid assembly.
Failure adding assembly to the cache: The file specified is not a valid assembly.
Failure adding assembly to the cache: Attempt to install an assembly without a strong name.
Failure adding assembly to the cache: Attempt to install an assembly without a strong name.
fslib installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
fslib10 installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
fslib10ntc installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
mllib installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
mllib10 installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
mllib10ntc installed into the gac (/Library/Frameworks/Mono.framework/Versions/1.1.10/lib/mono/gac)
Failure adding assembly to the cache: The file specified is not a valid assembly.

んで、実行。

% mono -v bin/fsi.exe
Method (wrapper managed-to-native) System.Object:__icall_wrapper_mono_thread_interruption_checkpoint () emitted at 0x4b03b8 to 0x4b0478 [fsi.exe]
Method (wrapper managed-to-native) System.Object:__icall_wrapper_mono_thread_force_interruption_checkpoint () emitted at 0x4b0480 to 0x4b055c [fsi.exe]
Method (wrapper runtime-invoke) System.Object:runtime_invoke_void_string (object,intptr,intptr,intptr) emitted at 0x4b0560 to 0x4b0688 [fsi.exe]
Method System.OutOfMemoryException:.ctor (string) emitted at 0x4b0690 to 0x4b06d0 [fsi.exe]
Method System.SystemException:.ctor (string) emitted at 0x4b0728 to 0x4b0768 [fsi.exe]
Method System.Exception:.ctor (string) emitted at 0x4b0798 to 0x4b07d8 [fsi.exe]
Method System.Exception:set_HResult (int) emitted at 0x4b07e0 to 0x4b0810 [fsi.exe]
Method System.NullReferenceException:.ctor (string) emitted at 0x4b0818 to 0x4b0858 [fsi.exe]
Method System.StackOverflowException:.ctor (string) emitted at 0x4b0860 to 0x4b0890 [fsi.exe]

** ERROR **: file metadata.c: line 1827 (do_mono_metadata_parse_generic_class): assertion failed: ((gclass->context->container = gklass->generic_container) != NULL)
aborting...

うーん…… F# 自体が .NET Framework の 2.0 以上を要求したりするし、Mono が F# に必要な機能を提供できてないってことかねえ。

% [OCaml] Haskellの$(soutaroにっき)

そもそも Haskell の $ ってのがどんなもんだかわからなくてピンと来ない。 ので、Hugs の Prelude.hs を見てみた。

($)            :: (a -> b) -> a -> b
f $ x           = f x

なるほど。 それにしても、Haskell で一般的だと思われる、この思いっきり隙間が空く記法はどうしても好きになれないな。 タブ幅は 8 を推奨とか言われても 3 タブ同盟としては対応に困る(苦笑

ともあれ、せっかくだから OCaml で試してみよう。

# ($);;
Unbound value $

# let ($) f x = f x;;
val ( $ ) : ('a -> 'b) -> 'a -> 'b = <fun>

# let f x = x + 1;;
val f : int -> int = <fun>

# let g y = y + 2;;
val g : int -> int = <fun>

# f g 0;;
This function is applied to too many arguments, maybe you forgot a `;'

# f $ g 0;;
- : int = 3

ほほう。

% [OCaml] メソッド定義のスタイル

OCaml とか F# とか興味ない人には申し訳ないほどの勢いでエントリが続いてるが、なんかよくわかんないけどそういうスイッチが入っちゃってるんで勘弁願いたい。

クラス定義の際に引数無しのメソッドを作る場合には、二種類の書き方がある。 仮引数を書かない場合と、通常の関数宣言のように () を書く場合だ。

# class hoge =
  let p = print_endline in
  object
    val v = p "variable."
    method get = v
    method m1 = p "method 1."
    method m2 () = p "method 2."
  end;;
class hoge :
  object
    val v : unit
    method get : unit
    method m1 : unit
    method m2 : unit -> unit
  end

m1 と m2 の型を比べればわかるとおり、仮引数無しだとメソッドを関数として取り出すことができない。 通常の文法から想像すると m1 は関数ではなく単なる値のような気になるが、実際は単に引数が必要無いだけで、中身はちゃんと呼び出しの都度評価される。

# let o = new hoge;;
variable.
val o : hoge = <obj>

# o#get;;
- : unit = ()

いやあ、こういう実験するとき副作用は便利だなあ。 val v の中身はインスタンス化のときに評価されるので、new したときに variable. と表示されてるね。 その後、get メソッドで中身を取得しても、v の中身はすでに評価済みなのでユニット型が返ってくるだけ。

# o#m1;;
method 1.
- : unit = ()

# o#m2;;
- : unit -> unit = <fun>

# o#m2 ();;
method 2.
- : unit = ()

んで、m1 と m2 の動作の違いはこんな感じ。 この二つをどう使い分けるかなんだけど、わしの場合は単なる変数へのアクセサの場合は引数無しで(上記の get みたいなのね)、引数がいらない関数の場合は仮引数付きにしてる。 要するに、アクセサの場合は純粋に値として使えれば良いわけだから、通常の値と見た目で一貫性があった方が好ましいと思うし、関数扱いの場合も同じように通常の関数と見た目で一貫性があった方が良いんじゃないかと思うわけ。

あと、他に使い分けの指針を考えるとすれば、他のオブジェクト指向言語みたいにメソッドチェーンしたい場合に引数無しを使うってのもあるかな。

# class fuga =
  let p = print_endline in
  object (self)
    method p1 = p "fuga"; self
    method p2 () = p "fuga"; self
  end;;
class fuga : object ('a) method p1 : 'a method p2 : unit -> 'a end

# let o = new fuga;;
val o : fuga = <obj>

# o#p1#p1#p1#p1;;
fuga
fuga
fuga
fuga
- : fuga = <obj>

# o#p2()#p2()#p2()#p2();;
This function is applied to too many arguments, maybe you forgot a `;'

# (((o#p2())#p2())#p2())#p2();;
fuga
fuga
fuga
fuga
- : fuga = <obj>

p2 の方はいかにも冗長だよね。 ただまあ、引数有りなメソッドなら結局はこうなっちゃうわけだから、あんまり気にしても仕方ない気はする。

本日のツッコミ(全7件) [ツッコミを入れる]

Before...

% _ [みんなには内緒ですよ? >|| #camlp4o;; #require "camlp4.gramlib";; #re..]

% jijixi [おお、なんかスゴイの来た。 camlp4 の勉強は何度も挫折してるんで(苦笑)、使いこなしてる人は尊敬します。 @ ..]

% _ [がーん、向井先生のページで$を使ってはいけない理由がorz]


2005-11-12 [長年日記]

% [OCaml] 続 Haskell の $

向井さんによるとどうやら OCaml で "$" は使わない方が良いらしい。 まあ、わしの場合とりあえず試してみただけで、本気で今すぐにでも使おうと思ってたわけじゃないけど、中置演算子を定義する時には結合規則を気にしなきゃならないって気付いたのは収穫だった。

実はこういうのを考えてたもんで…

# class base_obj =
  object (self)
    val v = Hashtbl.create 0
    method send msg args = (Hashtbl.find v msg) args; self
    initializer
      let def msg func = Hashtbl.add v msg func in
      def "p" (fun s -> print_endline s);
      def "hello" (fun s -> print_endline ("hello" ^ s))
  end;;
class base_obj :
  object ('a)
    val v : (string, string -> unit) Hashtbl.t
    method send : string -> string -> 'a
  end

# let (@@) obj msg =
  match msg with
  sel, args -> obj#send sel args;;
val ( @@ ) : < send : 'a -> 'b -> 'c; .. > -> 'a * 'b -> 'c = <fun>

# let (<<) = (@@);;
val ( << ) : < send : 'a -> 'b -> 'c; .. > -> 'a * 'b -> 'c = <fun>

# o @@ ("p", "hoge") @@ ("hello", "world");;
This expression has type string * string but is here used with type
  < send : 'a -> 'b -> 'c; .. >

# o << ("p", "hoge") << ("hello", "world");;
hoge
helloworld
- : base_obj = <obj>

実際のものはもっと複雑だけど、とりあえずモデルとしてね。 んで、中置演算子の記号に何を使うか悩んでるんだけど、間違って @ とか使ってたらさらに激しく悩むところだったよ。

ちなみに昨日のメソッド定義ネタの p2 メソッドなんかは、

# (((o#p2())#p2())#p2())#p2();;

を、

# let (<<) obj arg = obj#p2 arg in
  o << () << () << ();;

とか書けるね。実用性はともかく(苦笑

% [雑談] 道具の使い方を憶えるのは……

シールを手ではがす作業に似ていると思うことがある。 つまり、最初の取っ掛かりさえ乗り越えれば、その後はわりとするすると進む気がするのだ。 もちろん、慎重に引っぱらないと途中で破けちゃうシールもあれば、一気にはがしちゃえるものもあるだろうけど。 さらに言えば、シールをはがしてもそれはとりあえず扱えるようになったってだけで、今度はそのはがした後をキレイに磨き上げる作業が待っていたりする。

いや、こんなこと思ったのも昨日の深夜に通算何度目かの『モナドを理解しよう』モードに入って、一生懸命解説ページとか読みつつも、結局やっぱり挫折したからだったり。 漠然とした概念はわからんでもないんだけど、どうしてもこう自分のものにできないと言うか。 きっかけさえ掴めれば一気に理解できそうな気がするんだけど、そのきっかけがどうしてもうまく掴めない歯がゆさが、なんだかシールをはがすときのもどかしさを連想させたのであった。

道具を使う時には中の仕組みを(少なくとも大体の流れくらいは)把握しないと気が済まないという性質がわしにはあって、それは長所でもあり短所でもあると思うが、そういうわけで『仕組みがわからなくても動けば良いや』ってのが許せない。 だから自動車を運転するためにエンジンの仕組みを勉強したりする。 別に整備士になったり、エンジンの開発者になるつもりは無いんだから、かなり無駄ではあるんだけどね。

そんなわけで、Haskell を使おうと思えばモナドの仕組みを理解したくなるし、何かの機械を使おうと思ったら分解してみたくなるし、何かのアプリケーションを使おうと思ったらそのソースコードを見たくてたまらなくなる。 うん、オープンソース文化万歳。

結局何を言いたいのかは、自分でもよくわからなかった。

% [雑談] 反省

最近、日記を書く時の誤字脱字に対する注意が足りない気がする。 んで、後から読み直して気付いても、直すと無駄に RSS が流れて嫌だなあとか思っちゃったりして二の足を踏んでみたり。

いやあ、文章は目でデバッグするしかないから大変だよね。 ……投稿前にちゃんと校正しよう...orz

% [PC] 昨日に引き続き Haskell のお勉強

っつーか、『Haskeller への道』を流し読みしてるだけだけど。 これくらいがちょうど良いと感じるレベルなのです、今のわしは。

ところで Haskell って『はすきゅる』って読むと思ってたんだけど、このページには『はすける』って書いてるね。 なんで『はすきゅる』だと思ったのかは憶えてないんだが、『はすける』だと微妙にダサくない? あと、「ちょっとプログラム書いてみたけど、IO モナドってウザいね。」には笑った。 ほんとそう思う。 副作用のある操作を特別なブロックに閉じ込めてしまうっていう思想(?)は、大変美しいとは思うんだけどやっぱウザいものはウザいよな(笑

Haskell もちょこちょこ遊ぶ分にはおもしろいと思うんだけど、実用的なものを書こうとなるとわしの手には余るなあ。 モナドをちゃんと理解できれば違うのかも知れないけど。

ところで唐突に 3 タブ同盟的な関数定義の書き方を思いついた。

cat fname =
   do h <- openFile fname ReadMode
      str <- hGetContents h
      putStrLn str

do 記法使ってもタブ幅 3 でピッタリ。 でもこうすると、イマイチ Haskell のコードに見えないのが不思議だ。 それはそれとして、cat の仕様が間違ってるっていうツッコミはナシの方向で。


2005-11-13 [長年日記]

% [雑談] どうして地球の遠心力で人間が飛ばないのなぜ?(ニャー速VIPブログ)

スレタイの日本語が破綻してますが、そんなことよりも……

地球の回転速度は時速1600キロぐらいなんだけどさ

旅客機で東へ飛ぶのは、無駄だな

ちょ、それヘリでまっすぐ上にあがって待機してれば

エアーウルフより速いってこと?????

ここでエアーウルフが出てくるセンスがすげえ(笑

% [Mac] あーもー Safari ってヤツは!!

バックスラッシュを変な文字に変えて POST しちゃう問題はいつ解決されるんだ、まったく。 Safari の問題と言うより WebCore の問題らしくて OmniWeb もシイラもダメダメなんだよな。 日記のちょっとしたミスをついつい Safari で直しちゃって、プレビューせずに書き込んじゃうと \n って書いてあったのがいつのまにか ?n になっててガックリしたり……まったくもう。

Mac から化けずに更新するには、WebCore を使ってないブラウザを使うしかない。 具体的には Mozilla 系、Opera、あとは w3m とか。 実際わしは、通常はもっぱら w3m + vim で更新してるんだが、プレビューを見てもスタイルシート適用後の見え方がわからなくて、つらいことがないこともない(変な日本語だ)。

FireFox で mozex がうまいこと使えれば良いんだけど、今んとこ動かないんだよね…… vim で書くのを諦めるなら Opera のメモ機能がそれなりに使えそうだけど、表示するときはともかく書くときは等幅フォントじゃないと気持ち悪いんだよな、個人的に。 やっぱり w3m が一番だ。

あ、X11 用の FireFox とか使えば mozex 使えるな。 ……この際、X11 をフルスクリーンモードにして、X Window だけで生きていくってのはどうだ?(極端です)


2005-11-15 [長年日記]

% [PC] 名前推論

プログラマ的観点で言うと、文脈依存って副作用と同じくらいバグの元になりそうな気がするんだけど、自然言語に近いプログラミング言語の可能性には、それなりに興味をひかれる。 なんか SF っぽいわくわく感が無きにしも非ず。

ところで『B-3. vs 型推論』って項で言われてる…

人間に前者を書かせて、前者から後者を処理系が推論するのが型推論ですが、型推論を期待したせいで後者(型情報)が書かれていないソースは、あまり読みたいものではないです。

って話は、まあわからなくもないけど、少なくとも OCaml では ocamlc -i で型情報が見れちゃうんで大丈夫だね。 ライブラリにするなら、いらんものをエクスポートしないようにインターフェイスファイル(mli)を用意するのはあたりまえだろうし。 そんなわけで、例に OCaml のコードが使われてるのはフェアじゃない気がした(気にしすぎ)。

こういうの、Haskell とかだとどうなんだろね。 ライブラリなんかだと、大抵自前で型宣言も書いてあるみたいだけど、型宣言無しのソースコードから型情報だけ抜き出す方法とかあるんだろうか。 つーか、無いと困るんじゃないかと思うんで、きっとあるんだろうという気もするが。

(追記) ツッコミをもらったので追記。 少なくとも ghc には型情報を抜き出す方法があるそうな。 詳しくはツッコミ欄を参照のこと。

% [Ruby] Irb/TipsAndTricks

向井さんとこ経由。

これはほんとに鼻血出そうだな。使わせてもらおう。

% [PC] 続・名前推論

さる偉い人は言ったもんだ。「名前重要」と。 それなりにコードが書けるようになったプログラマであれば、この言葉には程度の差はあれ頷かずにはいられまい。 それほどまでに、現在のプログラミング言語は名前を付ける事が重要な要素になっているのである。 そして、プログラマはそれに慣れきっているのだね。

ただ翻ってみて、逆にプログラマではない人々は(プログラマから見て)驚くほど名前に頓着しないものなのだということを知るべきだ。 疑うのならそこら辺にいる非プログラマを捉まえて、彼/彼女の作った Excel のファイルを見てみるが良い。 十中八九、シート名は sheet1, sheet2,... ってなってるはずだ。 あるいは VB のプロジェクトを見てみるが良い。 十中八九、メインウィンドウの名前は Form1 であるはずだ。 間違いない。*1

これらのことを踏まえれば、名前推論がどんな分野で活用されるべきかが見えてくる。 つまりプログラマではない人が使うための言語だ。 例えば、VBA のようなものであったり、AppleScript のようなものであったり。 名前に無頓着な人間には、はなっから名前なんて付けさせずに、型だけを強く意識させてやった方が良い。 a, aa, aaa って三つの変数があって全て型が違うだとか、そういう喜劇のような悲劇は起きなくなるだろう。

そんなことをつらつらと考えた、今日の帰り道であったとさ。

*1 ごめんなさい、多少誇張が入ってます

本日のツッコミ(全5件) [ツッコミを入れる]

Before...

% ikegami [Haskell のコンパイラ ghc の場合、 -fwarn-missing-methods ..]

% ikegami [上で嘘書いた -fwarn-missing-signatures です。]

% k.inaba [>痛し痒しのような気もします。 確かに。^^; リンク元でsoutaroさんが述べられているように、 OCamlは型..]


2005-11-16 [長年日記]

% [OCaml] Obj モジュールを利用したオーバーローディングもどき

なんかふと思いついて書いてみた。あくまでもお遊びだけど、まったく応用が利かないってわけでもない……かも知れない。

% cat overload_test1.ml
let add x y =
   let ox, oy = Obj.repr x, Obj.repr y in
   let is_float o =
      if not (Obj.is_block o) then false
      else
         if (Obj.tag o) = Obj.double_tag then true
         else false
   in
   let to_float o = float_of_int (Obj.obj o) in
   match Obj.is_int ox, Obj.is_int oy with
   | true, true ->
      float_of_int ((Obj.obj ox) + (Obj.obj oy))
   | true, false when is_float oy -> (to_float ox) +. (Obj.obj oy)
   | false, true when is_float ox -> (Obj.obj ox) +. (to_float oy)
   | false, false when is_float ox && is_float oy ->
      (Obj.obj ox) +. (Obj.obj oy)
   | _, _ -> failwith "error!"
% ocaml
# #use "overload_test1.ml";;
val add : 'a -> 'b -> float = <fun>
# add 1 2;;                 
- : float = 3.
# add 1.0 2;;
- : float = 3.
# add 1 2.0;;
- : float = 3.
# add 1.0 2.0;;
- : float = 3.
# add "hoge" 2;;
Exception: Failure "error!".

最初は、返値を Obj.t にしちゃって int 同士の場合は int で返そうと思ったんだが、正しい型に束縛しないと役に立たないんでやめた。 つーか、int が返ってきてるのに float に入れたりするとたぶん死ぬし。 int 同士か float 同士を許可するならこんな感じとか…

% cat overload_test2.ml
let add (x : 'a) (y : 'a) : 'a =
   let ox, oy = Obj.repr x, Obj.repr y in
   let is_float o =
      if not (Obj.is_block o) then false
      else
         if (Obj.tag o) = Obj.double_tag then true
         else false
   in
   match Obj.is_int ox, Obj.is_int oy with
   | true, true ->
      Obj.magic ((Obj.obj ox) + (Obj.obj oy))
   | false, false when is_float ox && is_float oy ->
      Obj.magic ((Obj.obj ox) +. (Obj.obj oy))
   | _, _ -> failwith "error!"
% ocaml
# #use "overload_test2.ml";;
val add : 'a -> 'a -> 'a = <fun>
# add 1 2;;
- : int = 3
# add 1.0 2.0;;
- : float = 3.
# add 1 2.0;;  
This expression has type float but is here used with type int

C レベルの API を勉強すれば、もっといろいろいじれるようになるはず。 まあ、クラッシュの危険と隣り合わせだから、よっぽどのことが無い限りはやらない方が無難だけど。 黒魔術のご利用は計画的に。

% [OCaml] Obj ネタ続き

こんなのはそれなりに役に立つのかなーとか。

% cat overload_test3.ml
let (^.) x y =
   let ox, oy = Obj.repr x, Obj.repr y in
   let to_str o =
      if Obj.is_int o then string_of_int (Obj.obj o)
      else
         let tag = Obj.tag o in
         match tag with
         | _ when tag = Obj.double_tag ->
            string_of_float (Obj.obj o)
         | _ when tag = Obj.string_tag -> Obj.obj o
         | _ -> failwith "error."
   in
   (to_str ox) ^ (to_str oy)
# #use "overload_test3.ml";;
val ( ^. ) : 'a -> 'b -> string = <fun>
# "hoge" ^. 1;;
- : string = "hoge1"
# 2 ^. "hoge";;
- : string = "2hoge"
# 2005 ^. "/" ^. 11 ^. "/" ^. 16;;
- : string = "2005/11/16"

まあ、ちゃんとやるなら素直に format string を使った方が良いけど。

% [OCaml] Obj ネタさらに続き

調子にのって F# バージョンとか。

% cat overload_test2.fs
let add (x : 'a) (y : 'a) : 'a =
   let ox, oy = Obj.repr x, Obj.repr y in
   let is_int (o : Obj.t) =
      ((o.GetType()).ToString()) = "System.Int32"
   in
   let is_float (o : Obj.t) =
      ((o.GetType()).ToString()) = "System.Double"
   in
   match is_int ox, is_int oy with
   | true, true ->
      Obj.magic ((Obj.obj ox) + (Obj.obj oy))
   | false, false when is_float ox && is_float oy ->
      Obj.magic ((Obj.obj ox) +. (Obj.obj oy))
   | _, _ -> failwith "error!"
> #use "overload_test2.fs";;
val add : 'a -> 'a -> 'a

> add 1 2;;
val it = 3
val it : int

> add 1. 2.;;
val it = 3
val it : float

> add 1 2.;; 
toplevel(2,6): error: FS0001: This expression has type
        float
but is here used with type
        int
% cat overload_test3.fs
let (^.) x y =
   let ox, oy = Obj.repr x, Obj.repr y in
   let to_str (o : Obj.t) =
      match (o.GetType()).ToString() with
      | "System.Int32" -> string_of_int (Obj.obj o)
      | "System.Double" -> string_of_float (Obj.obj o)
      | "System.String" -> Obj.obj o
      | _ -> failwith "error."
   in
   (to_str ox) ^ (to_str oy)
> #use "overload_test3.fs";;                            
val ^. : 'a -> 'b -> string

> "hoge" ^. 10;;
val it = "hoge10"
val it : string

> 20 ^. "hoge";;
val it = "20hoge"
val it : string

> 2005 ^. "." ^. 11 ^. "." ^. 16;;
val it = "2005.11.16"
val it : string

.NET Framework のクラスリファレンスがあると楽しめそう。

% [雑談] 衝撃の事実! パンは危険な食べ物(ワラタ2ッキ)

なんともするどい風刺だなあ(笑

% [OCaml] なるべく見やすいように型宣言をコードに組み込むには

昨日のネタがらみの自分のツッコミとか、それに関連して soutaro さんのエントリとか k.inaba さんの返事とか、もろもろから。

何と言うか、書く側と読む側の都合にはいろいろ噛み合わない部分があるんだろうなあ…などと思った。 たしかに読む側からすれば、k.inaba さんが言うように型宣言が併記してあった方が概要を掴みやすいんだよね。 でも、書く側からすると型宣言をいちいち書かなくても良いというのは大きなアドバンテージなわけで。 結局、一般論でいくならこればっかりはトレードオフで、読み書き双方が納得する形の決着ってのはありえないんだと思う。

ただまあ、個人的な話でいけば、わしはわりと OCaml のコードを動的型付けの言語のような感覚で書いていたりする。 要するに、書く時に型をあんまり意識していない。 それこそ型推論ってのは、そのためにあるんだとわしは思ってる。

OCaml って厳格な型付けがあるせいで、コンパイルがある種のデバッグになっている感じがあるでしょ。 もちろん C だってコンパイルできないならバグがあるんだから、コンパイルできるか試すのがデバッグの一つになるけど、あれはコンパイルが通ったからと言って整合性が取れてるかなんてまったく保証されないから効果は薄い。 逆に OCaml ならコンパイルが通った時点で(プログラマの意図どおりかは別として)整合性が取れていることがほぼ保証される(例外が絡むとそうもいかなくなったりするが)。 そうすると意図どおり動かなかったとしても、チェックすべき箇所は探しやすい。 C みたいにちょっとしたことで謎現象が起こったりしないわけだからね。

で、コンパイルをデバッグの一部としてとらえた場合、大事なのはやっぱりヒューマンエラーが入り込む隙をなるべく減らすことだったりする。 ヒューマンエラーを減らすのに一番手っ取り早いのは『コンピュータができることはコンピュータにやらせる』ことだ。 だからどうしても必要にならない限り、自分で型宣言はしない。 とまあ、そんな感じでわしはやってるという話。

ただまあ、これって OCaml だから許される話で、もし Haskell で何かを書くなら一生懸命型宣言しちゃいそうではある。 何でかって言うと、やつには関数のオーバーロードがあるでしょ。 実はわし、オーバーロードが非常に嫌いでね。 なんつーか静的型付け言語にとって、キャストに匹敵する凶悪な敵って感じがする。 なもんだから、わしはぶっちゃけ OCaml の < とか > とか = とかそういうこっそりオーバーロード関数なやつらが嫌いで仕方なかったりすんの。 そういや、Haskell には暗黙のキャストみたいなのもあるよね(厳密には違うのかもわからんが)、あれもイヤだなあ。

……えーと、何の話だっけ。 そうそう前振りって言うか無駄話が長過ぎたが、OCaml で mli ではなく ml ファイルになるべく見やすい形で型宣言を埋め込む方法の話。 つっても大したことじゃなくてすぐ終わるんだが、まあちょっと書き方工夫しましょうって感じで。

let add x y = x + y

なんていう関数を定義する場合、まあわしの場合 C やその亜種に慣れてるせいもあって、どうしてもこういう形で書いてしまうわけで、かつ、すでに書いた通り必要にならなければ型宣言は書かないポリシーだからして、もしあとで型宣言を追加する必要が出た場合は、結局こんな風になる。

let add (x : int) (y : int) : int = x + y

すごく汚らしい。 soutaro さんが言うところの『書きにくい』も、この辺の印象から来てると思われる。 ただ、最初っから型宣言を書くぞーと決めてかかるなら、こんな書き方もできる。

let add : int -> int -> int = fun x y -> x + y

Haskell のように宣言と定義が別々になるわけじゃないから、完全にすっきりしてるとは言えないけど、前の例よりはずっとすっきりしてるよね。 実際 = までの部分を見れば、ocamlc -i で出てくるインターフェイスと見た目はほぼ同じ。 最初は宣言を書かないにしても、

let add = fun x y -> x + y

みたいな感じで定義するようにすれば、あとで足すときも安心かも。 ただその場合、

let add (x : int) y = x + y

みたいに一部分だけ型を指定すれば良いときにも、ちゃんと全部書かなきゃならなくなるのが玉に傷。 まあ、

let add : int -> 'a -> 'a = fun x y -> x + y

てな風にも書けるけど、実際には int -> int -> int になるから、これはちょっと違うだろうと(苦笑

そんなわけで、イマイチまとまらないがこんなところで。 ともあれ、わしは型宣言を書かない派。

本日のツッコミ(全6件) [ツッコミを入れる]

Before...

% jijixi [ああ、違和感の元はそこかなあ。 > 整数でもありうるし浮動小数点数でもありうる と言うのが、わしの目にはキャストのよ..]

% TrackBack [http://d.hatena.ne.jp/mmatsuoka/20060108#1136648479 晩ごはんの履..]

% TrackBack [http://d.hatena.ne.jp/mmatsuoka/20061001#1159697233 晩ごはんの履..]


2005-11-17 [長年日記]

% [OCaml][Haskell] オーバーローディングが良いか悪いかをあえて断ずることはしませんよ

えーと、昨日 Haskell に対してちょっと愚痴ったことへの反応(昨日分のツッコミ欄参照)とかもろもろに関して、エチケットペーパーを敷いておくテスト。

ぶっちゃけ演算子のオーバーロードがイカンとか叫ぶつもりはございません。 あるならあるで便利だからね。 ただ『個人的な嗜好』でいけば、好きじゃないってだけの話なのです。

あとまあ、整数は整数として使わせてくれと。 わしは数学屋さんじゃないので 1 / 2 は 0 が良いんですと。 そういう、なんと言うか貧乏くさい計算機屋的思考が影響してるとかいないとか。

ともあれ、OCaml も…

# 1.0 + 2.0;;
This expression has type float but is here used with type int

これくらいは許してくれても良いんじゃないかってのは、心情的にはわかります。 でも、たぶん今の型システムのままだとムリですな。 昨日の Obj.magic ネタを使えばそれっぽくできないことはないけど、あれだと int か float だけってのができなくて、型は 'a -> 'a -> 'a にしつつ適合しないものは実行時エラーではじくしかない。 んで、きっとそれって ML のポリシーに反するでしょう。 そうすると、言語仕様を拡張するしかなさそう。

% [Haskell] そもそもクラス構成がよくわかってないせいもある気が

向井さんの言を借りれば整数リテラルの型は Num a => a なわけでしょ。 でもわしは、hugs で

Prelude> 1
1 :: Integer

こう表示されるんで、てっきり Integer だと思ってたわけね。 でもあれだ、これって『1』ってリテラルが評価された結果として、こうなってるだけってことなのね。 つーか、型を調べるならこうすべきだったんだ。

Prelude> :t 
1 :: Num a => a

じゃあ小数は……

Prelude> :t 2.0
2.0 :: Fractional a => a

Fractional って何だよ〜。 えーと Prelude.hs を見ると、

class (Num a) => Fractional a where

…か。 これは要するに Fractional と言うのは Num を包含したクラスだよ……ってことなんだよね、たぶん。 ちなみに例の式だと型はどうなるんだ?

Prelude> :t 1 + 2.0
1 + 2.0 :: Fractional a => a

あーそー。つーことは、イメージ的には Num がより守備範囲の広い Fractional ってクラスに変換されてる感じがするなあ。

ところで Fractional とは混ざらないような位置に Integral ってクラスもあるね。 言葉の意味からすると、誤差が存在する数値が Fractional で誤差が存在しないのが Integral って感じなのかな。 んで、Num は Num → Real → Integral って感じで Integral に辿り着ける。

class と instance の違いがよくわからない。 多相的なのが class で、それに具体的な型をあてはめたのが instance ?

なんか結局のところ、それぞれの値には採り得る型がいくつかあって(整数なら Int, Integer, Num a, Integral a みたいに)、適用される文脈によって自動的に変換されてるように見えてしまうんだが、錯覚なのかな。 オーバーロード関数がきっちり定義されてるから、そう見えるだけとか? それとも遅延評価の呪いで、評価されるまでは抽象的な型(それがクラス?)のままでいて、評価されて初めてプリミティブな型に確定されるって寸法なのかな。

もう脳が沸騰しそうなんで、この辺でやめておこう。 また今度、暇を見ながらちまちま勉強することにする。

% [PC][Haskell] Haskell をいじるなら、一応 Concurrent Clean も見ておかないといけないのかも…

…と思って、言語リファレンスとか見てみたが、速攻で挫折した。 だってここの『10.1.2 正格文脈と遅延文脈』ての読んでぐったりしたのよ。

通常は正格評価で必要に応じて遅延評価(OCaml とか。Lisp 系もそうかな)は良い。 通常は遅延評価で必要に応じて正格評価(Haskell は一応そうだよね)も、個人的にはキツイけどまあ良い。 どちらも一本筋が通っていて、それを変えるにはプログラマ自身が指示しなきゃいけないのは、むしろ安心だ。 でもさ…

(文脈によって)ある特定の式が遅延か正格かが指定される。

…てのは、どうなのよ(カッコ内はわしの補足ね)。 普段は遅延評価だけど、ところにより(いつの間にか)正格評価…だなんて気持ち悪すぎる。 まあ、効率を求めてのことなんだろうし、慣れてしまえば気にならないのかもしれないが、少なくとも今のわしには混乱の元にしか思えない。

Haskell だっていっぱいいっぱいなのに無理。 てことで、Clean はスルー。縁があったらまた会いましょう。 ちゃんと遅延評価と正格評価のメリットデメリットを把握できるようになって、かつ普段から遅延評価でも気にならなくなるようなことがあれば、すごく良いものなのかも。 でも、わしがそうなることは難しい気がするなあ。 つーか、正格評価にずっぷりはまってる人間が遅延評価万歳を叫ぶようになるには、どれほどの訓練が必要なのかと。

本日のツッコミ(全2件) [ツッコミを入れる]

% k.inaba [数値リテラルも、オーバーロードされてます。ちと不正確な言い方ですが、100というのはInt型の百とInteger型の..]

% jijixi [ぐあ、なんてややこしい…… あー、でも数値だけが特別なんだったら、あきらめて気にしないのが良いんですかねえ(苦笑]


2005-11-18 [長年日記]

% [OCaml] なんとなく Java のラッパークラスみたいのを作るとすれば…

こんな感じかなーとか思いながら適当に書いてみた。 Haskell のクラス構成も参考にしつつ。

class virtual eq =
   object (self)
      method eq o = self = o
      method neq o = not (self#eq o)
   end

class virtual ord =
   object (self)
      inherit eq
      method compare o = Pervasives.compare self o
      method lessthan o = (self#compare o) < 0
      method greaterthan o = (self#compare o) > 0
      method lessequal o = (self#compare o) <= 0
      method greaterequal o = (self#compare o) >= 0
   end

class virtual ['a] base_obj n =
   object
      inherit ord
      val mutable v : 'a = n
      method get_val = v
      method private set_val a = v <- a
      method eq o = v = o#get_val
      method compare o = Pervasives.compare v o#get_val
   end

class virtual ['a] adder =
   object
      method virtual add : 'a -> 'a
   end

class virtual ['a] operator =
   object
      inherit ['a] adder
      method virtual sub : 'a -> 'a
      method virtual mult : 'a -> 'a
      method virtual div : 'a -> 'a
      method virtual moduli : 'a -> 'a
      method virtual negate : 'a
      method virtual abs : 'a
   end

class int_obj i =
   object (self)
      inherit [int] base_obj i
      inherit [int_obj] operator
      method add o = new int_obj (v + o#get_val)
      method sub o = new int_obj (v - o#get_val)
      method mult o = new int_obj (v * o#get_val)
      method div o = new int_obj (v / o#get_val)
      method moduli o = new int_obj (v mod o#get_val)
      method negate = new int_obj (-v)
      method abs = new int_obj (abs v)
      method to_i = self
      method to_f = new float_obj (float_of_int v)
      method to_s = new string_obj (string_of_int v)
   end
and float_obj f =
   object (self)
      inherit [float] base_obj f
      inherit [float_obj] operator
      method add o = new float_obj (v +. o#get_val)
      method sub o = new float_obj (v -. o#get_val)
      method mult o = new float_obj (v *. o#get_val)
      method div o = new float_obj (v /. o#get_val)
      method moduli o = new float_obj (mod_float v o#get_val)
      method negate = new float_obj (-.v)
      method abs = new float_obj (abs_float v)
      method to_i = new int_obj (int_of_float v)
      method to_f = self
      method to_s = new string_obj (string_of_float v)
   end
and string_obj s =
   object (self)
      inherit [string] base_obj s
      inherit [string_obj] adder
      method add o = new string_obj (v ^ o#get_val)
      method to_i = new int_obj (int_of_string v)
      method to_f = new float_obj (float_of_string v)
      method to_s = self
   end

module Util =
   struct
      let obj_i i = new int_obj i
      let obj_f f = new float_obj f
      let obj_s s = new string_obj s

      let (=) x y : bool = x#eq y
      let (<>) x y : bool = x#neq y
      let (<) x y : bool = x#lessthan y
      let (>) x y : bool = x#greaterthan y
      let (<=) x y : bool = x#lessequal y
      let (>=) x y : bool = x#greaterequal y

      let (+) x y = x#add y
      let (-) x y = x#sub y
      let ( * ) x y = x#mult y
      let (/) x y = x#div y
      let (mod) x y = x#moduli y
      let neg x = x#negate
      let abs x = x#abs
   end

Java なんかと違うところは、必要なメソッドさえ互換性があれば、共通インターフェースとか定義しなくて良いこと。 やっぱ、これはこれでおもしろいな。 厳密な型付けをしつつも、結構柔軟な構成にできる。 つーかこの辺が理解できるまでは、なんて堅苦しいオブジェクトシステムだと思ってたもんだが(苦笑

ちなみに Haskell の…

class Eq a where
    (==), (/=) :: a -> a -> Bool

    -- Minimal complete definition: (==) or (/=)
    x == y      = not (x/=y)
    x /= y      = not (x==y)

みたいに循環定義(用語変?)することはできないんで、オーバーライドするときは常に eq メソッドの方を…とか。 ついでに ocamlc -i の結果も載せとく。

class virtual eq :
  object ('a) method eq : 'a -> bool method neq : 'a -> bool end
class virtual ord :
  object ('a)
    method compare : 'a -> int
    method eq : 'a -> bool
    method greaterequal : 'a -> bool
    method greaterthan : 'a -> bool
    method lessequal : 'a -> bool
    method lessthan : 'a -> bool
    method neq : 'a -> bool
  end
class virtual ['a] base_obj :
  'a ->
  object ('b)
    val mutable v : 'a
    method compare : 'b -> int
    method eq : 'b -> bool
    method get_val : 'a
    method greaterequal : 'b -> bool
    method greaterthan : 'b -> bool
    method lessequal : 'b -> bool
    method lessthan : 'b -> bool
    method neq : 'b -> bool
    method private set_val : 'a -> unit
  end
class virtual ['a] adder : object method virtual add : 'a -> 'a end
class virtual ['a] operator :
  object
    method virtual abs : 'a
    method virtual add : 'a -> 'a
    method virtual div : 'a -> 'a
    method virtual moduli : 'a -> 'a
    method virtual mult : 'a -> 'a
    method virtual negate : 'a
    method virtual sub : 'a -> 'a
  end
class int_obj :
  int ->
  object ('a)
    val mutable v : int
    method abs : int_obj
    method add : int_obj -> int_obj
    method compare : 'a -> int
    method div : int_obj -> int_obj
    method eq : 'a -> bool
    method get_val : int
    method greaterequal : 'a -> bool
    method greaterthan : 'a -> bool
    method lessequal : 'a -> bool
    method lessthan : 'a -> bool
    method moduli : int_obj -> int_obj
    method mult : int_obj -> int_obj
    method negate : int_obj
    method neq : 'a -> bool
    method private set_val : int -> unit
    method sub : int_obj -> int_obj
    method to_f : float_obj
    method to_i : 'a
    method to_s : string_obj
  end
and float_obj :
  float ->
  object ('a)
    val mutable v : float
    method abs : float_obj
    method add : float_obj -> float_obj
    method compare : 'a -> int
    method div : float_obj -> float_obj
    method eq : 'a -> bool
    method get_val : float
    method greaterequal : 'a -> bool
    method greaterthan : 'a -> bool
    method lessequal : 'a -> bool
    method lessthan : 'a -> bool
    method moduli : float_obj -> float_obj
    method mult : float_obj -> float_obj
    method negate : float_obj
    method neq : 'a -> bool
    method private set_val : float -> unit
    method sub : float_obj -> float_obj
    method to_f : 'a
    method to_i : int_obj
    method to_s : string_obj
  end
and string_obj :
  string ->
  object ('a)
    val mutable v : string
    method add : string_obj -> string_obj
    method compare : 'a -> int
    method eq : 'a -> bool
    method get_val : string
    method greaterequal : 'a -> bool
    method greaterthan : 'a -> bool
    method lessequal : 'a -> bool
    method lessthan : 'a -> bool
    method neq : 'a -> bool
    method private set_val : string -> unit
    method to_f : float_obj
    method to_i : int_obj
    method to_s : 'a
  end
module Util :
  sig
    val obj_i : int -> int_obj
    val obj_f : float -> float_obj
    val obj_s : string -> string_obj
    val ( = ) : < eq : 'a -> bool; .. > -> 'a -> bool
    val ( <> ) : < neq : 'a -> bool; .. > -> 'a -> bool
    val ( < ) : < lessthan : 'a -> bool; .. > -> 'a -> bool
    val ( > ) : < greaterthan : 'a -> bool; .. > -> 'a -> bool
    val ( <= ) : < lessequal : 'a -> bool; .. > -> 'a -> bool
    val ( >= ) : < greaterequal : 'a -> bool; .. > -> 'a -> bool
    val ( + ) : < add : 'a -> 'b; .. > -> 'a -> 'b
    val ( - ) : < sub : 'a -> 'b; .. > -> 'a -> 'b
    val ( * ) : < mult : 'a -> 'b; .. > -> 'a -> 'b
    val ( / ) : < div : 'a -> 'b; .. > -> 'a -> 'b
    val ( mod ) : < moduli : 'a -> 'b; .. > -> 'a -> 'b
    val neg : < negate : 'a; .. > -> 'a
    val abs : < abs : 'a; .. > -> 'a
  end

% [PC] 私の知らなかった C++([鍋]鍋あり谷あり)

ある意味タイムリーな話題。 話の趣旨とは違うんだろうけど(数値リテラルの型の話だろうから)、こういうのがあるからオーバーロードは気持ち悪い…と言うのがわしの感想。 ただでさえ暗黙のキャストで気持ち悪いのに、さらに関数オーバーロードを乱用されると何が起こってるのか追うのがツライったらもう。 さらに演算子オーバーロードが絡んでくると、もう死ぬしかない。

……ともあれ、

この仕様を知らなかったことについては、あまり修行が足りないという気持ちにはならなかった。

には同意。 こんなのを憶えるよりも、こんなことに悩まされるようなコードを書かないための修行をした方が良いっすよね。

% [OCaml] 多重継承かインターフェイスか

最近ようやく OCaml におけるオブジェクトの使い方のコツがわかってきた感じなんで、どうしても『にわかなやつは語りたがる』の法則でいろいろ書いてしまうのである(笑

んで、さっきのラッパークラスネタの中の eq, ord, adder, operator というクラスは、Java の interface みたいな使い方(と言うか、Ruby の Mix-in かな)をしているクラスなんだが、この例ではこれらのクラスを virtual クラス(インスタンス化しないクラス)として定義しておいて、多重継承することで利用している。

OCaml にはこれ以外にも class type というのがあって、これは Java の interface の本来の使い方に近い代物だと思う。 要するにクラスの抽象化と言うか、そんな感じのためのもの。 そんで Java のノリでもって、この class type を Mix-in にも利用しようとするとハマる。

そもそも一つのクラスで複数のインターフェイスを実装する方法がわからない(わしが文法を知らないだけでやり方はあるのかも知れないが)。 それにたとえ Mix-in 的に使えたとしても、Java の interface が持つのと同じ欠点(実装を使いまわせない)があるから、結局多重継承を使ったほうが便利が良い。

と言うわけで、せっかく多重継承があるんだから Mix-in をやりたいなら多重継承を使おう。 ただし、むやみやたらに親子関係を作るのは見通しを悪くするので、その辺は気をつけないとね。 Mix-in 用のクラスは、それ専用にして他のクラス構成と混ぜないようにした方が良い。

んで、class type は何に使うかってーと、本来のインターフェイス的な使い方をしよう。 よくあるパターンでは、(ライブラリの)ユーザにはインターフェイスだけ見せるようにして、実装を隠蔽するとかそういうの。 あと、宣言と定義をわけるような使い方とか(これは前に書いたような気がする)。 他にも Java 的な見方であれば、複数のクラスを一まとめにして扱うような用途(List とかみたいな)もあるにはあるけど、さっきも書いたとおりメソッド構成に互換性さえあれば、普通に統一的に使えてしまうんで、あんまり意味は無いと思われる。

% [雑談][OCaml] OCaml をいじり始めて二周年(もう過ぎてるけど)

わしが OCaml に初めて触ったのは、ちょうど日記を tDiary に移行した頃だから、2003 年の 10 月下旬だ。 ちなみにわしに OCaml を伝道したのは、最近ここにも現れていた某 S 氏である(本人が気付いてるかはわからないが。その節はお世話になりました)。

なつかしいなあ。あの頃はそれこそ「末尾再帰って何だよ!?」みたいなレベルだったし。 だいたい OCaml のオの字も知らなかったし。 Lisp 系にはカッコ地獄で挫折してたし。 Haskell は名前くらいは聞いたことあった気がするけど、遅延評価って何?って感じで敬遠してたし。 変われば変わるもんだね。 ……まあ、今でも Lisp はイマイチ手を出す気にならんけど(苦笑

それにしても、最近は以前に比べて随分 OCaml の(日本語の)情報が出回るようになってきたね。 いや、元々あったのを最近になって知っただけなのかも知らんが。 確か始めた頃は、情報源って OCaml.jp五十嵐先生のページと、あとここくらいしか無かった気がする。 まあ、今もまとまったページってそんなに増えてないかもしれないけど、日記とかブログとかで結構情報が入る。

F# がもう少しがんばって、ML 人口を底上げしてくれれば、OCaml だってもっと盛り上がるだろうに……とか思う今日この頃。 つーか、OCaml で商売できるような世の中ぷりーず。

% [Haskell] 止まらない比較(どーんとやってみよう)

Eq クラスの (= =) と (/=) を再定義しないとどうなるかって話。笑った。

ところで OCaml の場合、class 定義だとどうにもうまくいかなかったんだが、通常の関数として定義するなら可能だと気付いた。 Haskell に負けてたまるか(何か違う)。

# let (=) x y = not (x <> y)
  and (<>) x y = not (x = y);;
val ( = ) : 'a -> 'a -> bool = <fun>
val ( <> ) : 'a -> 'a -> bool = <fun>
# 1 = 1;;
- : bool = true

これは間違い。なぜかと言うと、(=) の定義で使われてる <> は Pervasives.(<>) で、(<>) の定義で使われてるのは Pervasives.(=) だから。 この辺に、再帰の時 rec 修飾を強制する意味が見えなくもない。 こういう仕様の方が、関数を上書きしてやるときに元の関数を素直に使えて便利っぽい。

んで正しく(?)やるなら、こう。

# let rec (=) x y = not (x <> y)
  and (<>) x y = not (x = y);;
val ( = ) : 'a -> 'b -> bool = <fun>
val ( <> ) : 'a -> 'b -> bool = <fun>
# 1 = 1;;
Stack overflow during evaluation (looping recursion?).

わーい(笑

でも、上書きするときはセットでやらないとダメなのデス...orz

# let (=) x y = Pervasives.(=) x y;;
val ( = ) : 'a -> 'a -> bool = <fun>
# 1 = 1;;
- : bool = true
# 1 <> 1;;
Stack overflow during evaluation (looping recursion?).

もう、OCaml さんったら正格すぎ。

本日のツッコミ(全2件) [ツッコミを入れる]

% 向井 [循環定義というかそれはデフォルト定義なので、どっちか一方を定義しないと止まらなくなりますよ(笑) # 少なくともどち..]

% jijixi [あはは、そりゃそうですね。 でもその止まらない定義もすんなりできちゃうのが、Haskell らしいという感じですね。]


2005-11-19 [長年日記]

% [Mac][FreeBSD] 旧 iMac (Rev.D) に FreeBSD 6.0R を入れてみようと思ったが……

ブートローダは立ち上がったものの、プロンプトがカウントダウンしてる隙に鼻かんで戻ってきたらカーネルパニックしてた...orz

デバッグオプション付きだったらしくカーネルデバッガに落ちていたが、キーボードが効かずに何もできず。 今日は一日これをいじるつもりだったのに、こんないきなり出ばなをくじかれるとは思わなかったよ。 あーもー何もやる気がしない。

いや、厳密に言えば、他にもやることはいっぱいあるんだけど、せっかくやるぞーって気合い入れてたのがポシャったときに急に気持ちを切り替えられるほど人間ができてないんだよぅ、しくしく。

% [OCaml] 正格じゃなくてなんて言えば良いんだろ?

この件

用語がおかしかったですか。ちらしの裏なんで、ごめんなさい。 正確にはなんて言うのかな。静的ディスパッチ? OCaml は関数の定義なんかもきっちり前から順番に処理していくから、やっぱりイメージ的には『正格』って感じなんだけど。

ともかくあれは OCaml をいじり始めた頃、気持ち悪いと思ってた現象の一つだったりする。 今はすっかり慣れたけど。 すでにある名前を上書きできないならそうでもなかったんだろうけど、当時 Ruby とかでは…

% irb
irb(main):001:0> def f x; x * 2; end
=> nil
irb(main):002:0> f 2
=> 4
irb(main):003:0> def g y; f y; end
=> nil
irb(main):004:0> g 2
=> 4
irb(main):005:0> def f x; x * 2 * 2; end
=> nil
irb(main):006:0> f 2
=> 8
irb(main):007:0> g 2
=> 8

こうなることを知っていたんで、上書きしてもかたくなに以前の定義を守り続ける OCaml に対して、なんて頑固なヤツだと思ってた(笑

ちなみに、オブジェクトであんなことやられるとたまったもんじゃないんだけど(オーバーライドのとき困る)、その辺はちゃんと現在の定義を見てくれるようになってるね。

% [OCaml] ローカルに open するコーディングスタイル

open すると名前空間が汚れるんでイヤってのはあるんだけど、中置演算子を定義しているモジュールは open しないとそれらを中置で使えないんでありがたみが薄い。 とは言え、必要なものだけ持ってくるってのが今の OCaml ではできないから、中置演算子を使おうと思うと余計なものまで展開されてしまって嬉しくない。

そんなときに、こんな方法もあるかな…という例。


% cat openTest.ml
module Local =
   struct
      open Num
      module Export =
         struct
            let (+/) = (+/)
            let (-/) = (-/)
            let ( */ ) = ( */ )
            let (//) = (//)
            let ( **/ ) = ( **/ )
            let (=/) = (=/)
            let (</) = (</)
            let (>/) = (>/)
            let (<=/) = (<=/)
            let (>=/) = (>=/)
            let (<>/) = (<>/)
         end
   end
open Local.Export

まあ、一個一個 let (+/) = Num.(+/) とかやってくのとどう違うんだって話はあるけど(苦笑 。 あと、例に使った Num モジュールは、そもそも open されることを前提にわりと冗長な名前を使って定義されてるから、ここまで神経質になる必要も無い気がする。

それから、open するのがその場限りで良いなら、サブモジュールの中で処理を完結させてしまえば良いね。

% cat subMod.ml
module Local =
   struct
      open Num
      let return =
         let a = num_of_int 10
         and b = num_of_string "1/10" in
         string_of_num (a +/ b)
   end
let _ = print_endline Local.return

あと、ライブラリを作る側の視点で話をすると、中置演算子なんかを作るときは open しやすいようにサブモジュールにまとめといてあげると良い気がする。 昨日のラッパークラスネタでそれっぽいことやってるけど(Util モジュールのこと)。

% [雑談] 食い物は腐りかけが(略

冷蔵庫に賞味期限が 11/10 と表示されたハムがあるのを見つけて、「まあ二日や三日過ぎてても大丈夫だろ」と思って食った。 ・・・いや、今日 19 日ですよ?

なんかいつもよりうまかった。 やっぱ良い塩梅に熟成されてたんだろうか。 まあその後、すでに三回くらい便所に通ってるわけだが(爆

せめて火を通して食うべきです。

% [OCaml] 『OCamlの型推論』(via d.y.d)

39 ページ目とか笑う。P35 ですでに連想して笑うし。

あとまじめな話、高橋メソッドじゃなかったらわかりやすい説明だと思う(身も蓋もないな)。 記号がたくさん使われてるのに、ああやってどんどん画面が移ってしまうとどれがどの記号に対応してたかわからなくなっちゃって、全然わかってない人に説明するにはツライ気がする。 内容を一覧できるレジュメとセットのプレゼンならいけるかなあ。 でも、それだとネタバレが……

まあ、高橋メソッドは内容が無いのをごまかすテクニックだから(たしか本人がそう言ってた気がする)、あんまりややこしいことを説明するのには向かないんだろうな。 とは言え、妙におもしろいので次回にも期待。

% [PC] 私の知らなかった C++ もうひとつ([鍋]鍋あり谷あり)

暗黙のキャストだけでも十分トラブルの元なのに、関数のオーバーロードが絡むとさらにグダグダになるという話。 C++ 怖い。 これに比べたら C って何て平和な言語だろ。

C++ のこういうところを整理しただけの言語を ++C とかいう名前で誰か作るといいんじゃないかと思うが、どうだろう。

ほんとそう思う。 もう少し型の在り方を厳格にしてくれれば(要するに変に C との互換性を引きずってなければ)、わりと良い言語だと思うんだけどね。 暗黙のキャストが無ければ、もっと平和なのに。


2005-11-20 [長年日記]

% [雑談] しばらくおとなしくしていよう(愚痴)

車検費用が想定範囲を超えてきたんで、がっくり。 お金無い、お金無いよう、しくしく。 タイヤも買わなきゃならんから、もうからっけつじゃん。

はあ、切り詰めていかにゃ。

それにしても、リサイクル費用 8,860 円が妙にうらめしい今日この頃。


2005-11-21 [長年日記]

% [RBM] しばらく前から、またぼちぼちやってる

……んだけど、今日は二度目のコード全捨て決行。 まあ、全捨てと言いつつちょこちょこコピペで救ってたりもするが、肝になる部分を捨ててるのは確か。 ぶっちゃけ、OCaml のオブジェクトがらみで想定外の制限にぶつかって、それをちまちま回避するくらいなら最初っから書いた方が早いとか思っちゃう感じ。 つーか、そう思っちゃう程度にしか書けてないという話も。 前回が 300 行弱で今回が 600 行弱だから、大した量じゃない。

なんつーか、結局オブジェクトに何でもかんでも詰め込もうとすると、却ってコードが汚くなる感じなんで、必要最小限だけをオブジェクトに包んだら、あとはユーティリティ関数でいじることにした。


2005-11-22 [長年日記]

% [雑談] アップルのiMac G5欲しい!

なんか知らんけど、最近はてなでこの文句を見かけるんで何かと思ってたんだが、こういうことだったのか。 はてなじゃない人にもくれれば良いのに(ムリ

% [PC] C++の不思議な世界 - 2度目は意味が違う([鍋]鍋あり谷あり)

わはは、C++ 気持ち悪すぎ。 やっぱ真っ当なコードを書こうと思ったら、それなりに命名規則は必要だよね。 名前重要。

演算子オーバーロードなんて、難読コードコンテストのために存在するんではないのかと(笑

% [雑談] プログラマは 0 から数える?

某所のブログにはサブタイトル(?)として『プログラマは0から数える』って書いてある。 その言葉にことさら異を唱えるつもりは無いが、本当のプログラマは『0 かそうでないかだけ考える』ものなんじゃないかと思ったりもする。

『0』じゃなくて『無』とか『null』とかの方が良いかな。 『プログラマは数えない』とかもアリかも。 数えるのは常にコンピュータである。 と言うか、そうさせるべきである。とかね。

% [本日のリンク元] Google 検索 (xs ocaml)

パターンマッチを持たない言語(ぶっちゃけ非関数型言語)しか経験が無くて、初めて OCaml の勉強をし始めた人が、

let rec f lst =
  match lst with
    [] -> ...
  | x :: xs -> ...

みたいなのを見て、「xs って何だよ〜」とか疑問に思ったというストーリーを想像した。 つーか、実際わしがそう思ってた(笑

match の使い方がわかってないと、これって何か意味があるもののように思えちゃうんだよね。 実際は単なる名前付けなわけだけど。 たぶんこれって、

何か一つの要素 = x
x が複数集まったもの(複数形) = xs

ぐらいの意味なんでしょ? いや、もしかしたらもっと由緒ある由来が無いとは限らないけど。

ところで、こんな話は全然関係なくて、実は OCaml と Perl を連携させる話でした……とかだったりしないよね?

% [雑談] どうしても言っておきたいこと

エチルアルコールは二日酔いの香りがする。 うぅ、飲んでもいないのに具合悪い。

% [OCaml] 相互に参照するメソッド

ラッパークラスネタに関する soutaro さんとこのエントリで、なにやら訂正が入っていたものの、何をどう勘違いしていたのかがわからなかったんでスルーしてた。 …ら、実はコメントで少しやり取りがあったみたいなのに今さら気付いた。 はてなはコメントが RSS で流れないんで気付かなかったよ...orz

んでまあ、sumii さんが指摘しているとおり、相互に参照するメソッド自体は定義できる。 できるんだけど、例の eq クラスのようなものを作る場合にはちょっと問題があって、例えば素直にこうすると…

# class eq =
  object (self)
    method eq o = not (self#neq o)
    method neq o = not (self#eq o)
  end;;
Some type variables are unbound in this type:
  class eq : object method eq : 'a -> bool method neq : 'a -> bool end
The method eq has type 'a -> bool where 'a is unbound

こうなる。 要するに、eq の型が 'a -> bool になるんで型が不定ですよってこと(そーゆー場合は多相型のクラスにしないといけない)。 こういう場合、何とかするためには二つの方法があって(厳密には三つ目もあるが、どうせ役に立たんのでとりあえず無視)、

(* その1:メソッドに型指定を入れる方法 *)
# class eq =
  object (self)
    method eq (o : eq) = not (self#neq o)
    method neq o = not (self#eq o)
  end;;
class eq : object method eq : eq -> bool method neq : eq -> bool end
(* その2:多相クラスにする方法 *)
# class ['a] eq =                 
  object (self)                   
    method eq (o : 'a) = not (self#neq o)
    method neq o = not (self#eq o)       
  end;;                                  
class ['a] eq : object method eq : 'a -> bool method neq : 'a -> bool end

こんな感じかな。 んで、このクラスがこれだけで完結するなら良いんだけども、これを継承して云々ってなってくるといろいろと問題がある。 特に一つ目なんかはメソッドのオーバーライドもろくにできないんで泣けること請け合い。

# class ['a] base n =
  object 
    inherit eq
    val v : 'a = n
    method get = v
    method eq o = v = o#get
  end;;
This expression has type eq
It has no method get

# class ['a] base n =      
  object                   
    inherit eq             
    val v : 'a = n         
    method get = v         
    method eq (o : 'a base) = v = o#get
  end;;
This expression has type 'a base
It has no method get

eq クラスに get : 'a なメソッドを作っとけば良いかも知れないが、それなら結局多相クラスにしなきゃならないし、等値比較を目的として分けてあるクラスなのに get って何だよ、みたいなことにもなってうれしくない。

二つ目だと、

# class ['a] base n =
  object     
    inherit ['a] eq
    val v : 'a = n
    method get = v
    method eq o = v = o#get
  end;;
class ['a] base :
  'a ->
  object
    constraint 'a = < get : 'a; .. >
    val v : 'a
    method eq : 'a -> bool
    method get : 'a
    method neq : 'a -> bool
  end

てな感じで一瞬うまくいったと思うんだが、実際は型情報をよく見るとわかるとおり…

# let o = new base 1;;
This expression has type int but is here used with type
  < get : 'a; .. > as 'a

こんな風になるんでダメ。 なんとかするには base クラスを…

# class ['a, 'b] base n =   
  object                   
    inherit ['a] eq        
    val v : 'b = n         
    method get = v         
    method eq o = v = o#get
  end;;
class ['a, 'b] base :
  'b ->
  object
    constraint 'a = < get : 'b; .. >
    val v : 'b
    method eq : 'a -> bool
    method get : 'b
    method neq : 'a -> bool
  end

こうしないといけない。 この後、継承するたびにいちいち 'a だの 'b だのが、どれに対応してどうのこうのと考えなきゃならなくてやっぱりうれしくない。 少なくとも、eq クラスのためにこんな苦労をするのはバカバカしいと思う。

ちなみに二つ目の方法は、この時に「どうにもうまくいかなかった」と書いたあとに思いついたんだけど、例のサンプルを書く時点で思いついてても採用しなかったと思う。

% [OCaml] 自分自身を参照する際の制限

クラス定義内で自分自身を参照する場合には、自分自身を静的な型の値として扱えないという制限があるらしく、例えばこんなクラスは定義できない。

# class hoge =
  object (self)
    val mutable obj : hoge option = None
    method me_or_other =
      match obj with
      | None -> self
      | Some o -> o
  end;;
This expression has type hoge but is here used with type
  < me_or_other : 'a; .. >
Self type cannot escape its class

要するに、ここで self は hoge クラスであることは明白なのに、hoge クラスであるとみなされない。 人間の目からは self も new hoge も同じ型なのに、型が一致しないってことになってしまうのだ。 わしはこれで一度切ない思いをした……まあ、それは良いや。

ちなみにこの件はマニュアルの Reference to self セクションの末尾辺りに載ってるが、そこを見てもわかるとおり一応回避策が無いこともない。

# let hoge =                            
  object (self)                         
    val mutable obj = None              
    method me_or_other =  
      match obj with      
      | None -> self      
      | Some o -> o       
  end;;                   
val hoge : < me_or_other : 'a > as 'a = <obj>

いつの頃からか存在するオブジェクト式とかいうやつで、クラス定義じゃなくオブジェクトを直接作ってしまっている。 活用する場合は Cloning objects が参考になるかも。 当然のことながら、型ではないので継承とかはできない。

ちなみにさっきのネタで書いた三つ目の方法ってのはこれ。 継承できないから、アレの役には立たないが、一応書いとくとこんな感じで作れる。

# let eq =
  object (self)
    method eq o = not (self#neq o)
    method neq o = not (self#eq o)
  end;;
val eq : < eq : 'a -> bool; neq : 'a -> bool > = <obj>
# eq#eq eq;;
Stack overflow during evaluation (looping recursion?).

% [OCaml] 相互に参照その2

soutaro さんから的確な意見が返ってきて目から鱗。 また一つ賢くなった。

あー、でもそうするとさっきの自分自身を参照する問題の大多数はこれで救えるんでは?と思ってやってみた。

# class hoge =
  object (self : 'a)
    val mutable obj = None
    method me_or_other : 'a =
      match obj with
      | None -> self
      | Some o -> o
  end;;
class hoge :
  object ('a) val mutable obj : 'a option method me_or_other : 'a end

でけた。 まだまだ修行が足りないことを実感した、2005 年初冬であった、マル。

% [雑談] あら〜

mod 演算子は modulo の略か……ほんと物知らねえな、わし。 まあ、語源は同じだから moduli でもかすってはいるのかな。

いや、この前のサンプル書いた時に、「modulo だっけ? moduli だっけ?」って悩んだ挙げ句、結局 moduli にしたっていう経緯があって、(あの時は即興だったんでちゃんと調べなかったから)今回ちゃんと調べてみたらそんな感じだったというお話。

英語苦手……数学も(駄目

本日のツッコミ(全2件) [ツッコミを入れる]

% sumii [「二つ目」は単に↓の書き間違いだったりしないでしょうか? class ['a] base n = object..]

% jijixi [ああ、確かにこの例だとこれで良いんですね。 ただ継承先のクラスでいろいろ多相値を持たせたいときに、eq クラスのため..]


2005-11-24 [長年日記]

% [PC] Cでも不思議な世界 - 2度目は意味が違う([鍋]鍋あり谷あり)

typedef の恐怖。よく思いつくなあ。 まあ、現実にこんなはまり方することは無いと思うけど、それを言い始めたらこの前の C++ の例だってそうだしね。

そして、演算子のオーバーロードは上記に点に起因する問題をより深刻にしているだけで、原因そのものではない。様に思う。

…は、確かにそのとおりなんだと思う。 つーか、わしも演算子オーバーロードって便利だとは思うし。 ただ、変な使い方しちゃったときのしっぺ返しが怖いから好きになれないってだけで(苦笑

ちなみに foo(foo) だとうまくいかないのは、正しいんじゃないのかな。

% cat foo.c
#include <stdio.h>
typedef int (foo)( void * );
int main(void)
{
   foo( foo );
   foo( foo );
   return 0;
}

% cc -Wall foo.c
/tmp/ccOMxG3k.o:foo.c:(.text+0x2d): undefined reference to `_foo'
/tmp/ccOMxG3k.o:foo.c:(.text+0x32): undefined reference to `_foo'
collect2: ld returned 1 exit status

多分一つ目の foo(foo) も foo という関数に識別子 foo という引数を与えて呼び出してることになってる。 当然、foo はまだ存在しないので、リンク時にエラー。 逆にどっかに foo が存在するなら、動く(と言うか踏む?)と思うけど。

typedef してるのは void* を引数にとって int を返す関数だから、変数宣言としてはそれへのポインタじゃなきゃいけない。 だから typedef を関数へのポインタにすれば…

% cat foo.c
#include <stdio.h>
typedef int (*foo)( void * );
int main(void)
{
   foo( foo );
   foo( foo );
   return 0;
}

% ./a.exe
zsh: 636 illegal hardware instruction (core dumped)  ./a.exe

うまく(?)いく。(動作確認は cygwin で行なった)

% [雑談] ブックマークコメントにコメントをつける

とは言え、わしはソーシャルブックマークサービスは使ってないのでここで。

ただのメモ『ネット右翼dasmの(´・ω・`)日記(棒読み』

いやあ、これって時間の無駄というよりむしろ otsune さんは嬉々としてやってません? 頭に来ると黙ってられない系の人をいじるの趣味にしてるでしょ、たぶん(笑

そもそもモヒカン族ってもの自体がネタなんだから、一生懸命啓蒙してまわる…なんて必要性は本来無いはずだし。 otsune さんにいじられるのがイヤな人はスルーするべきなんだと思うけど、それができない人だからこそ狙われるという話も(苦笑

ネタが一人歩きしすぎて、ネット上における otsune 像が必要以上に肥大している気がする今日この頃。 まあ、見てる分にはおもしろいんだけど。

% [PC][雑談] ++C って D のことじゃないか?

以前から気にしているものの、なかなか縁が無くてまともに触ったことのない D である。 better C++ の地位を C# に奪われてしまって、いまいち盛り上がってない気もするけど、.NET Framework がイヤでイヤでたまらないって人もいるだろうし、そういう人には良い選択肢なんじゃないかと思う。 …思うが、実際のところ C++ に比べて(あるいは C# に比べて)D ってどうなの?ってのがイマイチ把握できてなかったんで調べてみた。

D vs その他の言語

このページだけ見ると、すごく良いものに見えるなぁ。 C++ が持ってるものはほぼ全て持ってる。 多重継承が無いが、Mixin があるから全然困らない。 GC 付きだけど、明示的なメモリ制御もできる。 少なくともカタログスペックはピカイチだ。

ただまあ、わしが以前 D について調べてた頃(2001 年か 2002 年頃かな)は、仕様がころころ変わったりとかコンパイラにバグがあったりとか、いろいろトラブルが絶えない印象があったんで、結局ほとんど触らずに来たんだよな。 その辺最近はどうなんだろ。

そう言えば、最近こんな話を読んだ。

D言語のつまらなさは、それが「正しい答えすぎる」と、いうのがあるよな…。

わりと同意(笑

% [雑談]

やべっ!!つい見ちゃったよ。 どこ?どこを吸えば良いの!?

% [OCaml] 型を書くのか書かないのか

わしは mixi は使ってないので話の流れはわかんないけど、そこかしこで話題になってるみたいなんで、せっかくだから書いとこうかなと。

とりあえず、C ではヘッダから書くね。 やっつけ仕事でファイルを分割しないときでも、まずプロトタイプから書く。

そのノリでいくと、OCaml でもまず mli から……ってなりそうなんだけど、今のところそれをやったことは無いなあ。 いきなり ml からガリガリ書く。 ただ、頭の中で思ってる型と、推論される型が一致してるかどうか確かめるために、たまに ocamlc -i する。 まあ、頭の中に入れとくくらいなら、mli に書いちゃえば?ってのはあるけど、めんどくさいし。 と言うか、手が滑って間違いを突っ込んじゃうくらいなら、書かない方が安心っていう方向性。 んで、だいたいでき上がったら正式に ocamlc -i の出力を元に mli を仕上げる感じかね。 想定より緩く推論されちゃってるのを直したり。

……とか語るほど、OCaml でコード書いた経験があるわけでもないんだけど(苦笑

ちなみにわしがコード書くときは、100x30 くらいのターミナルで vim を使うのが基本。 ターミナルは 3,4 枚開いておいて、それぞれ vim を立ち上げて必要なファイルを見れるようにしてる感じ。 で、本来はターミナルのウィンドウを行き来するのにトラックポイントを使うのが最強なんだが、残念ながら現在のメイン環境である iMacG5 ではそれが使えないので、仕方なくいちいち手を離してトラックボールを触ってる。 一時期使ってたトラックパッド付きキーボードは、だんだんキータッチに我慢できなくなってきて結局やめた。

実は MacOSX の X11.app ではコマンドキー+数字でウィンドウの切り替えができるんだが、いかんせん肝心の数字がタイトルバーとかに表示されないから、直感的に思ったウィンドウを選べなくて意味が無い。 やっぱホームポジションから手を離さずに使えるポインティングデバイス重要。 ……いつの間にか話の趣旨がずれていた...orz

本日のツッコミ(全6件) [ツッコミを入れる]

Before...

% jijixi [ラジオネタかあ、残念ながら守備範囲外。 いちおーそれらしきのを見っけたから貼っとこう。 http://66.102...]

% Zapper [「ハンタマキレター」「ヌンコマレター」]

% jijixi [いや、意味わかんないし(苦笑]


2005-11-25 [長年日記]

% [雑談][ローカルネタ] スネークマンショー

Zap 師とか塵○さん(未登場キャラは一応伏字)辺りなら CD 持ってんじゃないかな……とか思った。

% [PC][雑談] 不思議な世界シリーズ(勝手に命名)

そうですか、マイクロソフトのコンパイラが変でしたか、これは失礼。 わしもあとで試してみよう(実は使ったことないんで、ダウンロードから始めないと)。

それはそれとして、C++ で二度目が意味違うシリーズ。 演算子オーバーロードの恐るべき能力を垣間見た気がする。 つーか、変数宣言で使うような記号はオーバーロードしたらダメだね。 危険すぎる。 C のネタは踏まない自信があるけど、C++ のネタは踏みそうだもの(苦笑

あと D の話は概ね同意。 GC を内蔵してるとランタイムが(たぶん)大きくて組み込みには向かないだろうとは思うし。 ただ、OS は OCaml で OS 作ってる人とかもいるくらいだし、何とかならないこともないんじゃないかなあ…と思わなくもない(微妙すぎる表現)。 当然ブートストラップは C なりアセンブラなりで書かなきゃだめだろうけど、一旦メモリ空間を自由に使えるようになってしまえば、あとは D で大丈夫じゃないかと。 まあ、カーネル作るには GC(…のメモリアロケーション部分)を多少改造しなきゃならないとは思うけど。 それさえ乗り越えてしまえば、インラインアセンブラが使える分、OCaml で作るより楽そうな気も。

とは言え、わしは OS なんて作ったこと無いんで何を言っても素人のざれごとだし、そもそも C++ で OS 作るのと比べてむしろツラいんじゃないかとか思わなくもなかったりするし、作れるかどうかはともかく、作るのに向いてないとは思う。

ああ、でもシステムコールレベルで GC を提供してくれる OS ってのがあれば、ちょっとあこがれるなあ。

% [PC] MS のコンパイラだとエラーになる話

Visual C++ Toolkit 2003 てのを落としてきて試してみた。 ソースは昨日の二つ目。 出力は適当にはしょったり整形したりしてあるよ。

・C としてコンパイル
>cl /Tc foo.c

foo.c
foo.c(5) : error C2059: syntax error : 'type'
foo.c(6) : error C2059: syntax error : 'type'

・C++ としてコンパイル
>cl /Tp foo.c

foo.c
c:\tmp\test\foo.c(6) : warning C4700: local variable
'foo' used without having been initialized

C++ の方の警告は至極真っ当だけど、C の方のエラーメッセージは意味不明だな。 とりあえず、

#include <stdio.h>
typedef int (*foo)( void * );
int main(void)
{
   foo foo;
   foo( foo );
   return 0;
}

こんな風に直すと通る。 まあ、さすがにこれが通らないと困るが。 ちなみに、ちょろちょろいじってみると…

int main(void)
{
   int( hoge );
   return 0;
}

これは通る。

typedef int hoge;
int main(void)
{
   hoge( hoge );
   return 0;
}

これはダメ。ついでに…

typedef int hoge;
int main(void)
{
   int( hoge );
   return 0;
}

これもダメ。 どうも、宣言にカッコが混じってるときのパースの仕方が、gcc と違うようだ。 ただ、この辺の厳密な定義って規格書には無かったような気がするんで、一概に MS のコンパイラがダメだとは言えない気もする。 つーか、余計なカッコがあるときにエラーになってくれる MS の方がありがたいって感じがしなくもない。

% [PC] 釣られた

2ch の ML スレで 976 が気になることを言ってた。

> print res; というコマンドは、有効なのに。

文字列を表示したいとか言ってんだから、print って string -> unit なんじゃないのか?なのにリスト与えても良いの?と思いつつ調べる。

val print : string -> unit

[The TextIO structureより引用]

どう見ても string -> unit です。 ほんとうにありがとうございました。

まさかオーバーロードはしてないよなあ…と思いながら一覧を見てもやっぱり TextIO にしか print なんて無い。 でも一応確かめないと気になって仕方ないので、わざわざ SML/NJ を落としてきて試してみた。

- print;;
val it = fn : string -> unit

- let l = ["a";"b";"c"];;
stdIn:2.1-2.8 Error: syntax error: deleting  LET ID EQUALOP
stdIn:2.13 Error: syntax error found at SEMICOLON

orz

- val l = ["A","B","C"];;
val l = ["A","B","C"] : string list

- print l;;
stdIn:4.1-4.8 Error: operator and operand don't agree [tycon mismatch]
  operator domain: string
  operand:         string list
  in expression:
    print l

やっぱダメじゃん(倒

% [雑談][PC] 想像の斜め上をいく初心者クオリティ

向井さんも釣られたらしい 976 だが、978 が核心に斬り込んだのでその話は回答待ちとして置いておく(笑

んでまあ、例の話はともかくとしてたまに思うのは、根本的にプログラミングの経験が乏しい人って、はっきり言って信じられないような質問をすることがあるよね…と。 この 976 にしてもそう。 「リストの要素を取り出す方法がわからない」って質問は予想できても、「リストの『文字列表現』を取得したい」なんて質問は予想できん。 そもそも、

val res = ["A", "B", "C"] : string list
val tmp = "result = " : string

を繋いで、

result = ["A", "B", "C"]

と表示したいって言ってるくせに、試したのが tmp ^ res ってのがもうあり得ない。 少なくとも、この手合いに噛んで含むように教えるような能力はわしには無いなあ。 と言うか、これくらいのことは基本的なドキュメントを読んで乗り越えられるくらいじゃないと、プログラミングなんてできないんじゃないかと思うんだが……

とは言え、レイヤの違いこそあれ、わしだって多分熟練者から見れば「何言ってんの、こいつ?」ってな疑問を抱いてたりする可能性は多々あるわけだし、馬鹿にできた話ではないんだろうなあ……などと自戒してみる。

% [雑談] 984 はわし

val print : 'a -> 'a だそうで。

http://wwwcgi.reading.ac.uk:8081/cgi-bin/cgiwrap/wsi14/poplog_form?system=ml&Type=help&name=print

この print 関数って Ruby の p メソッドみたいになってんのかな。 実際試してないからわからんけど。

% [雑談] 可哀想な 976

SML の解説読めば print は普通 string -> unit って書いてあるだろうけど、運悪く標準に準拠してない処理系を使ってしまったがために混乱してるってことなんだろうか。 誰だ、こんな変な処理系薦めたの(笑

まあ、便利は便利なのかも知れんが……

本日のツッコミ(全3件) [ツッコミを入れる]

% 向井 [同じく、私も釣られちゃいましたよ(笑)]

% 向井 [ちなみに 978 は私 文字列表現にする、というか表示させるための関数を用意するっていうのは、まぁわからないでもない..]

% jijixi [別の名前にすれば良いのに、なんで敢えて print って名前にするんですかね(苦笑]


2005-11-26 [長年日記]

% [雑談] サスペンス劇場のタイトル(ワラタ2ッキ)

メロンパンは一体どこから!? なんか新聞のテレビ欄で見かけて吹いた記憶があるが。

このシュールなセンス……あなどれない。

% [Mac] kinput2.macim と VJE その後

いつぞや試したときには微妙に不具合があったから結局使わないようにしてたんだが、fink の kinput2.macim がバージョンアップしてから試してないなーと思って試してみてるところ。

5/28 版のパッチが取り込まれてる時点で、なんとなくうまい方向に進んでる気はしてたんだが、試すのすっかり忘れてたよ。 つーか、もうすでにことえりに慣れすぎてて乗替欲が出ねえ(苦笑

ともあれ、前回試したときのような変換ウィンドウの不具合は一応無くなってる模様。 多少ウィンドウの表示にもたつきがある気がするけど、許容範囲内だな。

なんか良い感じに使えるようにはなってるみたいだけど、さて結局どうすっかなあ。 基本的には VJE の方が好みだけど、今のことえりの候補予測機能とか同音異義語の意味を表示する機能とかって結構便利なんだよな。 先の無い VJE を頑固に使い続けるより、とっととことえりに慣れてしまえって気持ちはあって、実際実用上それほど問題を感じないほどにはことえりも賢くなってるんだけど、やっぱ感情的には VJE を使いたい気持ちは大きく、でもよく考えるとしばらく VJE は使ってなくて辞書も全然学習されてないし(かつ学習していた辞書はとっくに紛失してたりするし)、おとなしくすでに育った辞書があることえりを使った方が良いんちゃうの?と語りかける脳内人格がいるようないないような、そんな状況。 わけわかんね。

ま、適当にやるわ。 ああ、そう言えば全角入力で Shift キーを使った入力ができないのは変わってないな。 ただ直接入力にすれば入力可能なんで、一応回避可能なものではある。


2005-11-27 [長年日記]

% [Mac] さようなら VJE

いろいろいじってたら環境設定パネルが開かなくなった(厳密には開いた直後に死ぬようになった)。 設定ファイルの類いを全部捨ててみても直らない。 まあ、Jaguar の時代に出たソフトで、しかも今まで一度もパッチが出てない代物だしなあ。(いや、パッチは出てたか。でも少なくとも Tiger になってからは出てないはず。) もちろん今後パッチが出る可能性もまず無い。

再インストールすれば直るかもしれないが、また何かの拍子に使えなくなるんじゃかったるい。 多少の残念はあるが、ここはすっぱりあきらめてことえりを使うことにしよう。 さようなら、今までありがとう(いや、最近使ってなかったけどもな)。

ちなみに Windows ではまだ使ってるが、これもいずれ MS-IME に移行する運命なんだろうなあ。 別に skkime でも良いけどさ。


2005-11-28 [長年日記]

% [雑談] 白いガラナ

セブンイレブンに行ったら売ってたんで、とりあえず保護。 さらっとググっただけだと公式情報は見当たんなかったんで、今日初めて目にしたニュースサイトの記事でも貼っとく。

白いつってもアンバサみたいのじゃなくて、実際は無色透明。 味はぶっちゃけ『薄いガラナ』だな。 大した振ってもいないのに、開けたら噴きやがったのが印象悪い(苦笑


2005-11-29 [長年日記]

% [PC] 関数型言語への恐怖心は薄れたことだし、そろそろ Lisp 系に手を出しても良いのではないか

…などと思った。 まあ、どうしたって Lisp 系言語のコードを読むと「うはwww、なにこのカッコwww、テラキモスwww」とか、思わず VIPPER 化したコメントが出てきてしまうわけだが、そこはぐっとこらえてみたい。

んでまあ、Lisp 系と言っても多々あるわけで、どれにしようかなあ…と考えて「やっぱ日本人なら Gauche じゃよね?」ってことで Mac に fink で Gauche を入れてみた。 なんかビルドに失敗したんで、クイックハックとかしつつインストール完了。 バージョンがちょっと古い(0.8.3)が、とりあえず本格的に使うわけじゃないから良いだろ。 ちなみにパッチはこんなん。

--- Gauche-0.8.3.orig/src/compile.c.orig	2005-11-29 07:30:05.000000000 +0900
+++ Gauche-0.8.3/src/compile.c	2005-11-29 07:30:25.000000000 +0900
@@ -67,6 +67,7 @@
 static ScmObj compile_lambda_family(ScmObj form, ScmObj args, ScmObj body,
                                     ScmObj env, int ctx);
 static ScmObj compile_body(ScmObj form, ScmObj env, int ctx);
+static ScmObj compile_named_let_body(ScmObj, ScmObj, int);
 
 #define LIST1_P(obj) ?
     (SCM_PAIRP(obj) && SCM_NULLP(SCM_CDR(obj)))
@@ -1518,7 +1519,6 @@
                                   env, ctx);
     } else {
         /* Named let. */
-        static ScmObj compile_named_let_body(ScmObj, ScmObj, int);
         /* TODO: this is broken if lambda is locally bound! */
         ScmObj proc = Scm_Cons(id_lambda, Scm_Cons(vars, body));
         return compile_let_family(form, SCM_LIST1(name), SCM_LIST1(proc),

これを /sw/fink/dists/unstable/main/finkinfo/languages/gauche.patch の中に追加してやれば良い。 何で関数のプロトタイプがこんなところにあるのかわからんけど、C99 とかだと許されてるんだっけ? 変数宣言は関数の途中でできるようになったはずだけど……

ともあれ、いじる。 ……って、よく考えたら、わし Scheme の基本的な文法すらよく知らないや。ダメじゃん。 適当に入門文書見つけてこないと。 千里の道も一歩からだよね?(苦し紛れ)

ところで ledit 大好き人間用の tips を一つ。 gosh に ledit を使うときは、

% ledit gosh -i

と、-i オプションを付けるが吉。つーか、付けないとダメだったから。

% [Scheme] どうでも良いけど…

いや、どうでも良くはない気もするけど、#t と #f ってパッと見区別付きづらくない? シンタックスハイライトで別の色付けてくれりゃ良いんだけど、vim ではそうはなってないなあ。

% [雑談] いちいち区別を考えるのがめんどくさいので……

カテゴリは思いつきでどんどん増やしても良いことにした。

% [Scheme][vim] Emacs 使えない人は ledit 必須だよね(あと Vim も)

素の gosh なんて使ってられないもの。 ledit があれば閉じカッコを入力したとき対応したカッコを表示してくれるから(vi で言うところの showmatch)安心。

まあ、もう少しまとまったコードを書くようになれば、vim で書きつつ適当に…

:!gosh %

とかすると思うけど。 Ruby とか OCaml(で遊ぶ時)もそんな感じだし。

% [Scheme] とりあえず、ごく基本的なことは…

OCaml に慣れた今となっては特にややこしいことは無いな。 末尾再帰だって怖くなんかないさ。 マクロも、『健全な』方のなら何とか理解できるみたいだ。 カッコ地獄は諦めるしか無いし(笑

そんなわけで、あとは『継続』が肝であるわけだ。 って言うか、こいつのためにわざわざ Scheme を勉強し始めたんだから、まあゆっくり焦らずやろう。 とりあえず、前に『なんでも継続』を読んだ時には、そもそも Scheme の知識が無さ過ぎて意味がよくわからなかったから(だってサンプルコード見ても何やってるかわからないんだもんな)、その辺りを何とかしてみたいところ。

% [Scheme] 継続わかんねえ

まあ、一つわかった。

継続は習うより慣れろ

[Scheme 入門 A-2. 継続についてもう少しより引用]

モナドもどっかで同じようなこと言われてたな。 ともあれ、継続の場合は setjmp の賢いやつ…みたいな感じっぽいので、モナドよりはまだ理解のための取っ掛かりがある気がする。 のんびりやろう。

本日のツッコミ(全5件) [ツッコミを入れる]

Before...

% jijixi [図らずもみなさんのお役に立っているようで何よりです。 print の話は、ocaml コマンドで表示されるみたいな汎..]

% Zapper [jijixi師匠、日曜カレー喰いに逝くデスよ?]

% jijixi [だから師匠はやめれと(笑 カレーは了解。]


2005-11-30 [長年日記]

% [本日のリンク元] .fr の Google 検索 (jijixi)

誰だ、おフランスの google でわしの名前検索してんのわ(笑

(追記)…とか書いたら、わざわざ uk とか at の google で検索してくれた人がおるぞ。 暇だなあ(笑

% [PC][雑談] Perlは書き殴りたいときに使うのか(J)

なんかすごく納得する話(笑

気分が乗ってさえいれば、「関数型Objective-elsifだらけのバッチ処理。でも中身は全部正規表現でやってますっ」と、いったコードだって、書けてしまうわけだ。

に激しく笑う。

% [Scheme] ちょっと惚れそうな気配

ヤバ、Gauche にハマりそうな気がしてきた。 わしが好んでいる Ruby の機能って…

  • 素で日本語が使える(あと文字コード変換もできる)
  • 正規表現のリテラル表記がある
  • case..when で正規表現が使える
  • クロージャ

ってな辺りだと思うんだが……全部あるよ。 むしろ Proc#call が嫌でたまらないってことを考えると、Gauche の方が好みだ。 逆に Gauche の(と言うか Scheme の)嫌なところは…

  • カッコ多すぎ
  • C 方面から入った人間には文法が特殊

てなところか。 でもこの辺は純粋に慣れで済む気もする。 まあ、カッコはかなり精神的にキツイ部分もあるんだが(苦笑)、文法は最近じゃむしろこっちの方がしっくり来てたり(演算子まで前置記法なのはキモいけど)。 たまに Ruby で、

def hoge x y
   x + y
end

とか、

hoge 1 2

とか書いて怒られたりする始末だから。 識別子がやたら冗長なのが微妙な気はするけど、vim でキーワード補完使ってればあんまり気になることも無い(むしろ変な略称より気が利いてる)。

日々のちょっとしたことをするための言語としては Ruby 以上のものは無いと思ってたが、Gauche はなかなかあなどれない存在のようだ。 あとは gosh に irb くらいの機能(キーワード補完とか)があればなあ……

% [FreeBSD][vim] もうシラネ

Synopsis: editors/vim issue: report invalid size of multi-byte character

State-Changed-From-To: open->closed
State-Changed-By: flz
State-Changed-When: Wed Nov 30 12:32:56 GMT 2005
State-Changed-Why: 
Feedback timeout.  Don't hesitate to re-open the PR if vim 6.4 didn't
solve your problem.

かったるいから無視するよ。 いまだに、『freebsd vim mblen』とか『vim 文字化け』とかで検索リファラ付いてきてるけどな。 一応、対策を書いとくか。

% cat /usr/ports/editors/vim/files/patch-mbyte_c
--- mbyte.c.orig        Sun Nov 28 20:40:35 2004
+++ mbyte.c     Sun Nov 28 20:42:49 2004
@@ -650,6 +650,7 @@
                     * where mblen() returns 0 for invalid character.
                     * Therefore, following condition includes 0.
                     */
+                   mblen(NULL, 0);
                    if (mblen(buf, (size_t)1) <= 0)
                        n = 2;
                    else

こいつを突っ込んどく。以上。

% [Scheme] 継続の基本的なところはだいたいわかった(気がする)

とりあえず、ここのページに書いてある程度のことは大丈夫。たぶん。

一つ目の例の…

(define cont #f)
(+ (* 1 2)
   (call/cc
     (lambda (c) (set! cont c) (* 3 4))))

こういうのは、OCaml で途中まで適用した関数を作るようなもんだ。 cont には call/cc を呼んだ時点での計算の経過が入る。 関数として書くならこんな感じ?

(lambda (x) (+ (* 1 2) x))

感覚的には、OCaml で

let cont = (+) (1 * 2)

こう書くようなもんか。 ただまあ、こんな使い方はあんまりしないような気がする。わからんけど。

んで、二つ目の例はいろんなところで例に挙がる大域脱出。 件のページにも書いてあるとおり、他の言語で言うところの例外処理とイメージはそっくり。 要するに、OCaml で書けば…

exception E of int
let temp =
   1 * 2 +
      try
         if true then raise (E 0)
         else 1
      with E x -> x

こんな感じの時(例は非常に無理矢理だが)、try 節が call/cc に渡される関数の部分で、raise を呼んでるところが、call/cc によって取り出された継続を適用するところ。

これで終わりなら簡単だけど、問題はむしろこの先だよなぁ。 これだけじゃ、はっきり言って何が嬉しいのかさっぱりだし。 継続を使った軽量スレッドとか、言葉だけ聞いてもイマイチピンと来ない。


トップ 最新 追記

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com