F# WebSharperで関数型的ウェブ開発

F# Advent Calendar 2011 の4日目の参加エントリーです。
詳しくは http://partake.in/events/1c24993a-c475-4fc2-bca4-7a1cd2f81869 を。

本当は F# 3.0 の TypeProvider 機能を使って WPF のカスタムプロバイダーを紹介しようと思ったんですが、
微妙にまだ未完成なのでやめときます。

ということで今日は WebSharper やります。
※ 3日目担当の liner_lock さんの記事見たときは、かぶってるかと思って心臓止まるかと思いました。事実ちょっとかぶってますけど・・・。

WebSharperとは?

通常、.NET で Web アプリケーションを作成するとしたら
ASP.NETASP.NET MVC を使うかと思います。
ASP.NET(MVC) でもF#は使えないこともないですが、
View は結局のところ HTML や Javascript を使わなければいけないんですよね。
MVC3からサポートされている Razor でも通常ではF#は使えません。

今日紹介する WebSharper は Markup-less でクライアントベースのWebサイトを作成することができます。

さらに下記のような優れた特徴を備えています。

まずはインストール

WebShaper はここからダウンロードできるのでVisual Studio 2010が入った環境で適当にインストールしてください。
http://www.websharper.com/

ここで一つ注意点です。
現時点の WebSharper は Visual Studio 2012 および .NET 4.5 がインストールしている環境では使えません。
すでに入れちゃったって人は頑張ってアンインストールしてから楽しんでください。

Hello World の HTML を生成してみよう

VS2012 をアンスコして WebSharper をインストールできたでしょうか?
ではまず Hello World を作ってみましょう。

まずWebSharperのプロジェクトは次の項目を選択してください。

  • IntelliFactory > WebSharper 2.3 HTML Application (Sitelets)

このプロジェクトはサーバーサイドなしの HTML を生成するプロジェクトです。
※ サーバーサイド付のWebアプリの場合は WebSharper の Rpc、またはASP.NET, ASP.NET MVCを使うこともできます。

プロジェクトを作成すると最初に Main.fs というファイルが生成されるので下記のコードに書き換えてください。


namespace HelloWorld

open IntelliFactory.WebSharper.Sitelets

module Site =
open IntelliFactory.WebSharper
open IntelliFactory.Html

type Action = | Index

let Index =
PageContent <| fun context ->
{ Page.Default with
Title = Some "Index"
Body = [Text "Hello"]
}
let MyActions =
[
Action.Index
]

type HelloWorldWebsite() =
interface IWebsite with
member this.Sitelet =
Sitelet.Content "/" Action.Index Index
member this.Actions = [ Action.Index ]

[)>]
do ()

これをF5で実行させると
index.htmlというファイルが出力されるので確認してみてください。
以降は、このコードのBodyを書き換えて説明していきます。

WebSharper による Web アプリケーション開発

具体的に WebSharper でどのように Web アプリケーションを開発していくかを説明していきます。

静的な HTML の生成

HTML の要素と属性は IntelliFactory.Html.Tags と IntelliFactory.Html.Attributes モジュールの関数として提供されており、
たとえば下記の式は div 要素 を表しています。


Div []

Tags, Attributes の関数は INode< a > を継承しており、また Tags の関数は引数として INode< a > の seq を取るので
下記のように入れ子で HTML を表すことができます。
オフサイドルールやパイプライン演算子を上手く使ってね。

Div [
Div [ H1 <| [Text "H1の要素だよ"] ]
Div [ H2 <| [Text "H2の要素だよ"] ]
]

ですが属性を与えるためにはちょっと注意が必要です。
F#には暗黙の型変換が存在しないので Attributes の関数と Elements 関数の戻り値が 同じ INode< a >だからといって
下記のように一緒に引数に渡そうとするとエラーになってしまいます。

Div [
Attributes.Id "h1-div"
H1 [Text "H1の要素だよ"]
]

もちろんキャスト演算子を使ったり型注釈を加えればいい話なのですが、
それではさすがにめんどくさ過ぎるので、それを回避するための ( -< ) という コンビネータが用意されていますのでそれを使って下記のように直しましょう。
ちなみに Haskell の Arrow 記法 とは多分関係ありません。

Div [ Attributes.Id "h1-div" ] -<
[ H1 [Text "H1の要素だよ"] ]

さてこれで OnClick 属性で javascript 関数名を指定できるようになりましたが
単純に OnClick "MyJavascriptFunction" みたいに普通の javascriptを指定できてもあまりうれしくないですよね。
ここからは、お待ちかねの JavascriptからF#の関数呼び出し
再帰関数でもパターンマッチでもできますよ〜

javascript関数

まずは次のようなモジュールを作成します。


module Controls =
open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.Html

type HelloButton() =
inherit Web.Control ()

[]
override this.Body =
let result = Div []
Div [
Button [Text "Click"]
|>! OnClick (fun e args -> Text "add text" |> result.Append)
result
]
:> _


ここで open されている IntelliFactory.WebSharper.Html 名前空間 は IntelliFactory.Html 空間 とは別物で、
さらに Button 関数などの HTML 要素関数も 前者と後者の名前空間では別の関数であるということに気をつけてください。
(|>!)は let ( |>! ) x f = f x; x のように定義されていて、OnClick 関数のような unit を返す関数を使用する際に便利です。

次は今作った HelloButton を Body に埋め込みます。


[ Div [ new Controls.HelloButton ()] ]

このように Web.Control 型を継承して作成した型は インスタンシエートすると 静的な HTML に埋め込むことができます。
これを実行して生成された HTML を見てみると ボタンが一つだけあって クリックすると "add text" という文字列が DOM に追加されたかと思います。
ソースを見てみるとわかるとおり IntelliFactory.Html 空間のものはそのまま HTML になって IntelliFactory.WebSharper.Html 名前空間のものは WebSharper の javascript で生成されていることがわかりますね。
ここまでの内容でクライアントサイドの基礎はばっちりです。

便利機能

WebSharper は Control を簡単に生成するための機能を標準で提供しています。
その一つが Formlet です。
Webアプリで Form を作るのって非常にめんどくさいですよねー。
整列させて、ラベルつけて、検証機能をつけて・・・。
WebSharper では次のように簡単に Form をつくることができます。


module Controls =
open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.Html
open IntelliFactory.WebSharper.Formlet
open IntelliFactory.WebSharper.Formlet.Controls
open IntelliFactory.WebSharper.JavaScript

[]
let form =
Formlet.Yield id
<*> Input ""
|> Enhance.WithSubmitButton

type HelloForm() =
inherit Web.Control ()

[]
override this.Body =
form.Run Alert


この Form には input と submit があって submit すると alert に入力された内容を alert で表示するだけの簡単なものです。
Formlet.Yield 関数の第一引数には、合成する input の値を複数個、受け取って一つの値にする関数を指定します。
そこで返された値が Run メソッドの関数の引数となります。
ここでは単に string -> string なので id 関数を指定しています。
もちろん レコード型の値を返す関数を指定することができます。

もう少し複雑な Form を作成してみます。


module Controls =
open IntelliFactory.WebSharper
open IntelliFactory.WebSharper.Html
open IntelliFactory.WebSharper.Formlet
open IntelliFactory.WebSharper.Formlet.Controls
open IntelliFactory.WebSharper.JavaScript

type PersonalInfo = {
Name : string
Age : int
}

[]
let form =
Formlet.Yield (fun name age -> { Name = name; Age = System.Int32.Parse age })

<*> (Input "佐藤祐介"
|> Enhance.WithTextLabel "名前"
|> Validator.IsNotEmpty "is not empty")

<*> (Input "100"
|> Enhance.WithTextLabel "年齢"
|> Validator.IsNotEmpty "is not empty"
|> Validator.IsInt "is int")

|> Enhance.WithSubmitButton

type HelloForm() =
inherit Web.Control ()

[]
override this.Body =
form.Run (fun person -> person.Name + "さんは" + person.Age.ToString () + "才です" |> Alert)


これで レコード型の Form を作成できました。
しかも検証機能もつきました。
Enhance, Validator モジュール超便利ですね!

Formlet には コンピュテーション式ビルダーが用意されていますが、
みんな大好き Applicative スタイルもサポートされているので安心ですね!

RPC

サーバーサイドでの処理を実行させたいときは RPC という機能を使うことができます。
ただしこれをやるときは当たり前ではあるが IIS でホスティングしないといけなく
今日はめんどくさいので、また今度詳しく書こうかと思います。
もちろん WebSharper のRPCを使わなくても ASP.NET や ASP.NET MVC との連携でサーバーサイド処理をすることも可能です。

不満など

  • 動的に生成される HTML がひどすぎる。Tableがネストしまくり。
  • Control の中にうっかり override 以外の関数を作ったりすると、ビルド時に意味のわからないエラーを吐いてくれる。
  • しょうがないけどビルドが超遅い
  • まだまだ実績が少なく情報も少ない。日本語の情報は皆無。

最後に

いままで記事を書くと、「端折りすぎだ!」っていつも言われてたので
今回は割りと丁寧に書いたつもりです。


あと今日はもう疲れたので F# Type Provider で WPFはまた今度書きます。
bleis先生ごめんなさい。
というか、ソースを載せておくので誰か治し方おしえてください。 → https://gist.github.com/1428096


次の F# Advent Calendar は まえしー こと @maeda_ です。
ではでは〜