mutable変数はクロージャに閉包できない

クロージャでかっこよく*1フィボナッチするよ!

let makeFiboClosure (a, b) =
    let mutable t = (a, b)
    fun () -> let x,y = t in t <- (y, x+y); x

makeFiboClosure は、クロージャを作る関数です。評価するとレキシカル変数なタプル t を更新しながら数列を生成する無引数のクージャを返します。
コンパイルしてみたら...

error FS0407: The mutable variable 't' is used in an invalid way.
Mutable variables cannot be captured by closures.
Consider eliminating this use of mutation or using a heap-allocated
mutable reference cell via 'ref' and '!'.

ダメでした。
「mutable 変数は クロージャでキャプチャできない。ref でヒープにアロケートして ! で参照してね」
なんて親切なエラーメッセージなんだ。

というわけで。完成。

let repeatedly f = seq {while true do yield f ()}

let makeFiboClosure (a, b) =
    let t = ref (a, b)
    fun () -> let x,y = !t in t := (y, x+y); x

let fibo = makeFiboClosure >> repeatedly
(1, 1) |> fibo |> Seq.take 10 |> Seq.toList
;; => [1; 1; 2; 3; 5; 8; 13; 21; 34; 55]

関数型プログラミングっぽいね!*2

*1:別にかっこよくはない

*2:いや副作用とかあるし

Mono用 fsharp-mode 補助スクリプト

F#アセンブリを任意のパスに配置して開発できるように補助的な elisp を書きました。

A Helper of fsharp-mode (v0.3) for Mono

中途半端な感じですが、F# 学習用には使えるんじゃないかと思います。
やってることは

  • 環境変数 MONO_PATH の設定と、実行プログラムへの反映
  • コンパイル時に MONO_PATH にある dll を全て参照

という処理です。すべてコマンドラインを編集することで対応させています。
C-c x のキーバインドを上書きして、設定した環境情報のもとにプログラムを実行するようにしています。*1
一応、拡張子 fsx のスクリプトファイルも実行できるように作ってありますが文法エラー等のメッセージは Shell Output に垂れ流しです。C-x ` でエラー箇所へジャンプする事はできません。

需要があるのか謎な感じですが晒してみます。ご利用の方はパス設定など自分の環境にあわせてください。
動作確認環境は
Mono

Mono JIT compiler version 2.10.5 (Debian 2.10.5-1~dhx1~lucid1)

F#

Microsoft (R) F# 2.0 Compiler build (private)

です。

*1:.fs ファイルは C-c C-c で事前にコンパイルして exe にしておく必要があります。

UbuntuとEmacsでF#

意外と簡単に環境つくれました。Ubuntu は 10.04 LTS です。

♯Monoインストール

Ubuntu の apt で入れました。というか入れてありました。version は 2.10.5 なので F# 入れるには十分新しいようです。

♯make ツール

autoreconf をしたら libtoolize が無いと怒られたので

$ sudo aptitude install libtool

で入れました。automake autoconf も必要なのでなければ入れます。もちろん make も必要です。

♯F# 本体の make と install

$ cd ~/download
$ git clone git://github.com/fsharp/fsharp
$ cd fsharp
$ autoreconf
$ ./configure --prefix=$HOME/local/mono
$ make
$ make install

prefix はインストールパス指定。私はシステムを汚したくないので $HOME/local に色々入れてます。好みで適当な場所指定しとけばよいです。prefix を省略すると /usr/local にインストールされるようです。
make には結構時間がかかります。

環境変数設定

このままでは mono が F# のアセンブリを認識しないので環境変数を設定します。*1
~/.bashrc に、

export MONO_PATH=$HOME/local/mono/lib/mono/4.0

と書いて、一旦ログアウト & 再ログインで F# アセンブリが利用可能になります。

♯ここまでの確認

terminal で、interactive を動かしてみます。

~$ ~/local/mono/bin/fsharpi

Microsoft (R) F# 2.0 Interactive build (private)
Copyright (c) 2002-2010 Microsoft Corporation. All Rights Reserved.

For help type #help;;

> open System;;
> Console.WriteLine "Hello, F#";;
Hello, F#
val it : unit = ()
> #q;;
- Exit... 

mono の F# では、インストールディレクトリ直下の bin の中に 4つの実行ファイルがあります。

$ cd ~/local/mono
$ find ./bin | sort
./bin
./bin/fsharpc
./bin/fsharpc2
./bin/fsharpi
./bin/fsharpi2

fsharpi が interactive で、 fsharpc がコンパイラ。 2 がついてるのはよく分りませんが version が微妙に違うようです。ここでは無印の方を使うことにします。
コンパイルもしてみましょう。

$ cd ~/workspace
$ echo 'System.Console.WriteLine "Hello, F#"' > hello.fs
$ cat hello.fs
System.Console.WriteLine "Hello, F#"
$ ~/local/mono/bin/fsharpc hello.fs 
Microsoft (R) F# 2.0 Compiler build (private)
Copyright (c) 2002-2010 Microsoft Corporation. All Rights Reserved.
$ ls
hello.exe  hello.fs
$ ./hello.exe 
Hello, F#

環境変数 MONO_PATH が設定されていないと、./hello.exe 実行時に例外がスローされます。

$ ./hello.exe 
Missing method .ctor in assembly /home/USERNAME/workspace/fsharp/hello.exe, type Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute
Can't find custom attr constructor image: /home/USERNAME/workspace/fsharp/hello.exe mtoken: 0x0a000001

Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=4.0.0.0, ... (以下略)

Emacs設定

fsharp-mode を入れます

cd ~/download
git clone https://github.com/emacsmirror/fsharp-mode.git
cp -r fsharp-mode ~/.emacs.d/site-lisp/fsharp-mode

~/.emacs.d/init.el 等に例えばこんな感じで設定を書きます。*2

(setenv "MONO_PATH" (expand-file-name "~/local/mono/lib/mono/4.0"))
(add-to-list 'load-path "~/.emacs.d/site-lisp/fsharp-mode")
(add-to-list 'auto-mode-alist '("\\.fs[iylx]?$" . fsharp-mode))
(autoload 'fsharp-mode "fsharp" "Major mode for editing F# code." t)
(autoload 'run-fsharp "inf-fsharp" "Run an inferior F# process." t)

(let* ((fsharp-bin "~/local/mono/bin/")
       (fsi-command (concat fsharp-bin "fsharpi --readline-"))
       (fsc-command (concat fsharp-bin "fsharpc")))
  (setq inferior-fsharp-program fsi-command)
  (setq fsharp-compiler fsc-command))

Emacsでの開発

拡張子 .fs のファイルを開くと fsharp-mode になります。
fsharp-mode で C-c C-s とすると、inferior-fsharp-program がミニバッファに表示され、Enter で interactive が起動します。
ソースコードのバッファで C-c C-r がリージョンの評価、 C-c C-e は fsharp-eval-phrase だそうですが、いまいち評価される範囲が分らない。
便利なのは、C-c C-c でコンパイルして、 C-c x でコンパイルした実行ファイルを実行とか。コンパイルエラーがあれば、 C-x ` でエラー行へ飛びます。

♯その他ライブラリのインストール

ちょっと乱暴だけど、とりあえず MONO_PATH に設定したディレクトリに dll を放り込めばライブラリも使えるようです。PowerPack も FParsec もそれでいけました。
FParsec は Windows でビルドしたアセンブリをコピーしてきたもので動作確認とれました。
やっぱこれじゃダメかも。あとで確認します。

♯確認した

外部アセンブリを認識させるには elisp を書く必要がありそうです。別エントリで晒してます。
Mono用 fsharp-mode 補助スクリプト

*1:もしかしたら ./configure でインストール先(prefix) がデフォルトのままならば MONO_PATH 設定は不要かもしれませんが未確認です。

*2:fsharp-mode の README に書いてあるものを若干アレンジしてあります。

F# で ProjectEuler18

List メインで解こうと決めてやってみたけど無理してるのが見え見え。Seq にしてやった方がよかったかも?
筋のいいプログラマーなら文字列からのパースをもっと安全なコードにするんでしょうかね。

Project Euler 18 解答 (gist)

リファクタリング(2011/11/26)

あれから多少 F# について学んだので、気になるところをリファクタリングしてみた。
殆ど別物になってますが、アルゴリズムは同じです。

Project Euler 18 解答その2 (gist)

関数の型註釈

関数定義の時に型を明示しなくても推論で型を決めてくれるけど、明示したいこともある。
例えば、** 演算子は float 型専用なので他の型では使えない。

let pow x y =
    x ** y
pow "2" "10"
//=> error FS0001: This expression was expected to have type float but here has type string

そこで型註釈が必要なのだが。

let pow (x:string) (y:string) =
    (float x) ** (float y)
    |> string
pow "2" "10"   //=> "1024"

期待通りには動くが、pow の定義の型註釈がとても見にくい。
引数ひとつひとつに註釈するのではなく、関数全体の型をまとめてかければその方が見やすかろう。その方法もある。
F# では変数の型註釈を

let x : int = 42

という風に識別子と値の間に書くことができる。
そして関数もファーストクラスであり、無名関数を識別子に束縛することで関数定義ができるわけで、要するに変数と同じように関数も型註釈ができる。

let pow : string -> string -> string =
    fun x y ->
        (float x) ** (float y)
        |> string

ちょっと haskell に似てる。まず関数の型を記述してその後に定義を記述する。
この記法なら型註釈も億劫じゃない。
ただ、一つ前に書いた「引数に個別に型註釈を付けたコード」と比べると「fun x y ->」が入る分、インデントが1つ深くなってしまう。
それが嫌なら一応これでもいける。

let pow : string -> string -> string =
fun x y ->
    (float x) ** (float y)
    |> string

let と fun は同じインデントレベルになってもいいみたい。流石にこれは抵抗あるかな? 慣れりゃなんてことないと思うけど...

ちょっと変えた

本文のコード int だと話がややこしくなるので、string にしました。

最後のはよくないみたい

上の最後のコードはコンパイラがワニングだしてました。

warning FS0058: Possible incorrect indentation: this token is offside of context started at position (18:1). Try indenting this token further or using standard formatting conventions.

エラーではなく警告なのでコンパイルはされますが、よくないみたいです。

習作:数字混じり文字列ソートをF#で

まだ文法とか半分くらいしか学んでないけど、リスト操作を憶えたので今の知識でどんな風になるか腕試し。
元ネタはどう書く?.org 「数字混じり文字列ソート」

open System

let takeNums cs =
    let rec loop cs n =
        match cs with
        | [] -> (n, [])
        | c::cs when Char.IsDigit c -> loop cs (string c |> Int32.Parse |> ((+) (n*10)))
        | c::cs -> (n, c::cs)
    loop cs 0

let takeChars cs =
    let rec loop cs s =
        match cs with
        | [] -> (s, [])
        | c::cs when Char.IsDigit c -> (s, c::cs)
        | c::cs -> loop cs (s + (string c))
    loop cs ""

let parse (s:string) =
    let rec loop cs list =
        match cs with
        | [] -> list
        | _ ->  let s, cs = takeChars cs
                let n, cs = takeNums cs
                loop cs (List.append list [(s, n)])
    loop (s.ToCharArray() |> Array.toList) [] 

//let mixedSort = List.sortWith (fun a b -> compare (parse a) (parse b))

let mixedSort = List.sortBy parse

多分冗長なんだろうね、これじゃ。
書いた後で気付いたけど、末尾再帰にしないほうがシンプルになったかも?

追記

List モジュールに take とか drop とか無くて不便だなぁーと思いながら書いてたけど、
どうやら Seq モジュールにそういった関数があるみたいだ。

さらに追記

sortBy 使うべきでしたね。コード訂正。

not-empty には ? が付かない

理由は単純。評価結果が 真/偽じゃないから。

(empty? [])    ;=> true
(empty? [1 2]) ;=> false

(not-empty [])    ;=> nil
(not-empty [1 2]) ;=> [1 2]

not-empty の実装を見ると

(defn not-empty
  "If coll is empty, returns nil, else coll"
  {:added "1.0"}
  [coll] (when (seq coll) coll))

さらに、seq の実装をみると

(def
 ^{:arglists '([coll])
   :doc "Returns a seq on the collection. If the collection is
    empty, returns nil.  (seq nil) returns nil. seq also works on
    Strings, native Java arrays (of reference types) and any objects
    that implement Iterable."
   :tag clojure.lang.ISeq
   :added "1.0"}
 seq (fn seq [coll] (. clojure.lang.RT (seq coll))))

説明がズラズラと書かれてますが、ここでのポイントは「コレクションが空の時 nil を返す」ってところ。not-empty はこの seq の特性と、nil が 偽 と判定される仕様を使って実装されているわけですね。*1

ところで、何故

(defn not-empty
  [coll] (seq coll))

じゃなく

(defn not-empty
  [coll] (when (seq coll) coll))

なんだろう?
違いは、Persistent なコレクションを渡したとき返却値が、引数そのものか seq かという点。
not-empty に関して言うならばどちらでもよさそうな気がするけど、わざわざ collection で返さなければならない理由があるんだろうか?

#無限シーケンスで使うとき注意

empty? は無限シーケンスでも特に問題ないけど

(empty? (range)) ;=> false


not-empty は空でないとき引数をそのまま返すので注意が必要

(not-empty (range)) ;=> 無限シーケンス

うっかり正格評価すると帰ってきません。

#やっぱり not-empty? も欲しいかも(?)

簡単に作れるけど ....

(defn not-empty?
  [coll] (not (empty? coll)))

(not-empty? [])      ;=> false
(not-empty? [1 2])   ;=> true
(not-empty? (range)) ;=> true

うーん、これだったら作るまでもないか。

*1:Pythonista は注意が必要。空っぽのコレクションは clojure では偽にならない。また、Lisper も注意 (= '() nil) => false です。