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(完結編)

ちょっと間が空いた。

さて、ここまでを踏まえれば、最初の記事に載せた

http://stackoverflow.com/questions/11150947/f-overloaded-operater-with-floats-not-working-with-other-floats/11152288#11152288

はほぼ理解できる。

  • 組み込みのfloatとユーザー定義のvector2の両方で使えるように、2項演算子 *=オーバーロードしたい
  • しかし、float同士の*=演算を、後付けで定義することができない
  • そこで、以下のハックを使う
    • ユーザー定義型 Overloadsに、3項演算子 ?<-を定義する
    • その演算子は、Overloads型の値を1つ、=演算子を適用したい型の値を2つ受け取るが、Overloads型の値は捨ててしまい、本来やりたかった=演算だけを実装する
  • 以上の準備ができたなら、グローバルレベルでのインライン演算子定義で、*=を実装し、Overloads型の?<-演算子にぶん投げる
    • Overloads型の値は演算子オーバーロードの解決に使われた後は捨てられるので、unit的に値が1つしかない型で十分。よって、Overloads型は値が1つしかない判別共用体として定義してもいい。

なんとまあ。分かってしまえば何ということはなかった。?<-を使っているのは単にそれが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)のどちらかの型に対して*演算子オーバーロードが定義されており、もう一方の項の型もその定義に適合するのであれば、どんな型であってもこの新しい演算子を適用できる。

もちろん、この演算子コンパイル時にインライン展開(β変換)されるので、通常のジェネリックな型に適用することはできない。ジェネリックな型は実行時に解決されるものだからである。

(続く)

*1:F#では列挙型制約やデリゲート制約をつけられるが、C#ではできないという違いはある

演算子のオーバーロード #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

http://stackoverflow.com/questions/11150947/f-overloaded-operater-with-floats-not-working-with-other-floats/11152288#11152288

F#の ?<- 演算子は「動的プロパティに値を設定する演算子」なんですが、なんでそれがここに? しかも、

type Overloads = Overloads

って、値が1つしかない判別共用体ですよね。なんでそれがここに?

F#の演算子のことをよくわかっていないことに気づいたので、改めて勉強していこうと思います。

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位以内には入れたかもしれないけど、他の人のスコアも変動するので、実際はどうでしょうね。(計算すれば出ると思いますが、面倒なのでコードに落としていません)