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)

10
256
120
5
"-----"
3
4
1
Haskell触ったのいつぶりだろう!
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
40
(1 2 4 8 16 32)
変数にいちいち鼻くそ(!)みたいなの付けなきゃいけないのがやや不満ですが、無事動いてmatch売りの少女サチコもご満悦。

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 Extention1
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 () ;;
こんにちは!太郎です!
hello,world
わたしは花子だ。
100
val it : unit = ()
Calcメソッドはそれぞれinlineを指定することによって、ここでもさりげなくStatically Resolved Type Parametersの恩恵を受けています。型推論によって総称型と解決されます。
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の参照をプロジェクトに追加します

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 ) );
		}
	}
}

1024

hello, world

init !
call !
20

256
続行するには何かキーを押してください . . .


やっぱり実行速度は遅いですが、大変面白いですね。
IronPythonもっと注目されてもいいんじゃないかなぁ。


PythonC#も久しぶりに触りました・・・。りはびり。

『F#入門』の資料公開

04月17日(土)にCLR/Hの第47回勉強会が開催されました。
僕は『F#入門 〜関数プログラミングとは何か〜』というセッションを担当しました。
その際の発表資料を公開します。


『F#入門』
SkyDriveにアップロードしてみました(ダウンロードしなくてもpptを表示できるという噂を聞いたので*1)。
アニメーションが色々盛り込まれているので、ぜひダウンロードしてから全画面表示でご覧になってください!

*1:[追記]サインインして、テクニカルプレビュープログラムに参加すると、Silverlightでその場で表示できるみたいです

続きを読む