F#+WCFでRESTfulなサービスをこしらえる・判別共用体で遊ぶ

F# の判別共用体は便利


便利ですよね。判別共用体。

知らない方のためにざっくり説明すると「超自由なenum」でしょうか。
Haskellではvariantって言うらしいですね。

例えば商品の評価とかを考えると

type Score =
     | Num of int
     | Unrated

評価は当然評価スコアを持つのですが、「そもそも評価されていない」という状態も考えられます。
上のScore型は「評価されていない(Unrated)」と「評価されていて、整数の得点(Num)を持つ」という事を表現出来ます。


さあさあWCFでも使用したいよ!

Option 型から始めよう!


最初は言わずと知れたOption型だよ。Some(x)とNoneの二つを取るよ。失敗するかもしれない関数によく使われるよ!

昨日のコードをちょい足しして、

[<ServiceContract>]  
type IAccountService = interface
  [<OperationContract>]  
  [<WebGet(UriTemplate="account")>]  
  abstract GetAllAccounts : unit -> (string * string) []
  
  [<OperationContract>]  
  [<WebGet(UriTemplate="account/{id}")>]  
  abstract GetAccount : id:string -> string

  [<OperationContract>]  
  [<WebGet(UriTemplate="tryaccount/{id}")>]  
  abstract TryGetAccount : id:string -> (string * string option)
end  
  
type AccountService() =
  static let accounts = Map.ofList [("0", "cat"); ("1", "dog")]
  interface IAccountService with  
    member this.GetAllAccounts () =
      accounts |> Map.toArray
    member this.GetAccount id =
      let ac = accounts |> Map.tryFind id
      match ac with
      | Some (x) -> x
      | None     -> raise (WebFaultException(System.Net.HttpStatusCode.NotFound))
    member this.TryGetAccount id =
      (id, accounts |> Map.tryFind id)

こんな感じで(string * string option)を返すオペレーションを追加しました。
これをブラウザからhttp://localhost:8080/FSharpWCF/AccountService/tryaccount/1 にアクセスすると、

{"m_Item1":"1","m_Item2":{"value":"dog"}}
<TupleOfstringFSharpOptionOfstringyfz30nII>
  <m_Item1>1</m_Item1>
  <m_Item2>
    <a:value>dog</a:value>
  </m_Item2>
</TupleOfstringFSharpOptionOfstringyfz30nII>

おお、ちゃんとシリアライズされて返ってきました。
タプルとオプション、それぞれちゃんとシリアライズされてます。
じゃあ、http://localhost:8080/FSharpWCF/AccountService/tryaccount/2 のようにNoneを含むコードの場合はどうなるかというと、

{"m_Item1":"2","m_Item2":null}
<TupleOfstringFSharpOptionOfstringyfz30nII>
  <m_Item1>2</m_Item1>
  <m_Item2 i:nil="true"/>
</TupleOfstringFSharpOptionOfstringyfz30nII>

nullだー!!!!
nullが返ってきました。この時期のnullは尻尾に毒がある、ってばっちゃが言ってました。*1

クライアントでは


昨日作ったクライアントでもこのサービスは使用出来ます。
option型の扱いがどうなるかというと、FSharp.Core.FSharpOption という方となります。
None の時は大方の予想通りnullになります。なんじゃそりゃ。

ほかの判別共用体ではどうなるの?


実用に足りるかは別として、Option型も返り値としてシリアライズされる事を確認しました。
Noneの時は要素全体がnullになるので注意が必要です。
Option型はF#の判別共用体の中でも特殊な型なので、
例えば自分で定義した判別共用体ではどうなるのか気になるところです。
それはまた明日にでも。

*1:ばっちゃはそんな事は言わないしnullに尻尾とかないですけど、nullが時に危険なのは本当です