2019年こそAkka.NET + F#の最強(当社比)の組み合わせがどうにか流行ってほしい
はじめに
あけましておめでとうございました
1月・2月は弊社にとっては2018年の13月と14月みたいなものだったので、実質3月から2019年がスタートした感じです。
2019年も相変わらず不遇な扱いのF#と日本での知名度はもやはゼロに等しいAkka.NETですが、個人的には大好きな技術スタックなので勘を取り戻すために復習してみました。
参考文献
www.fsharpreactivepatterns.com
ベース
ますは最も単純な、アクターにレコードを投げつけるだけのサンプルです。
アクターの定義がいまいちよくわからない定義方法になってますが、弊社もよくわかっていません。
open System open Akka open Akka.FSharp (* アクターシステムを生成 *) let system = System.create "fsharp" <| Configuration.load () (* アクター間でやり取りを行うメッセージを定義 *) type Value = | Value of value: int (* メッセージを受け取ってコンソールに出力するアクターの定義 *) let printToConsole (mailbox: Actor<_>) = let rec loop () = actor { let! Value(value) = mailbox.Receive () printfn "Value is %A" value return! loop () } loop () [<EntryPoint>] let main argv = (* アクターの生成 *) let processorRef = spawn system "printToConsole" printToConsole (* アクターにメッセージを送信 *) processorRef <! Value(42) (* アクターシステムを停止 *) system.Terminate().Wait() 0
C#でAkkaを使うとき一番イマイチなのが、アクター間のメッセージを定義するときなんですよね。
アクター間でやり取りされるメッセージはスレッドセーフじゃないといけないからクラスを定義するときに不変性に気を配らないとならないってのが面倒なんですよね。 だからと言って適当に定義してのちのち地雷を踏むのも嫌だし。
F#ならレコードなり判別共用体で不変なデータ構造を一撃で定義できるし、メッセージの値のパターンマッチや値の束縛を走らせるのもお家芸なのでAkkaはF#の方が書きやすいよねって思います。
フィルタリング
メッセージを消費するアクターの前段にメッセージをフィルタするアクターを配置します。 ここでは生命、宇宙、そして万物についての究極の疑問の答えのみを通すフィルターを定義して配置します。
open System open Akka open Akka.FSharp open Akka.Actor (* アクターシステムを生成 *) let system = System.create "fsharp" <| Configuration.load () (* アクター間でやり取りを行うメッセージを定義 *) type Value = | Value of value: int (* メッセージを受け取ってコンソールに出力するアクターの定義 *) let printToConsole (mailbox: Actor<_>) = let rec loop () = actor { let! Value(value) = mailbox.Receive () printfn "Value is %A" value return! loop () } loop () (* 述語を満たすメッセージのみを次のアクターに渡すアクターの定義 *) let filter (predicate: 'a -> bool) (next: IActorRef) (mailbox: Actor<_>) = let rec loop () = actor { let! message = mailbox.Receive () if predicate(message) then next <! message return! loop () } loop () [<EntryPoint>] let main argv = (* アクターの生成 *) let printToConsole = spawn system "printToConsole" printToConsole let pred42 (value: Value) = match value with | Value(42) -> true | _ -> false let filter42 = spawn system "filter42" (filter <| pred42 <| printToConsole) (* アクターにメッセージを送信 *) filter42 <! Value(42) filter42 <! Value(43) (* アクターシステムを停止 *) system.Terminate().Wait() 0
やっていることは単純で、あらかじめ述語を受け取り述語を満たすメッセージのみを後段に流すアクターを定義しているだけです。
変換
open System open Akka open Akka.FSharp open Akka.Actor (* アクターシステムを生成 *) let system = System.create "fsharp" <| Configuration.load () (* アクター間でやり取りを行うメッセージを定義 *) type Value = | Value of value: int (* メッセージを受け取ってコンソールに出力するアクターの定義 *) let printToConsole (mailbox: Actor<_>) = let rec loop () = actor { let! Value(value) = mailbox.Receive () printfn "Value is %A" value return! loop () } loop () (* 述語を満たすメッセージのみを次のアクターに渡すアクターの定義 *) let filter (predicate: 'a -> bool) (next: IActorRef) (mailbox: Actor<_>) = let rec loop () = actor { let! message = mailbox.Receive () if predicate(message) then next <! message return! loop () } loop () (* 指定されたコンバータを適用して次のアクターに渡すアクターの定義 *) let convert (converter: 'a -> 'a) (next: IActorRef) (mailbox: Actor<_>) = let rec loop () = actor { let! message = mailbox.Receive () next <! (converter <| message) return! loop () } loop () [<EntryPoint>] let main argv = (* アクターの生成 *) let printToConsole = spawn system "printToConsole" printToConsole let twice (value: Value) = match value with | Value(v) -> Value(v * 2) let converter = spawn system "converter" (convert <| twice <| printToConsole) let pred42 (value: Value) = match value with | Value(42) -> true | _ -> false let filter42 = spawn system "filter42" (filter <| pred42 <| converter) (* アクターにメッセージを送信 *) filter42 <! Value(42) filter42 <! Value(43) (* アクターシステムを停止 *) system.Terminate().Wait() 0
おわり