F#でプロセス間通信
MailboxProcessorというクラスを使ってやると、スレッド間で簡単にメッセージのやり取りをすることができます。
でもこれがプロセス間となると一工夫が必要。F#側には用意されていないので、BCLの力を借りることになります。
しかし.NETに用意されたプロセス間通信のためのクラスは、F#のMailboxProcessorとはかなり毛色が異なりますし、使い方も少々面倒。というわけで、これらの仕組みをラップしてF#色に染め直してみました。
MailboxProcessorに比べると機能は少ないですが、ほとんど同じようにして利用することができます。
ちなみに、.NET(CLR)にはアプリケーションドメインという概念があるため、「プロセス間通信」というよりは、「アプリケーション間通信」と言ってやった方が適切かもしれません。
結構長いのでソースは一番最後に載せます。まずはこのクラスの利用方法から。
利用例その1
サーバアプリケーション:
open System open Imuziok.Actor let Sample1 () = let server = MailServer<string>.Start (fun box -> async { let! msg = box.Receive () Console.WriteLine msg box.Post "さようなら" Console.WriteLine "サーバおしまい" }, name = "ServerName", max = 1) Console.ReadLine () |> ignore // 待機 server.Close () [<STAThreadAttribute>] do Sample1 ()
クライアントアプリケーション:
open System open Imuziok.Actor let Sample1 () = let client = MailClient<string>.Start (fun box -> async { box.Post "おはようございます" let! msg = box.Receive () Console.WriteLine ("{0} \n クライアントおしまい", msg) }, name = "ServerName") Console.ReadLine () |> ignore client.Close () [<STAThreadAttribute>] do Sample1 ()
最も単純な例です。
ひとつのサーバに対してひとつのクライアントが接続しています。
サーバに名前をつけてやる必要があります。ここでは"ServerName"という命名をしてあります。ネーミングセンスが光っています。
Startメソッドを呼びだすと非同期に処理が走り始めます。
利用例その2
今度は、ひとつのサーバで複数のクライアントを処理するサンプルです。
サーバアプリケーション:
open System open Imuziok.Actor let Sample2 n = for i = 1 to n do MailServer<string>.Start (fun box -> async { let! msg = box.Receive () Console.WriteLine ("[{0}] : {1}", i, msg) }, name = "unko", max = n) |> ignore Console.WriteLine "サーバすたーと" Console.ReadLine () |> ignore [<STAThreadAttribute>] do Sample2 3
クライアントアプリケーション:
open System open Imuziok.Actor let Sample2 () = Console.Write ">" let msg = Console.ReadLine () use client = MailClient<string>.Start (fun box -> async { box.Post msg }, name = "unko") Console.ReadLine () |> ignore [<STAThreadAttribute>] do Sample2 ()
サーバ側のプログラムでは、max = 3で処理を開始しています。
最大何個のクライアントを処理するのかをここで決めます。ここでは試しに最大3個です。数選びのセンスが光っています。
実際にクライアントを3個以上立ち上げて試してみてください。やっぱり試さなくていいです。
利用例その3
冒頭でアプリケーションドメインの話をしたので、そのサンプルです。
ひとつのプロセス(メモリ空間)を複数の部屋(アプリケーションドメイン)に区分けして、それぞれの部屋で1つずつアプリケーションを実行します。プロセス的には1つですが、複数のアプリケーションが共存しています。アプリケーションドメイン間の通信でも問題ありません。
サーバアプリケーション:
open System open Imuziok.Actor let SetServers n = for i = 1 to n do Console.WriteLine ("start {0}", i) MailServer<string>.Start (fun box -> async { let! msg = box.Receive () Console.WriteLine ("{0} : {1}", i, msg) }, name = "十二指腸", max = n) |> ignore let SetClients n = let f name = (AppDomain.CreateDomain name).ExecuteAssembly "Client.exe" |> ignore for i = 1 to n do async { f ("room" + i.ToString ()) } |> Async.Start [<STAThreadAttribute>] do SetServers 5 SetClients 5 Console.ReadLine () |> ignore
クライアントアプリケーション:
open System open Imuziok.Actor let Sample3 () = let r = new Random () let client = MailClient<string>.Start (fun box -> async { System.Threading.Thread.Sleep (r.Next 350) box.Post ("hello! @" + AppDomain.CurrentDomain.FriendlyName) }, name = "十二指腸") () [<STAThreadAttribute>] do Sample3 ()
サーバのデフォルト・アプリケーションドメインの他に5つ部屋を追加して、その中でクライアントを実行します。
各クライアントはランダムに待って、サーバにメッセージをポストします。それをサーバ側でまとめて表示。
以下は実行例。
start 1
start 2
start 3
start 4
start 5
1 : hello! @room1
4 : hello! @room3
2 : hello! @room2
3 : hello! @room4
5 : hello! @room5続行するには何かキーを押してください . . .
というわけで例をいくつか挙げてみました。
つくりは完璧ではないので、実用したい方は適当に改造して使ってください。
では以下がソースです。バグでもあったら教えてください。教えなくてもいいです。(!)
再帰理論
こちらもただのお遊びです。
今回はHaskellを選びました。
-- 後者関数は原始再帰関数 suc = (1 +) -- 射影関数は原始再帰関数 u1_1 x = x u3_1 (x, _, _) = x u3_2 (_, x, _) = x u2_1 (x, _) = x u2_2 (_, x) = x -- 加算関数 x +. 0 = u1_1 x x +. (y + 1) = suc $ u3_2 (x, (x +. y), y) -- zeroも原始再帰関数 zero x = 0 -- 乗算関数 x *. 0 = zero x x *. (y + 1) = u3_2 (x, (x *. y), y) +. u3_1 (x, (x *. y), y) -- 階乗関数 fac 0 = 1 fac (x + 1) = f (x, fac x) where f arg = (suc $ u2_1 arg) *. u2_2 arg -- 前者関数 pd 0 = 0 pd (x + 1) = x -- 減算関数 x -. 0 = x x -. (y + 1) = pd (x -. y) -- 最小、最大、絶対値 min' (x, y) = x -. (x -. y) max' (x, y) = (x +. y) -. min' (x, y) abs' (x, y) = (x -. y) +. (y -. x) main = do print $ 4 +. 6 print $ 16 *. 16 print $ fac 5 print $ 10 -. 5 print "-----" print $ min' (3, 4) print $ max' (3, 4) print $ abs' (3, 4)
10Haskell触ったのいつぶりだろう!
256
120
5
"-----"
3
4
1
n + k パターンを初めて有効活用した気がします。
ラムダがあれば何でもできる!元気d(ry
ただ(型なし)ラムダ計算を用いて形式的に記述してゆくだけの遊びです。
言語はSchemeを選択しました。(途中までPythonを使っていたのは秘密)
(define true (lambda (x y) x)) (define false (lambda (x y) y)) (define if_ (lambda (p x y) (p x y))) ;; マクロで遅延評価。(マクロは式の変換だから使ってもOK?) (define-syntax if. (syntax-rules () ((if. p x y) ((if_ p (lambda () x) (lambda () y))) ))) (define and. (lambda (p q) (if. p q false))) (define or. (lambda (p q) (if. p true q))) (define not. (lambda (p) (if. p false true))) ;; 部分適用できないので、手動でカリー化 (define pair (lambda (x y) (lambda (f) (f x y)) )) (define fst (lambda (p) (p true))) (define snd (lambda (p) (p false))) (define nil (lambda (x) x)) (define null fst) (define cons. (lambda (x y) (pair false (pair x y)))) (define hd (lambda (x) (fst (snd x)))) (define tl (lambda (x) (snd (snd x)))) ;; ただのIコンビネータ (define I nil) (define lisp (lambda (f lst end) (if. (null lst) end (cons. (f (hd lst)) (lisp f (tl lst) end)) ))) (define append. (lambda (x y) (lisp I x y))) (define map. (lambda (f lst) (lisp f lst nil))) (define fold-left (lambda (f e lst) (if. (null lst) e (fold-left f (f e (hd lst)) (tl lst)) ))) ;; ネストしたリストには対応してない (define (print-list x) (if. (null x) (display "nil") (begin (display (hd x)) (display " ") (print-list (tl x)) ))) ;; 本当はチャーチ数を使うべきだけど割愛! (define (list*2 x) (map. (lambda (x) (* 2 x)) x) ) (define (length. x) (fold-left + 0 (map. (lambda (_) 1) x)) ) ;; tests (define a (cons. 1 (cons. 2 nil))) (define b (cons. 3 (cons. 4 nil))) (define c (append. a b)) (define d (list*2 c)) (define e (length. c))
> (print-list c)
1 2 3 4 nil
> (print-list d)
2 4 6 8 nil
> e
4
match!
すごく久しぶりにSchemeを触っています。相変わらずとても心地良いのよい言語です。
Schemeにはパターンマッチライブラリとかないのかな〜と思って探していると、
「Andrew Wright match」なるものがあることを知りました。matchに待ったパターンマッチ!
それと、気がつくとIronSchemeがRCになっていました。とうとうここまで来たか。
というわけで処理系はIronScheme、開発環境はVisual Studio 2008を使いました。
(import (rnrs) (match) (srfi :26)) ;; Pat ::= (Pat ... . Pat) ;; || (Pat . Pat) ;; || () ;; || #(Pat* Pat ... Pat*) ;; || #(Pat*) ;; || ,Id ;; || ,(Id*) ;; || ,(Cata -> Id*) ;; || Id (define (test1 lst) (match lst (() 0) ((,x ,y) (+ x y)) ((,x ,y ,z) (* x y z)) )) (define (my-map f lst) (match lst (() '()) ((,x . ,xs) (cons (f x) (my-map f xs))) )) (display (test1 '(8 2))) (newline) (display (test1 '(2 4 5))) (newline) (display (my-map (cut expt 2 <>) '(0 1 2 3 4 5)))
10変数にいちいち鼻くそ(!)みたいなの付けなきゃいけないのがやや不満ですが、無事動いてmatch売りの少女サチコもご満悦。
40
(1 2 4 8 16 32)
F#でDuck Typing
F#にはコンパイル時にシグネチャをチェックする仕組みがあるので、
静的型付け言語であるにもかかわらずDuck Typingができてしまいます。C++のような感じ。
type Taro() = member self.Speak () = "こんにちは!太郎です!" member inline self.Calc (x, y) = x + y type Hanako() = member self.Speak () = "わたしは花子だ。" // Type Extention ※1 type Hanako with member inline self.Calc (x, y) = x - y // 静的に解決される型パラメータ(Statically Resolved Type Parameters) // を使って制約を加えてみる let inline Check (x : ^a when ^a : (member Speak : unit -> string) and ^a : (member Calc : 'm * 'n -> 'o)) = x // Member Constraint Invocation Expressions let inline Speak x = (^a : (member Speak : unit -> string) (Check x)) // 複数の値はタプルにして渡すらしい ※2 let inline Calc x y z = (^a : (member Calc : 'm -> 'n -> 'o) (Check x), y, z) let inline SpeakAndCalc (person : ^a) x y = printfn "%s" <| Speak person Calc person x y let Test () = let taro = SpeakAndCalc <| Taro() let hanako = SpeakAndCalc <| Hanako() taro "hello," "world" |> printfn "%s" hanako 123 23 |> printfn "%d"
> Test () ;;Calcメソッドはそれぞれinlineを指定することによって、ここでもさりげなくStatically Resolved Type Parametersの恩恵を受けています。型推論によって総称型と解決されます。
こんにちは!太郎です!
hello,world
わたしは花子だ。
100
val it : unit = ()
Taro.Calc の型は
member Calc : x: ^a * y: ^b -> ^cとなってます。
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
(※1)のように、あとから型を拡張したものであっても問題ないみたいです。
(※2)のところでは、型を Calc : 'm -> 'n -> 'o と制限していますが、
このように制限をかけても実際は 'm * 'n -> 'o の関数しか許可してくれません。(なんで?)
ですのでTaro, Hanako のCalcメソッドはタプルを使っています。
こうすることにより、共通のインタフェースがなくとも同じアヒルとして扱うことができます。コンパイル時チェックとはいうものの、シグネチャに違いがあればコードを書いてるそばから知らせてくれます。(VisualStudioすばらしい)
C#だとジェネリックメソッド中で、「'T型の値と'U型の値を足したい・・・!」ということができませんでしたが、F#だとinlineを指定するだけで一発解決。
でもMSDNにも注意書きがあるように、下手なinline展開はコンパイラの最適化を邪魔する恐れがあるので、利用はやや慎重に。
C#とIronPythonの連係がすごいことに
つい先日(Apr.12 2010)IronPythonの新しいバージョンがリリースされました。
言語仕様自体は変わっていませんが(2.6.1)、.NET 4.0向け機能が完成したようです。
β版の頃から注目していましたが、C#4.0(dynamic)と非常に相性がいい!
早速プログラムを見ていただきましょう。
Script.py
def hello( x ) : print "hello, " + x class Foo() : def __init__( self ) : print "init !" def __call__( self ) : print "call !" def method( self ) : return self.x * self.y def getMetaClass() : return type( 'Bar', (), {'expt' : lambda self, x : 2 ** x} )
C#
以下のDLLの参照をプロジェクトに追加します
- IronPython
- IronPython.Modules
- Microsoft.Dynamic
- Microsoft.Scripting
using System; using System.Collections.Generic; using System.Linq; using System.Text; using IronPython.Hosting; using Microsoft.Scripting.Hosting; namespace DynamicIPy { class Program { static void Main(string[] args) { var python = Python.CreateEngine(); // その場で式を実行。あまり面白くない例。 int result1024 = python.Execute<int>( "2 ** 10" ); Console.WriteLine( result1024 ); Console.WriteLine(); // ファイルを実行。helloメソッドを呼ぶ。まだ面白くない。 dynamic global = python.ExecuteFile( "Script.py" ); global.hello( "world" ); Console.WriteLine(); // Python側のクラスのインスタンスを作ってみる。 dynamic foo = global.Foo(); foo(); // __call__ foo.x = 2; // dynamicだからこんなことができる! foo.y = 10; int result20 = foo.method(); Console.WriteLine( result20 ); Console.WriteLine(); // Python側のメタクラスをこっちで使ってみる dynamic Bar = global.getMetaClass(); dynamic bar = Bar(); // Barクラスのインスタンスを作る! Console.WriteLine( bar.expt( 8 ) ); } } }
1024hello, world
init !
call !
20256
続行するには何かキーを押してください . . .
やっぱり実行速度は遅いですが、大変面白いですね。
IronPythonもっと注目されてもいいんじゃないかなぁ。