2019年こそAkka.NET + F#の最強(当社比)の組み合わせがどうにか流行ってほしい

はじめに

あけましておめでとうございました

1月・2月は弊社にとっては2018年の13月と14月みたいなものだったので、実質3月から2019年がスタートした感じです。

2019年も相変わらず不遇な扱いのF#と日本での知名度はもやはゼロに等しいAkka.NETですが、個人的には大好きな技術スタックなので勘を取り戻すために復習してみました。

参考文献

mikhail.io

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

github.com

おわり