F#のWebアプリケーションフレームワーク
http://fsharp.org/guides/web/ に載ってる中でちょっと気になったやつをメモ。
Suave.IOはノンブロッキングでクロスプラットフォーム。ほうほう。結構作りこまれてる様子。 http://suave.io/
type SuaveTask<'a> = Async<'a option> type WebPart = HttpContext -> SuaveTask<HttpContext>
open Suave open Suave.Http open Suave.Http.Applicatives open Suave.Http.Successful open Suave.Web let app = choose [ GET >>= choose [ path "/hello" >>= OK "Hello GET" path "/goodbye" >>= OK "Good bye GET" ] POST >>= choose [ path "/hello" >>= OK "Hello POST" path "/goodbye" >>= OK "Good bye POST" ] ] startWebServer defaultConfig app
Freyaはステートマシン。シンプルっぽい。 https://github.com/freya-fs/freya
type Freya<'a> = FreyaState -> Async<'a * FreyaState>
let double x = freya { return x * 2 }
演算子のオーバーロード #5(完結編)
ちょっと間が空いた。
さて、ここまでを踏まえれば、最初の記事に載せた
はほぼ理解できる。
- 組み込みの
float
とユーザー定義のvector2
の両方で使えるように、2項演算子*=
をオーバーロードしたい - しかし、
float
同士の*=
演算を、後付けで定義することができない - そこで、以下のハックを使う
- ユーザー定義型
Overloads
に、3項演算子?<-
を定義する
- ユーザー定義型
- 以上の準備ができたなら、グローバルレベルでのインライン演算子定義で、
*=
を実装し、Overloads
型の?<-
演算子にぶん投げる
なんとまあ。分かってしまえば何ということはなかった。?<-
を使っているのは単にそれが3項演算子だからってだけのようだし、Overloads
型が値が1つしかない判別共用体であることにも大きな意味はなかった。
演算子のオーバーロード #4
グローバルレベルで定義された演算子が演算子のオーバーロードを隠してしまう。それはわかった。それならそれで、グローバルレベルで定義された演算子は多相(ジェネリック)にできないのだろうか。
結論から言うとできる。ただし気を付けなければならないことがある。
まず、関数の場合は山かっこ(<
および>
)を関数名の直後に置ける。
let addCount<'a when 'a :> System.Collections.ICollection> (x: 'a) (y: 'a) = x.Count + y.Count
この書き方は演算子ではできない。代わりにこのように書く。
// 演算子の場合はこう書くしかない let (+) (x: 'a) (y: 'a): int when 'a :> System.Collections.ICollection = x.Count + y.Count // なお関数なら両方の書き方が許される let addCount (x: 'a) (y: 'a): int when 'a :> System.Collections.ICollection = x.Count + y.Count
こうすればジェネリックな演算子ができるのだが、F#のジェネリクスは.NETのジェネリクスを基盤にしているから、F#の型制約も基本的にはその範囲でしか書けない。つまり、(new制約や構造体制約は別とすれば)指定したクラスやインターフェースを継承している場合に、その型のインスタンスメソッドが呼び出せる、という程度のことしかできない。オーバーロードされた演算子はスタティックメンバー扱いだからジェネリックなメソッドからは呼び出せない。したがって、グローバルレベルで定義されたジェネリックな演算子の実装から呼び出すこともできない。C#と一緒である*1。
ただ、F#の強力なところは、インライン関数を使うことでその制限を超えることができることだ。インライン関数はコンパイル時に静的に型が解決されるため、明示的なメンバーの制約をかけることで、型階層によらず、また静的メンバーとインスタンスメンバーを問わず、あるメンバーを持っている型が使われることを要請できる。
VSで書くといまいちわかりづらいのでF# Intaractiveを使う。
> let inline (%*) x y = x * y;; val inline ( %* ) : x: ^a -> y: ^b -> ^c when ( ^a or ^b) : (static member ( * ) : ^a * ^b -> ^c)
%
演算子の型が、通常のジェネリック型引数で使われる 'a
や'b
ではなく、静的に解決される型を表す ^a
や^b
になっている。%
演算子を使う際には、左項(x
)または右項(y
)のどちらかの型に対して*
演算子のオーバーロードが定義されており、もう一方の項の型もその定義に適合するのであれば、どんな型であってもこの新しい演算子を適用できる。
もちろん、この演算子はコンパイル時にインライン展開(β変換)されるので、通常のジェネリックな型に適用することはできない。ジェネリックな型は実行時に解決されるものだからである。
(続く)
演算子のオーバーロード #3
さて、?<-
はいったん置いておいて、演算子のオーバーロード解決を勉強した。
F# の演算子定義は2通りのパターンがある。
1つは(C#と同様に)クラスなどのスタティックメンバーとして書くもの。
type MyRecord = { x: int; y: int } with static member (+) (left: MyRecord, right: MyRecord) = { x = left.x + right.x; y = left.y + right.y }
メソッドとして書くので、引数はタプルで受け取る。しかし実際には中置演算子として使える。
もう1つは、グローバルレベルで定義するもの。
let (+) (left: MyRecord) (right: MyRecord) = { x = left.x + right.x; y = left.y + right.y }
こちらはF#の関数として書くので、引数はタプルにしない。こちらも中置演算子として使える。
これらの違いは何かというと、演算子がオーバーロード(多重定義)されるかどうかが違う。
グローバルレベルで定義された演算子は、演算子のオーバーロードを隠してしまう。
let (+) (left: MyRecord) (right: MyRecord) = { x = left.x + right.x; y = left.y + right.y } let a = 1 + 2 // コンパイルエラー。MyRecord型の引数が期待されるところにint型の引数を渡している。
演算子のオーバーロードが期待通りに動作するためには、演算子はグローバルレベルで定義するのではなく、引数の型のスタティックメンバーとして定義しておかなければならない。
演算子のオーバーロード解決は、まずグローバルレベルの定義を探し、それが見つからなかった場合だけ、引数の型のスタティックメンバーから適切な演算子定義を探す、という順番になっているからだ。
(続く)
演算子のオーバーロード #2
F#の ?<-
演算子(動的プロパティに値を設定する演算子)はわりとキモいことがわかってきた。
まずはこんな感じでグローバルな演算子を定義してみる。
let (?<-) (d:#IDictionary<'K, 'V>) k v = d.[k] <- v
まず、キーとして適当な判別共用体を食わせてみる。
type MyUnion = A | B | C of int let testUnion = let d = new Dictionary<MyUnion, int>() let k = C 12 d.[k] <- 1 d?(k) <- 2 d?(C 12) <- 3 printfn "%A" (d.[k])
出力は想像通り、 3
になる。
まあそれはいい。
次に、キーとして文字列を食わせてみる。
let testString = let d = new Dictionary<string, int>() let k = "foo" d.[k] <- 1 d?(k) <- 2 d?foo <- 3 printfn "%A" (d.[k])
エラーは出ず、出力は 3
になる。
d?foo
は、C#で言うところのdynamic
型みたいな感じで、文字列として書いてなくても文字列としてコンパイルされるということのようだ。キモいけど、なんとかわかった。
(続く)
演算子のオーバーロード #1
Minority's hello, worldにF#で挑戦
CodeIQにMinority's hello, worldという問題が出ていました。
問題が見れなくなっているので引用すると、こういうもの。
■問題の概要
「hello, world」と出力するプログラムを提出して下さい。 提出されたプログラムで使われている文字を、全挑戦者について集計します。 各文字には「(その文字を利用した挑戦者の人数)の2乗」というポイントが割り当てられます。 提出されたプログラムのポイントは、そのプログラムで使われている文字のポイントの合計となります。 提出コードのポイントが少ない順に順位をつけます。最小ポイントの方が優勝です。
出題者の鍋谷さんによる結果発表と解説がこちら。
http://nabetani.sakura.ne.jp/codeiq/hwbattle2/
で、私はF#で適当に(あまり考えずに)下のようなコードをぶっこんで
System.Console.Write("hello, world")
結果、スコアは29,198点、順位は58位となり、当然上位には入れませんでした。
で、ここからがコードゴルフ。
じゃあ、どういうコードだったらもっとスコアが良かった(低くなった)か?ということで再考したのがこちら。
printf<|"%s"<|"hello,\u0020world"
これだと何点取れてたかを計算し直したコードがこちら。
let addCount (xs: (char*int) seq) (ys: (char*int) seq) = Seq.append xs ys |> Seq.groupBy fst |> Seq.map (fun (c,zs) -> c,(Seq.sumBy snd zs)) let calc (xs: (char*int) seq) (ys: char seq) = ys |> Seq.map (fun c -> Seq.find (fun (c',_) -> c=c') xs) |> Seq.sumBy (fun (_,v) -> v*v) [<EntryPoint>] let main argv = let othersCount = [(' ', 42); ('!', 9); ('"', 24); ('#', 10); ('$', 12); ('%', 10); ('&', 10); ('\'', 19); ('(', 28); (')', 26); ('*', 6); ('+', 8); (',', 36); ('-', 17); ('.', 24); ('/', 7); ('0', 17); ('1', 11); ('2', 15); ('3', 9); ('4', 18); ('5', 14); ('6', 11); ('7', 8); ('8', 12); ('9', 8); (':', 11); (';', 19); ('<', 20); ('=', 16); ('>', 15); ('?', 11); ('@', 4); ('A', 5); ('B', 4); ('C', 11); ('D', 13); ('E', 16); ('F', 9); ('G', 4); ('H', 16); ('I', 5); ('J', 5); ('K', 5); ('L', 16); ('M', 6); ('N', 5); ('O', 16); ('P', 4); ('Q', 7); ('R', 14); ('S', 5); ('T', 8); ('U', 4); ('V', 5); ('W', 13); ('X', 3); ('Y', 2); ('Z', 4); ('[', 11); ('\\', 14); (']', 13); ('^', 10); ('_', 8); ('`', 4); ('a', 20); ('b', 9); ('c', 26); ('d', 30); ('e', 33); ('f', 9); ('g', 6); ('h', 31); ('i', 21); ('j', 9); ('k', 9); ('l', 26); ('m', 8); ('n', 19); ('o', 33); ('p', 26); ('q', 8); ('r', 37); ('s', 23); ('t', 28); ('u', 17); ('v', 8); ('w', 22); ('x', 9); ('y', 12); ('z', 4); ('{', 10); ('|', 7); ('}', 12); ('~', 9)] //let myCode = "System.Console.Write(\"hello, world\")" let myCode = "printf<|\"%s\"<|\"hello,\\u0020world\"" let myCount = myCode |> Seq.distinct |> Seq.countBy id let count = addCount othersCount myCount printfn "%s" myCode printfn "%d" (calc count myCode) 0
実行結果は、20,964点。もしかしたら50位以内には入れたかもしれないけど、他の人のスコアも変動するので、実際はどうでしょうね。(計算すれば出ると思いますが、面倒なのでコードに落としていません)