読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

F#で言語指向プログラミング(LOP)のアプローチ。Seqを扱うための簡単なワークフローを作って、ワークフローを味見してみよう。

プログラミング F# LINQ

F#では「ワークフロー」という機能を備えることで、言語指向プログラミング*1のアプローチがし易くなっている。
これは開発者にメタプログラミング、DSLといった柔軟性を備えたライブラリを作成する機会を、
より多く与えてくれるのではないだろうか。この機能が言語に標準的に備わっている意義はとても大きいと思う。
前回のエントリで書いた「F#でStateモナドしてみよう。そうですよね、副作用は怖いですものね。」も
ワークフローの機能を利用したひとつの例で、ワークフローの面白さと可能性を垣間見ることができた。


Seqを扱うための簡単なワークフローを作って、ワークフローを味見してみよう

たとえば、以下のようなSeqを扱うための、ささやかなワークフローと補助的な関数を作ってみる。

#light
namespace Library1
open System
open System.Linq 

///SeqBuilder
type SeqBuilder() = 
 ///LINQで言うところのEnumerable.SelectManyします。
 member this.Bind (ies, f) = Seq.map_concat f ies
 ///1要素のみのseqを作ります
 member this.Return x = Seq.singleton x 
  
///Seqの操作をいろいろ提供します。
module SeqLibrary =
 //SeqBuilderを生成します。
 let seq = SeqBuilder () 
 //いわゆるguard関数
 let guard = function  
  | true -> Seq.singleton () 
  | _ -> Seq.empty

 ///LINQSelectを再定義
 let select  f source = Enumerable.Select(source,new Func<_,_>(f))
 ///LINQWhereを再定義
 let where   f source = Enumerable.Where(source,new Func<_,_>(f))
 ///LINQCountを再定義
 let count   f source = Enumerable.Count(source,new Func<_,_>(f))
 ///LINQMaxを再定義
 let max (source:seq<'a>) = Enumerable.Max<'a>(source)


SeqBuilderのBindは、let!(let bang)およびdo!(do bang)計算の式に対して呼び出され、
SeqBuilderのReturnは、return計算の式に対して呼び出されます。
ということを踏まえて、作成したワークフローを利用した下記のコードを見てみましょう。


Seqを扱うためのワークフローと補助的な関数を利用してみる

#light 
open System
open System.Linq 
open Library1.SeqLibrary

let panyn x = x |> (print_any >> Console.WriteLine)

//Seqの関数を使った場合
let _ = 
 seq {
      let! x = 
       let option x = 
          match x > 0 || x <= 0 with
           | true  -> Some x
           | false -> None
       {1..10}                                 //from
       |> Seq.filter (fun a -> a > 4)          //where
       |> Seq.filter (fun a -> a < 8)          //where
       |> Seq.choose (fun a -> option (a * a)) //select
      return x
     }|> Seq.fold (+) 0 |> panyn 

//System.Linqをそのまま使った場合
let _ = 
 seq {let! x = 
       let f  = {1..10}                                   //from
       let w1 = Enumerable.Where (f, (fun a -> a > 4))    //where
       let w2 = Enumerable.Where (w1, (fun a -> a < 8))   //where
       let s  = Enumerable.Select(w2, (fun a -> a * a))   //select
       s
      return x
     }|> Seq.fold (+) 0 |> panyn 

//System.Linqの関数を再定義して使った場合
let _ = 
 seq {let! x = {1..10}                    //from
               |> where  (fun a -> a > 4) //where
               |> where  (fun a -> a < 8) //where
               |> select (fun a -> a * a) //select
      return x
     }|> Seq.fold (+) 0 |> panyn 

//guard関数を使った場合
let _ = 
 seq {let! a = {1..10}  //from
      do! guard (a > 4) //where 
      do! guard (a < 8) //where 
      return a * a
     }|> Seq.fold (+) 0 |> panyn

let k = Console.ReadKey ()


実行結果

110
110
110
110

独自に作成したワークフローや補助的な関数を用いることで、特定の条件を満たすSeqを生成するためのコードが、非常にシンプルに記述されていることが確認できると思います。
なかでも「System.Linqの関数を再定義して使った場合」と「guard関数を使った場合」については、
とても直感的でわかりやすいコードになっているように思います。
System.Linqの関数と近似な関数がSeqモジュールにも提供はされていますが、
Seq.chooseがoption型を要求する点で異なっていて、この場合若干の扱いにくさを感じました。
また、そのままSystem.Linqを利用した場合、パイプでつないで書くことができないのでF#的にはどうも具合が悪いです。
この例では、再定義したSystem.Linqを使って記述するかguard関数を用いるかは、好みがわかれるところかもしれません。



MSDN - ワークフロー(F#)
http://msdn.microsoft.com/ja-jp/library/dd233182(VS.100).aspx

F# の基本を越えて - ワークフロー
http://www.infoq.com/jp/articles/pickering-fsharp-workflow

新たな関数型言語「F#」
http://codezine.jp/article/detail/4254

*1:LOP:Language Oriented Programming