WebSharperの基本
はじめに
この記事は F# Advent Calendar 2015の11日目です。 4年前に、WebSharperの紹介記事を書きましたが内容が陳腐化してしまったで、またWebSharperについて書きます。
まだ書き途中なので随時更新します。
WebSharperとは
WebSharperは、F#でWebアプリケーションを作るためのフレームワークです。 Client-SideもF#で記述するので、型の恩恵を受けれます。
Visual StudioでWebSharperを使う
現在、WebShaper 公式サイトからダウンロードできるVSIXは、WebSharper 3.x系に対応していません。 WebSharper 3.xのVSIXを入手するには、websharper.templatesが必要です。
- github/websharper.templates.gitをCloneします。
- websharper.templatesをビルドします。
- ビルドして出来たプログラムを実行してvsixを生成します。
Siteletsでウェブサイトを定義する
WebSharperがウェブサイトを定義するためにSiteletsという方法を提供しています。 ウェブサイトは、リクエストからレスポンスへの変換を定義します。 Website属性のつけた変数がウェブサイトになります。
[<Website>] let main : Sitelet = (* ここにSiteletを定義する *)
Website属性をつけた変数は、Sitelet型である必要があります。 Siteletは、下記の役割をもっています。
- リクエストからアクションを生成する
- アクションからコンテントを生成する
WebSharperは、HTTPのルーティングを型として抽象的に表現します。 そしてアクションは、そのルーティングの結果です。 コンテントは、レスポンスを得るための関数です。 つまりSiteletは、アクションやコンテントという抽象を介してリクエストをレスポンスに変換するための型です。
Siteletの具体的な定義を示します。
type Sitelet<'Action when 'Action : equality> = { Router : Router<'Action> Controller : Controller<'Action> }
Siteletを構成する型を説明します。
Siteletを構成する型 | 定義 |
---|---|
Router | Request -> 'Action |
Controller | 'Action -> Content<'Action> |
Controllerの戻り値であるContent<'Action>型は、下記のように定義されています。
type Content<'Action> = CustomContent of (Context<'Action> -> Http.Response) CustomContentAsync of (Context<'Action> -> Async<Http.Response>)
Content<'Action>には、同期版と非同期版がありますが、どちらもレスポンスを生成するという点では同じです。 下記は、Content<'Action>を生成するためのメソッド一覧です。コンテントの種類によって使い分けましょう。
メソッド | 説明 |
---|---|
Content.Page | HTMLのボディ、HTMLのヘッド部、HTMLのタイトル、HTMLのDoctypeを指定してHTMLのコンテントを生成します | |
Content.Json | JSONのコンテントを生成します |
Content.Text | テキストのコンテントを生成します |
Content.File | ファイルを読み込んでコンテントを生成します |
Content.Custom | HTTPステータス、HTTPヘッダー、ボディを指定してコンテントを生成します |
コンテントを返すSiteletを定義する
コンテントを返すSiteletは、Sitelet.Contentメソッドで定義します。
Sitelet.Contentメソッドの型は、location: string -> action: 'T -> cnt: (Context<'T> -> Async<Content<'T>>) -> Sitelet<'T>
です。
引数名 | 型 | 説明 |
---|---|---|
location | string | コンテントの場所を指定します。 |
action | 'T | コンテントのアクションを指定します。 |
cnt | Context<'T> -> Async<Content<'T>> | コンテキストからコンテントの変換を指定します。 |
下記は、ルート(GET /)に対してページ(HTML)を返すSiteletの定義例です。
type EndPoints = Index [<Website>] let main = Sitelet.Content "/" Index (fun _ -> Content.Page(Body=[Text "login helo"], Title="login page"))
Siteletを合成する
SiteletはSitelet.Sumメソッドで合成できます。
[<Website>] let main = Sitelet.Sum [ Sitelet.Content "/" Login (fun _ -> Content.Page(Body=[Text "login helo"], Title="login page")) Sitelet.Content "/my" My (fun _ -> Content.Text "my") ]
Siteletのルーティングを型で定義する
Siteletのルーティングは、抽象化されていて型で定義できます。
Sitelet.Infer
メソッドは、定義したルーティングとURLとのマッピングを推論します。
型がどんなふうにルーティングを表現するかは下記のチートシートを参照するとよいでしょう。
http://websharper.com/docs/sitelets-ref
認証されたSiteletを定義する
Sitelet.Protectメソッドは、認証されたSiteletに変換します。
第一引数にはFilterを適用します。
Filterは、VerifyUser : string -> bool
と LoginRedirect : Endpoint -> Endpoint
で定義します。
(* 変換したいSitelet *) |> Sitelet.Protect ({ VerifyUser = (fun _-> false) LoginRedirect = (fun _ -> Login) } : Sitelet.Filter<EndPoints>)
Applicationモジュール
WebSharper.Applicationモジュールは、Siteletのファサードです。
HTML Combinators
WebSharperで扱うHTMLは、HTML Combinatorsによって生成します。 HTML Combinatorsは2種類あります。
- Server-Side HTML Combinator
- Client-Side HTML Combinator
Client-Side HTML Combinatorは、Pageletとも呼びます。
Server-SideのHTML Combinator
Server-SideのHTMLは、WebSharper.Html.ServerのHTML Combinatorを使って生成します。
- HTML要素の型 : Element
- HTML属性の型 : Attriute
ElementとAttributeは、INodeを実装しています。
let serverSideHtml : Element = Article [ H1 [Text "hello serverSideHtml"] // 下記のように属性を定義する場合は、アップキャストするのがめんどくさいので -< 演算子が定義されている P [Class "paragraph"] -< [Text "This is a paragraph."] // HTML: <p class="paragraph">This is a paragraph.</p> // 下記のように Tags、Attrを修飾するとインテリセンスが効いて便利。 // AutoOpenなので、修飾しなくても大丈夫。 Tags.P [Attr.Class "paragraph"] -< [Tags.Text "This is a paragraph."] embedClientSide ]
Client-SideのHTML Combinator (Pagelet)
Client-SideのHTMLは、WebSharper.Html.ClientのHTML Combinatorを使って生成します。 Server-Sideとは名前空間が違うだけで同じ関数名でHTMLを生成できます。
- HTML要素の型 : Element
- HTML属性の型 : Attriute
ElementとAttributeは、Pageletを実装しています。
JavaScript属性をつけておくことで、ビルド時にWebSharperがILからjavascriptにコンパイルします。
[<JavaScript>] module ClientSideHtml = open WebSharper.Html.Client let clientSideHtml () : Element = Article [ H1 [Text "hello clientSideHtml"] ]
サーバーサイドのHTML Combinatorは、変数でよいですがクライアントサイドの場合は関数かプロパティでないといけません。。 Server-Sideとは違いClient-SideのHTMLは、(|>!) 演算子 と OnXXX系関数 でイベントハンドラーを定義します。
let clientSideHtmlWithEventHandler () = Article [ H1 [Text "hello clientSideHtmlWithEventHandler"] Button [Text "Click Me"] |>! OnClick (fun button event -> WebSharper.JavaScript.JS.Alert("clicked!") ) ]
PageletはRenderメソッドを持っていて、レンダリングが終わったことをOnAfterRenderでフックできます。
let clientSideHtmlWithHookRendering () = Article [] |>! OnAfterRender (fun article -> H1 [Text "hello clientSideHtmlWithHookRendering"] |> article.Append )
Pageletは、動的なDOM操作をサポートしています。
let clientSideHtmlWithDynamicDom () = Article [] |>! OnAfterRender (fun article -> // 要素を追加したり H1 [Text "hello clientSideHtmlWithDynamicDom"] |> article.Append // 要素のテキストノードを読み取ったり Div [ H2 [Text "Text property"] Div [Text (P [Text "text property value"]).Text] ] |> article.Append // 要素のinner HTMLを読み取ったり Div [ H2 [Text "Html property"] Div [Text (P [Text "Html property value"]).Html] ] |> article.Append // JSとしてのDOMを読み取ったり (P [Text "DOM property value"]).Dom // JavaScript.Dom.Element |> article.Append // JSとしてのBodyを読み取ったり (P [Text "Body property value"]).Body // JavaScript.Dom.Node |> article.Append )
PageletをServer-Sideに埋め込む
Client-SideのHTML ConbinatorsをControlとして定義する
Controlとして、Client-SideのHTMLを定義することもできます。 Controlを定義することで、つぎの2つのメリットがあります。
module ClientSideHtmlAsControl = open WebSharper.Html.Client type ClientSideControl() = inherit WebSharper.Web.Control () [<JavaScript>] override this.Body = Article [ H1 [Text "hello clientSideHtml"] ] :> _
RPC(Remote Procedure Call)
関数にRemote属性をつけると、Pageletから呼び出せるようになります。 Rpc属性というものもありますが、Remote属性と同じ意味です。 RPCには、下記の3種類があります。
- メッセージパッシング
- 非同期呼び出し
- 同期呼び出し
WebSharper 2からサポートされたHandlerObjects機能を使うとメソッド呼び出しもできるらしいが、使い方はよくわかりません。
メッセージパッシング
* -> unit
なRemote関数は、メッセージパッシングになります。
つまり、クライアントはRemote関数の終了に対してブロックをしません。
[<Remote>] let callMessagePassing () = Thread.Sleep(5000)
非同期呼び出し
* -> Async<*>
なRemote関数は、非同期呼び出しになります。
つまり、クライアントはRemote関数の終了に対してブロックをしません。
クライアントサイドのコードは、Asyncを使って戻り値を取得できます。
[<Remote>] let callAsync () = async { // async中で実行される処理は、スレッドがリクエストのスレッドとは異なるので // System.Web.HttpContext.Currentのようなスレッドローカルなものに注意する。 // コンテキストを取得するにはWeb.IContextを使うこと。 Thread.Sleep(5000) return "ok" }
同期呼び出し
* -> *
なRemote関数は、同期呼び出しになります。
つまり、クライアントはRemote関数の終了に対してブロックします。
同期呼び出しは、呼び出し中にブラウザが固まるので推奨されません。
[<Remote>] let callSync () = Thread.Sleep(5000) "ok"
WebContext
WebContextを使ってRpcでユーザーを取得します。
[<Rpc>] let showLoginUser () = // Rpc(Remote Procedure Call)でIContextを取得するにはRemoting.GetContextメソッドを使う。 let ctx = Remoting.GetContext() ctx.UserSession.GetLoggedInUser()
RpcをClient-Sideから呼び出します。
[<JavaScript>] module ClientSideSample = open WebSharper.Html.Client let printLoginUser () = H1 [] |>! OnAfterRender (fun p -> async { let! user = RemoteSample.showLoginUser() p.Append("Welcome : " + user.Value) } |> Async.Start )
SiteletにClientSideSampleを埋め込みます。 Siteletの中では、IContext経由でUserSession機能を使えます。
module Site = open WebSharper.Html.Server type EndPoints = string [<Website>] let Main = Sitelet.Infer (fun ctx user -> async { // Siteletの中では、IContext経由でUserSession機能を使える。 do! ctx.UserSession.LoginUser(user) return! Content.Page(Body=[Div [ClientSide <@ ClientSideSample.printLoginUser () @>]], Title="hoge") } )
HTML Template
HTML Templateは、HTMLに下記の3つの方法でプレースホルダーを埋め込むことができます。
プレースホルダーの種類 | 指定方法 |
---|---|
子要素プレースホルダー | data-hole 属性をHTML要素につける |
要素そのもののプレースホルダー | data-replace 属性をHTML要素につける |
文字列プレースホルダー | HTML要素のインナー要素として${xxxx} を記述する |
下記のようなhtmlファイルでtitle
とbody
をプレースホルダーにします。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <title>${title}</title> </head> <body> <div data-replace="body"> </div> </body> </html>
Main.htmlのテンプレートを埋めるための型を作ります。 Pageのメンバは、プレースホルダーにした要素になります。
type Page = { Title : string Body: list<Element> }
最後にContent.Template関数でContent.Templateオブジェクトを作成します。 プレースホルダーをどのように置換するかWithメソッドで記述します。
let MainTemplate : Content.Template<Page> = Content.Template<Page>("~/Main.html") .With("title", fun x -> x.Title) .With("body", fun x -> x.Body)
次にArticle.htmlのテンプレートを作ります。
プレースホルダーは、title
とcontent
です。
<article> <h2>${title}</h2> <section class="main" data-hole="content"></section> </article>
Article.htmlのテンプレートを埋めるための型を作ります。
type Article = { Title: string Content: list<Element> }
let ArticleTemplate: Content.Template<Article> = Content.Template<Article>("~/Article.html") .With("title", fun x -> x.Title) .With("content", fun x -> x.Content)
let MainPage (ctx: Context<Application.SPA.EndPoint>) : Async<Content<Application.SPA.EndPoint>> = let articles : Article list = [ { Title = "Article1"; Content = [Text "this is the article1"]} { Title = "Article2"; Content = [Text "this is the article2"]} ]
MainTemplateをContent.WithTemplateでContentを生成します。
- 第一引数にContent.Template
- 第二引数にT
Content.WithTemplate MainTemplate { Title = "My Page" Body = [ yield H1 [Text "Body of the page"] for article in articles do // Content.Templateに対してRunすることでインスタンス化できる // thisにContent.Template<T> // 第一引数にT // 第二引数にルートフォルダ(string) yield! ArticleTemplate.Run(article, ctx.RootFolder) ] }
UI.Next
UI Nextは、リアクティブなUIを構築するためのクライアントサイド向けのライブラリです。
名前空間 |
---|
WebSharper.UI.Next.Server |
WebSharper.UI.Next.Html |
WebSharper.UI.Next.Client |
Siteletを定義する
Siteletを定義します。
module Site = open WebSharper.UI.Next.Server open WebSharper.UI.Next.Html [<Website>] let Main = Application.SinglePage (fun ctx -> // client関数で、クライアントサイドのDocをサーバーサイドに埋め込みます。 Content.Doc(client <@ ClientSideSample.firstSample () @>) )
Client-Sideでリアクティブ変数を作る
Client-SideでUI.Nextを使うには下記の名前空間を開きましょう。
open WebSharper.UI.Next.Client open WebSharper.JavaScript
リアクティブ変数(Var)を作る。 rvプリフィックスは、Reactive Variableの意味。
let rvInput = Var.Create ""
View.FromVarで VarからViewを取得できる。
let vInput = View.FromVar rvInput
View.Mapで入力入力された文字列を大文字化します。
let vUpperInput = vInput |> View.Map (fun str -> str.ToUpper())
UI.Nextは、Virtual Domを扱うための型としてDoc型を用意しています。 Doc型は、IControlBodyを実装しています。 Doc.AsPageletでPageletに変換できる。 Docは、Concatで結合できます。
Doc.Concat [ // Doc.Inputは、リアクティブ変数と結びつくインプット要素を生成します。 Doc.Input [] rvInput // View<string>からテキストノードを生成します。 Doc.TextView vUpperInput ]
HTML要素は、Elmを返す関数で表します。 divAttrのようにAttrサフィックスの関数は、Attrを受け取ります。
divAttr [attr.styleDyn vSearchEngineColor] [ Doc.Select [] (function |Google -> "Google" | Yahoo -> "Yahoo") [Google; Yahoo] rvSearchEngine Doc.TextView (rvSearchEngine.View |> View.Map (function |Google -> "Google" | Yahoo -> "Yahoo")) ]
SubmitterでDataflowの変更を通知する
Submitterは、Dataflowレイヤーの特別なノードです。 SubmitterのViewは、最後にTriggerを呼ばれた時点のViewになります。
作成時にViewと初期値を渡します。
let submitter = Submitter.Create rvInput.View ""
ButtonのアクションとしてSubmitterのTriggerを渡せます。
Doc.Concat [ Doc.Input [] rvInput Doc.Button "submit" [] submitter.Trigger Doc.TextView submitter.View ]
UI.NextのTemplating
// TODO
ApiaryProviderで大マッシュアップ時代を生き抜く
はじめに
この記事は F# Advent Calendar 2014の3日目です。 今回は、ApiaryProviderというライブラリを紹介します。
Apiaryとは
Apiaryとは、Blueprintと呼ばれるWebAPI記述言語で作られたドキュメントをホスティングするサービスです。 ただドキュメントを公開するだけでなく、そのドキュメントを元にモックサーバーを立ち上げたり、 クライアントからの容易なアクセスを提供することができます。 詳しくは、apiary.io をチェック!
こんな面白そうなAPIも公開されています!
- FsSnippetのAPI
- 出前のAPI
- テスラSのAPI (まずテスラSが欲しい!)
Blueprintについて
WebAPIの仕様記述といえば、SOAP、WSDLにトラウマを持っている人は少なくないんじゃないでしょうか。 Apiaryが対応しているBlueprintという言語は、Markdownベースで非常にシンプルです。
下記の公式サンプルを見てください。
F#でApiaryを利用する
ApiaryProviderというライブラリを使うことで Apiaryで公開されてるAPIを、F#から簡単に使うことができます。
まずは、nugetでインストールしてください。
PM> Install-Package ApiaryProvider
FsSnippetを利用する簡単なサンプルを作ってみましょう。
open ApiaryProvider [<EntryPoint>] let main argv = let fs = new ApiaryProvider<"fssnip">("http://api.fssnip.net/") let snips = fs.Snippet.List() for snip in snips do printfn "%s" snip.Title 0
ApiaryProviderの型引数に渡すのは、Apiaryに登録されているAPI名です。 コンストラクタのURLは、実際にリクエスト飛ばす先になります。
ApiaryProviderは、指定されたAPIのBlueprintから、APIの構造を型に反映します。 後は、インテリセンスの力でさくさくっとAPIを呼び出すことができます。
fs.Snippet.List()
の部分は、Snippetの一覧を取得するAPIを呼び出しています。
また結果に対しても、型が付いていることがわかると思います。
これは便利!
最後に
近年、IFTTTや、Zapierなど便利なハブサービスを多く目にするようになりました。 インターネット上に存在する様々なサービスを、容易に組み合わせられる環境が整ってきたと感じます。
今回紹介したApiaryとApiaryProviderは、WebAPIを手軽に扱うために役立ちます。 ハブサービスや、Apiaryをうまく活用すれば、きっとマッシュアップ時代をより楽しむことができると思います。
ということで3日目終了です。 明日は、@yukitos さんです!
DelegateDecompilerでEntityFrameworkをぱわわっぷ(入門編)
はじめに
こんにちは、みなさんEntityFrameworkでドメインモデルつくってますか?(EFじゃできねーよ!という声が聞こえてきます…)
今日はEFプログラミングでドメインモデルづくりを促進するライブラリDelegateDecompilerを紹介します。
このライブラリはEntityFramework専用というわけではないですが、 個人的に便利なユースケースとしてEntityFrameworkプログラミングを中心に解説を行います。
概要
DelegateDecompilerは、Delegate のIL(中間言語)を解析して、 式木にDecompileしてくれるライブラリです。
例えば、下記のような Expression(式木) -> Delegate がCompileだとすれば
Expression<Func<int, bool>> expr = (int i) => i == 5; Func<int, bool> compiled = expr.Compile();
逆操作である、Delegate -> Expression(式木) は Decompile(逆コンパイル)ですね!
Func<int, bool> del = (int i) => i == 5; Expression<Func<int, bool>> decompiled = del.Decompile();
もちろんすべてのILを式木に変換してくれるわけじゃありません。 だけど、Linq to Entitiesで使うとしたらSQLに変換できるレベルのILは割とがんばって変換してくれます。
導入方法は、Nugetに登録されているのでDelegateDecompilerで検索すればヒットします。
EntityFrameworkの不便さ
DelegateDecompilerを使う前に、EntityFrameworkの不便さを味わってみましょう。
下記のようなエンティティクラスがあるとします。(簡単のため細かいところは端折っています)
public class Person { // FirstName と LastName はデータベースのスキーマとマッピングされています。 public string FirstName { get; set; } public string LastName{ get; set; } // FullName はデータベースのスキーマではない public string FullName { get { return FirstName + " " + LastName; } } }
※ Model/Database-firstの場合はparshal classを使ってください。
Person のリポジトリ(IQueryable)に対してFullNameを検索するにはこんなクエリを書きたいでしょう。
// 実行時にNotSupportedException var result = people.Where(p => p.FullName == "Taro Yamada").Single();
※ Singleのオーバーロードを使わないのはDelegateDecompilerを使うための布石
残念ながらこの呼び出しはLinq to Entitiesでは実行時エラーです。 プロパティの呼び出しという式木は、SQLに変換できないからです。
正しく動作させるためには、下記のように修正します。
var result = people.Where(p => (p.FirstName + " " + p.LastName) == "Taro Yamada").Single();
このように、クエリの式木で表現できるのはデータベースのスキーマと直接マッピングできるものだけに限定されてしまいます。
おかげでせっかく定義したFullNameが台無しですね。
DelegateDecompilerを使う
DelegateDecompiler で上のコードを改善するのはすごく簡単!
Decompileする必要のあるメソッド、もしくはプロパティにComputed属性をつけて、
[Computed] public string FullName { get { return FirstName + " " + LastName; } }
クエリに対して Decompile メソッドを呼ぶだけです。(クエリがToList,SingleなどでSQLが発行される前に)
var result = people.Where(p => p.FullName == "Taro Yamada").Decompile().Single();
結果的には、FullNameプロパティ呼び出しは、その実装であるFirstName + " " + LastName
が式木に変換されます。
これは明らかにSQLに変換可能ですね! めでたしめでたし。
最後に
とても便利なDelegateDecompilerですが、使用する上で気を付けなければいけない点があります。
例えば…
- 何度も言いますが、すべてのメソッド、プロパティを式木に変換できるわけではありません。 もっともEntityFrameworkと併せて使う場合は、式木に変換できてかつ、式木からSQLに変換できる必要があります。
- 式木に変換できなかった場合、Stackが空です のような一見不可解な例外が発生します。
- クエリが実行されてSQLが発行されたあとにDecompileしても何の意味もないので、SQLが実際に発行されるタイミングを十分に意識する必要があります。(Linq to Entitiesだけに限ってもそうだけどね…)
ラムダ式を含むメソッド、プロパティは今日時点のDelegateDecompilerでは式木に変換できません。 現在、対応版をpull requestをしているところです。私の github から直接 clone してくれば問題ありません。-> v0.8.0から対応
今回は、EntityFrameworkに絞ってDelegateDecompilerを紹介しましたが、それ以外の可能性も無限大です!
Functional HTTP Programming
はじめに
この記事は F# Advent Calendar 2012の13日目です。
今回は、仕事でも十分に”実用”できるFunctional HTTP Programmingについて書いていきます。
一般的な.NET HTTP プログラミング
.NETでHTTPプログラミング(サーバーサイド)といえば、一般的に下記のようなものがありますね。
- ASP.NET
- ASP.NET MVC
- WCF WEB HTTP
これらでF#を使って関数型の恩恵を受けようとしても下記のようなさまざまな障害があります。
- 肥大化して複雑な設計のHttpRequest, HttpResponse
- パターンマッチの使えないNameValueCollection
- コンパイル時検証がまったくないUriTemplate
Lemonとは
Lemonとは、ASP.NET上で関数型プログラミングを簡単に行えるライブラリです。 今回は主にLemonを使用し、簡単なHTTPプログラミングをしていきます。 このライブラリはgithubにてソースコードが公開されています。
Lemonの使い方
最初のコード
まずは、Lemonでhello worldを書いてみましょう。
準備のために下記の手順でプロジェクトを作成します。(Visual Studio 2010の場合)
- "新規作成"で"新しいプロジェクト" > "Visual C#" > "ASP.NET空のWebアプリケーション"(プロジェクト名はHelloLemon)
- "追加"で"新しいプロジェクト" > "Visual F#" > "Windows" > "F# ライブラリ" (プロジェクト名はHelloLemon.Code、.NETは4.0)
- HelloLemonからHelloLemon.Codeをプロジェクト参照する。
- HelloLemon.CodeのLibrary1.fsを削除。
- HelloLemon.Codeにnugetでlemonを検索し、インストール。
HelloLemon.Codeに下記の二つのファイルを追加
Server.fs
module Server open Lemon let (server:Server) = function | GET(Url []) -> ok >> response "hello world" | _ -> notFound
- Handler.fs
namespace HelloLemon type Handler () = inherit Lemon.HttpHandler(Server.server)
- HelloLemonのWeb.configを下記のように書き換える
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.5" /> <httpRuntime targetFramework="4.5" /> <httpHandlers> <add verb="*" path="*" type="HelloLemon.Handler"/> </httpHandlers> </system.web> </configuration>
これで準備完了です。 実行してみるとブラウザ画面にhello worldと表示されるはずです。
リクエストのパターンマッチ
Server.fsで定義したようなServer型の関数はHTTPメソッド、URL、HTTPヘッダー、クエリパラメータなどでパターンマッチをすることができます。
HTTPメソッドとURLによるパターンマッチ
HTTPメソッドとURLは同時にパターンマッチすると便利です。 アクティブパターンとしてGET, POST, PUT, DELETEが定義されていて、 一つ目の引数はURLのパターン、POSTとPUTの場合は二つ目の引数にボディのストリームが渡されます。 これらを使用したコード例を次に示します。
// GET /hoge/piyo/foo.txtのリクエストで文字列を返します。 | GET(Url["hoge" ; "piyo"; "foo.txt"]) -> ok >> response "GET /hoge/piyo/foo.tx" // POST /echo のリクエストで受け取った文字列ボディをそのままエコーします。 | POST(Url["echo"], body) -> ok >> (readText body |> response) // PUT /put のリクエストでボディを受け取って201 Createdを返します。 | PUT(Url["put"], body) -> created // DELETE /delete のリクエストで204 NoContetを返します。 | DELETE(Url["delete"]) -> noContent
HTTPヘッダーとクエリパラメータのパターンマッチ
あとで書く
レスポンス生成
レスポンスは、型がResponserである型を一つ以上合成することで生成されます。 ステータスコード関数、ボディ関数、ヘッダー関数などを組み合わせることができます。 これらを使用したコード例を次に示します。
// ステータスコード: 200 Ok // ボディ: 空 ok // ステータスコード: 200 Ok // ボディ: "Hello World" ok >> response "Hello World" // ステータスコード: 404 Not Found // ボディ: "NotFound" // ヘッダー: Hoge : aaa notFound >> response "NotFound" >> setHeader "Hoge" "aaa"
ボディ関数はテキストを送信するresponse関数以外にも、XMLを送信するxmlResponseがあります。
エラー処理
あとでかく
機能拡張
あとでかく
注意点
lemonを使う上で注意すべき点が二つあります。
まず第一にHTTPヘッダーの扱いを簡単にするためにNameValueCollectionを単なるASOCリストにしています。
NameValueCollectioは実は同じキーを複数持てるのでこれは正しくありません。
ちなみに、ASP.NET MVCの新しいAPIではNameValueCollectionではなくIDictionary<string, IEnumerable
もう一つの弱点は、ボディのバッファレス読み込みに対応してないことです。 そのため、ボディの読み込みは一時的にオンメモリにバッファしてしまいます。 これは.NET 4.5でサポートされた機能ですが、lemonは.NET 4.0で作られているためです。
http://msdn.microsoft.com/ja-jp/library/hh160863.aspx
さいごに
レガシーなAPIにFunctionalな皮を被せて扱うことは非常に楽しいです。 僕は仕事でHTTPのコードをガリガリ書くことが多いので、もはやLemonは仕事必需品です! みなさんも関数型で仕事をバリバリ効率化しましょう!
F# Advent Calendar 2012 14日目は、僕の右斜め後ろの席の @htid46 さんです。 よろしくオナシャス! それではまた。
詳細設計書について思うこと 続き
前回の記事の続きです。
注意:結構身内ネタが含まれていてコンテキストがわからない人はなんのこっちゃってなるかもしれません。
今回は@kyon_mm君の設計書論争での独り言についてです。
前回の記事を書いて@kyon_mmの記事を読んだ翌日、みんなで議論をしました。
メモ書きを元にしてるのでまとまってなく記憶違いもあるかもしれませんが、
議論の内容と、僕の意見を書き残しておきます。
僕が@kyon_mmの記事を読んだ限り僕が否定するような主張はほとんどないが、僕の記事をミスリーディングにしてるのでは?と思う節はあった。
具体的には、
プログラミング言語が十分に読み易くなった現代では不要という主張
http://d.hatena.ne.jp/kyon_mm/20120809/1344442163
の内容。
僕の記事では、「”設計書”とは本来ソースコードのことではないのか。」とした上で、「わざわざ自然言語で設計書を作ることに意味が薄れてきている」と主張したが、
ドキュメントの存在(言うまでもなく設計書だけがドキュメントではない)や、それに記述する自然言語や図については一切批判していない。これは記事の最後でもくどい位に言っている。
さらにいうとアプリケーションドメインとソリューションの差が縮まったといってるだけで、ソリューションドメインのすべてを形式言語で記述するべきという立場はとっていない。
ソースコードの話に限定したとしても、コメントばかりのソースコードは批判するが、コメント自体の存在を否定しているわけではない。
ソースコードの意図や制約について説明する必要があるなら、自然言語や図を使うことはいいことだと思う。
僕の主張することは、ソリューションドメインを記述するための中心的なドキュメントは、
@irofさんの記事のようなExcelによる詳細設計書ではなくプログラミング言語などの形式言語であるべきだということに過ぎない。
僕は”詳細設計書”という名前と、その実態(一例としては@irofさんの記事のようなもの)をセットで批判しているので、
@kyon_mmが必要だと主張する意図や境界を記述するドキュメントは、要件定義書や仕様書などと呼べばいいと思う。
そして僕の批判した実態は、そうではなくソリューションドメインを自然言語で記述しているものである。
@kyon_mmの論法は次のように思える。(もし@kyon_mmの記事が僕の記事への批判だと仮定すれば)
- 僕は詳細設計書の一例をあげて、それについて批判をする。
- @kyon_mmは詳細設計書を僕の定義したものから拡大解釈して(僕はドキュメント自体は批判しないと念を押しているにもかかわらず)、僕の言っている主張との矛盾点を作り上げそれに対して批判を行う。(きょん君いわく僕の記事への批判じゃないそうだが僕はそう感じた)
この構造は、わら人形論法そのもの。
それが意図的かどうかはさておき、僕が[”設計書”とは本来ソースコードのことではないのか。]という主張をWhat is Software Designの引用だけで済ませてしまったというところには、僕にも落ち度はある。
これについてはこの記事(→"”設計書”とは本来ソースコードのことではないのか。"の説明)で、もう一度まとめようと思う。
”つくるべきものの意図が十分に伝わっていない設計書にいかほどの価値があるのか?(意図が伝わる設計書にすべきである)」”について
これに関しては、僕への批判ではないと思っている(多分)
ただし、付け加えて言うならできるだけ意図をソースコードという設計書に含める努力はするべきである。
近年のプログラミング言語の発達により、C言語のような高級言語の皮をかぶった低級言語を使うよりソースコードに意図を多く含められるようになったと感じる。
たとえば
- ガベージコレクションなどの技術で意図に関連しない記述を減らす。
- 命名規約で関数名や変数名に意図を反映する。
- Design By Contract(契約による表明)
- 関数型プログラミングによる制御構造の抽象化(インテンショナルプログラミングに通ずるものがある)
- アプリケーションドメインに対するDSLを定義する(@kyon_mmもこれは主張してるよね?)
です。
もちろんそれだけで補えない部分を、コメントやワークフローなどの図で表現することは多いに賛成。
ソースコードからAPI一覧やクラスダイアグラム、ワークフローを自動で出力する技術についても賛成。
DSLの話。
アプリケーションドメインをどう表現するかの話でDSLについても盛り上がった。
僕は、アプリケーションドメインとソリューションドメインの距離が縮まりはすれど、決して同一にはならないと主張すると次のような意見がでた。
・現在は技術的な課題はあるが、それが解決すればアプリケーションドメインのすべてがDSLによって記述することができる(@bleis, @kyon_mm)
→ ハードウェア制約は?(@otf)
→ 将来的にそれを考慮する必要はなくなる(@bleis) → たしかに今でも昔にくらべれば富豪プログラミングという側面はあるよね(@otf)
→ UXなどのアプリケーションドメインは?(@otf)
→ ここでいうアプリケーションドメインは形式的に記述できるものと定義する(@bleis)
→ 形式的に定義できるアプリケーションドメインを仮に、”アプリケーション代数”と仮定すれば、
アプリケーション代数とソリューション代数が同一になることは直感的*1に正しいと思う(@otf)
→ DSLについては技術的な課題があるし、証明されたわけでもないと思うのであくまで”直感的に”という話だね(一同)
「なんでリバースエンジニアリングと要件定義一緒にやってんの。。。」について
これはもしかしたら僕の仕事で書いたコードで@kyon_mmにそうさせているのかもしれないと思いました。
そのときのコードは、自分の技術不足でコーディングに無駄な時間をかけてしまい、
そのような苦労を押し付けてしまったと思っています。
ですが、決してドキュメントを書かない主義ではないということを主張しておきます。
"”設計書”とは本来ソースコードのことではないのか。"の説明
歴史的経緯を無視した、〜べき論の話になりますが、
設計書という名前がつけられているからには、それはソフトウェアの設計を表していると思います。
ということで、まずは設計とは何か・何であるべきかということから議論する必要があります。
ExcelやWordなどを使い自然言語と図で記述するドキュメントを書くアクティビティを設計とし、
ソースコードを記述するアクティビティを製造とする文化があると思いますが、
それによっていくつかの弊害が現れます。
例えば、
- "信じられないほど巨大かつ複雑なもの"を管理と検証が難しい自然言語で記述しなければいけないこと
- "完璧な設計*2となっている場合,製造チームは設計者からの介入を一切受けることなく,その製品を生産できる"という誤解を生みやすいこと。(これは自動車業界など他業種における工学のアナロジです。)
です。
逆説的に、完璧な設計があれば、製造を完了することができるものが設計だとすれば、
ソースコードが設計であるといえると思います。
さらに製造とはソースコードのビルドになります。
そうすれば上記のような弊害はなくなります。
前回の記事でも言いましたが、ビルドが非常に低コストで行えるというのがソフトウェア工学の特徴の一つです。
つまり、ビルドが完了したあとのアクティビティであるテストによる結果をすぐに設計に反映してイテレーションを高速にまわすことにつながるわけです。
補足ですが、テストはビルドを行ったあとでないとできませんが
数理的モデルの検証や証明はビルドを行わなくても可能です。(AlloyやCoqなど)
ここで説明したことはWhat Is Software Design(和訳)の要約みたいなものです。実際いくつかの文は、ほとんどそのまま引用しています。
最後に
基本的に@otfに向けた記事
http://d.hatena.ne.jp/kyon_mm/20120809/1344442163
という内容の記事で
設計書否定するなら、ここにある事くらい論破するくらいの人じゃないと一緒に仕事したくない。逆に、ここに同意するくらいなら設計書否定すんなよ。自分の仕事を呪え。
http://d.hatena.ne.jp/kyon_mm/20120809/1344442163
とまで言われたら僕への批判ではないといわれても、
その記事を僕の主張への批判だと受け取るほかない。
名指しでかつ、”設計書否定するなら”に僕が当てはまっていたので、この記事で自分の考えを書きましたが、
もしそれ自体が勘違いであればごめんなさい。
もちろん、僕への批判だと主張しながらこの記事に対しての反論することは大いに結構だと思います。
技術的な話なので、その点については僕への社交辞令は必要ないです。
詳細設計書について思うこと
詳細設計書とは何か?ときかれれば下記のようなものを想像する人は多いと思う。
職業PGにわかるFizzBuzz
irofさんの上の記事をみて詳細設計書について思うことをtwitterで垂れ流していたら、
詳細設計書を馬鹿にしている人達に腹立たしさのような何かを感じる。全力で殴りたおすようなブログかきたい。
というツイートがTLに流れてきたので殴られないように
自分の考えをしっかり記事に書こうと思う。
さて、本題。
詳細設計書に対しての言われていそうな批判を思いつくだけ挙げてみた。
自分の意見と参考文献を交えながら一つずつ議論していこう。
”設計書”とは本来ソースコードのことではないのか。
オブジェクト指向言語や関数型言語が普及したことによって、
もはやアプリケーションドメイン(解決したい問題領域)とソリューションドメイン(解決するためのデータ構造とアルゴリズム)の差は縮まり
わざわざ自然言語で設計書を作ることに意味が薄れてきている。
設計を直接、形式言語で表現することで、型による検証などの恩恵を受けることができる。
また近年のソフトウェアの複雑さから言って、設計を自然言語で記述することは非現実的だ。
設計をテストするには製造(ビルド)を行う必要があるが、
製造が非常に低コスト*1だということもソフトウェア工学の特徴の一つだ。
ソースコードを書き上げ、修正し、最終的なプロダクト(プログラム)に反映することは、
他の工業と比べ非常に低コストである。私の仕事でいえばビルド時間は3秒から30秒の間だ。
一息ついてる間に、テスト可能なプロダクトが目の前に現れる。
例えば自動車業界ではそうはいかない。設計を修正し、それを製造ラインに反映することは非常に高コストだ。
たった一つの変更でも巨額の金銭的損失が発生してしまうだろう。
プログラミング言語の高級化、製造の低コスト化が進んでいるソフトウェア工学において、
自然言語で記述された複雑な設計を形式言語に変換する作業はまったくもって無駄なのだ。
1992年にC++Journalで発表された What is Software Design で同じようなことが書かれている。
フローチャートを日本語化しただけにすぎない。
この意見は真っ当である。
処理のシーケンスを定義するのに自然言語は向いておらず(詳細というなら尚更のこと)
それより形式言語で定義しようとするのは当然のことだ。
UMLのアクティビティ図についての批判もこれに由来する。
アジャイルではドキュメントなんて一切不要だぜ ワイルドだろぉ?
これについては論外である。このような都市伝説は本当に実在するのだろうか。
アジャイルマニフェストをみても”包括的なドキュメントよりも動くソフトウェアを、”
とあるとおり、順序関係を定義しただけで、ドキュメントの重要性を無視する記述はない。
そして「ドキュメントは一切不要である」という論理はアジャイルのアンチパターンとしてよく語られる。
エクセルホーガン氏ェ・・・。
エクセルホーガン氏ェ・・・。
*1:ここでいうコストは、時間的コスト、人件費を含む金銭的コストのこと
ADO.NET EFで簡単にSQLをトレースする方法
Linq to SQLでは DataContext.Log というSQLを簡単にトレースできるプロパティが用意されていた。
だが、ADO.NET EFではそのようなプロパティはなく、
もっとも率直にSQLをトレースするならば、個々のクエリに対してObjectQuery.ToTraceStringを呼び出す必要があった。
そうじゃなくてLinq to SQLのようにコンテキストに対してすべてのSQLをトレースできるようにしよう。
デフォルトではそのような機能は提供されていない。
まずは、Entity Framework Tracing Providerをnugetでインストールしよう。
https://nuget.org/packages/CommunityEFProviderWrappers.EFTracingProvider/1.0.0
次に下記のようなコードをF#で用意しておく。
module EFTracingopen System
open System.Data.Objects
open System.Data.EntityClient
open EFProviderWrapperToolkit
open EFTracingProvider
[
]
let createTracingConnection (connectionString : string, tracer : Action) =
EFTracingProvider.EFTracingProviderFactory.Register() |> ignore
let conn =
EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(connectionString, "EFTracingProvider")let tracingConnection = conn.UnwrapConnection
()
tracingConnection.CommandExecuting.AddHandler (fun sender e -> e.ToTraceString () |> tracer.Invoke)
conn
そして下記のように使えば簡単にSQLをトレースすることができる。
// 第二引数にSQLを受け取るActionを渡す。
var conn = EFTracing.CreateTracingConnection("name=SampleEntities", Console.WriteLine);using (var ctx = new SampleEntities(conn))
{
ctx.AddToProducts(new Products() { Name = "Microsoft Surface"});
ctx.SaveChanges();
}
結果は、このようにConsoleにSQLが出力されていることがわかる。