mutable変数はクロージャに閉包できない
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
Mono用 fsharp-mode 補助スクリプト
F#アセンブリを任意のパスに配置して開発できるように補助的な elisp を書きました。
A Helper of fsharp-mode (v0.3) for Mono
中途半端な感じですが、F# 学習用には使えるんじゃないかと思います。
やってることは
という処理です。すべてコマンドラインを編集することで対応させています。
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)
です。
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 補助スクリプト
F# で ProjectEuler18
関数の型註釈
関数定義の時に型を明示しなくても推論で型を決めてくれるけど、明示したいこともある。
例えば、** 演算子は 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 にしました。
習作:数字混じり文字列ソートを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
うーん、これだったら作るまでもないか。