I'm trying to gain some experience with Akka.NET actors in F#. I have the scenario when one actor needs to spin up another actor as its child. The first actor would convert each message and then send the result to the other actor. I use actorOf2 function to spawn actors. Here is my code:
let actor1 work1 work2 =
let mutable actor2Ref = null
let imp (mailbox : Actor<'a>) msg =
let result = work1 msg
if actor2Ref = null then
actor2Ref <- spawn mailbox.Context "decide-actor" (actorOf2 <| work2)
actor2Ref <! result
imp
let actor1Ref = actor1 work1' work2'
|> actorOf2
|> spawn system "my-actor"
What I don't like is the mutable actor reference. But I had to make it mutable because I need mailbox.Context to spawn a child actor, and I don't have any context before the first call. I saw this question but I don't know how to apply it to my function.
In a more advanced scenario I need a collection of child actors which is partitioned by a key. I'm using a Dictionary of actor refs in this scenario. Is there a better (more F#-ish) way?
In order to keep your "state" across iterations, you need to make the iterations explicit. That way, you can pass the current "state" as tail call argument. Just as in the question you linked:
let actor1 work1 work2 (mailbox : Actor<'a>) =
let rec imp actor2 =
actor {
let! msg = mailbox.Receive()
let result = work1 msg
let actor2 =
match actor2 with
| Some a -> a // Already spawned on a previous iteration
| None -> spawn mailbox.Context "decide-actor" (actorOf2 <| work2)
actor2 <! result
return! imp (Some actor2)
}
imp None
And now, you don't need to use actorOf2 or actorOf for spawning this actor, because it already has the right signature:
let actor1Ref =
actor1 work1' work2'
|> spawn system "my-actor"
.
EDIT
If you're concerned about the extra boilerplate, nothing prevents you from packing the boilerplate away as a function (after all, actorOf2 does something similar):
let actorOfWithState (f: Actor<'msg> -> 'state -> 'msg -> 'state) (initialState: 'state) mailbox =
let rec imp state =
actor {
let! msg = mailbox.Receive()
let newState = f mailbox state msg
return! imp newState
}
imp initialState
And then:
let actor1 work1 work2 (mailbox : Actor<'a>) actor2 msg =
let result = work1 msg
let actor2 =
match actor2 with
| Some a -> a
| None -> spawn mailbox.Context "decide-actor" (actorOf2 work2)
actor2 <! result
actor2
let actor1Ref =
actor1 work1' work2'
|> actorOfWithState
|> spawn system "my-actor"
You could do something along these lines, and just not store a reference to the child actor at all, because the Context is already doing that for you.
let actor =
let ar = mailbox.Context.Child(actorName)
if ar.IsNobody() then
spawn mailbox.Context actorName handler
else ar
If the Context.Child lookup turns out to be too slow, creating a memoized function that keeps mutability hidden from the other code would be pretty easy to do.
Related
The code I have is --
let mySlaveActor (mailbox: Actor<_>) =
let rec loop() = actor {
let mutable actor_id = -1
let mutable rumor = ""
let! rcv = mailbox.Receive()
let sender = mailbox.Sender()
match rcv with
| MessageType2(idx) ->
actor_id <- idx
| MessageType8(neighbor) ->
if (neighbor <> -1) then
let neighbor_slave = spawn mailbox (sprintf "workerActor%i" neighbor) mySlaveActor
neighbor_slave <! MessageType5(actor_id)
The current object is of a mySlaveActor whose name is workerActor1 and I want to call WorkerActor2 when the match is MessageType8(..). But in the penultimate line, I get an error like --
The value or constructor `mySlaveActor` is not defined
Am I doing something wrong is my code missing something? Thanks for the help.
When I'm working in F# Interactive, I often want to make changes to an event handler. Simply calling the Subscribe or Add or AddHandler functions on an event causes the old event to continue being called, which is rarely the intention.
One solution is to use the IDisposable that it returns, but that requires tracking the IDisposables in your own code, which is cumbersome for exploratory tasks.
I've tried making a Dictionary<IEvent,IDisposable> to call Dispose() when the same event is subscribed to again:
let events = Dictionary<obj, IDisposable>()
let subonce (e:IEvent<'h,'e>) (handler: 'e -> unit) =
if events.ContainsKey e then
events.[e].Dispose()
events.Remove e |> ignore
let d = e.Subscribe handler
events.Add (e,d) |> ignore
let w = Window()
w.Show()
//Running this line in FSI a second time onward should Dispose() the previous subscription
subonce w.MouseUp (fun e -> printfn "%A" <| e.GetPosition(w))
Unfortunately, as it turns out, F# generates a new IEvent instance, so naively using = or obj.Equals doesn't cut it.
> w.MouseUp;;
val it : IEvent<Input.MouseButtonEventHandler,Input.MouseButtonEventArgs> =
<published event> {addHandler = <fun:it#5-70>;
createHandler = <fun:it#5-72>;
removeHandler = <fun:it#5-71>;}
> w.MouseUp;;
val it : IEvent<Input.MouseButtonEventHandler,Input.MouseButtonEventArgs> =
<published event> {addHandler = <fun:it#6-74>; //note that these functions are of a different anonymous instance
createHandler = <fun:it#6-76>;
removeHandler = <fun:it#6-75>;}
Are there any properties or fields I can find within an IEvent that would identify it against other instances of the owner and against different events in that owner?
Not exactly an answer to the question, but I can't think of many other scenarios in which you'd need to identify an event instance, so maybe this is good enough:
type OneHandler<'e> = { mutable h : 'e -> unit }
let onehandler (e:IEvent<'h,'e>) =
let h = { h = fun _ -> () }
e.Subscribe(fun e -> h.h e) |> ignore
h
let w = Window()
let wmouseup = onehandler w.MouseUp
wmouseup.h <- (fun e -> printfn "%A" <| e.GetPosition(w))
This way, by evaluating just the assignment to wmouseup.h, we can change the event handler without having to restart the w or juggle IDisposable or Handler objects.
I've got an F# project that loads some files to an outside subsystem and then uses Table Dependency to wait for some rows to be added to a table as a side effect.
Table Dependency is used in the type below to watch for the db changes. It fires a custom event when a row is added/changed/whatever:
// just using this type for the RecordChangedEvent to marshal the id we want into something
type AccountLoaded() =
let mutable someId = ""
// this property name matches the name of the table column (SomeId)
member this.SomeId
with get () = someId
and set (value) = someId <- value
// AccountLoadWatcher
type AccountLoadWatcher() =
let mutable _tableDependency = null
let event = new Event<_>()
interface IDisposable with
member this.Dispose() =
_tableDependency.Stop()
_tableDependency.Dispose()
// custom event we can send when an account is loaded
[<CLIEvent>]
member this.AccountLoaded = event.Publish
member private this.NotifyAccountLoaded(sender : RecordChangedEventArgs<AccountLoaded>) =
let accountLoaded = sender.Entity
event.Trigger(accountLoaded.SomeId)
member this.Watch() =
_tableDependency <- DbLib.getTableDependency "dbo" "AccountTable"
null
_tableDependency.OnChanged.Add(this.NotifyAccountLoaded)
_tableDependency.Start()
What I want to do is take the above object and just wait for all the rows with ids I care about to be loaded. What I have so far is:
let waitForRows(csvFileRows) =
let idsToWaitFor = parseUniqueIdsFromAllRows csvFileRows
let mutable collected = Set.empty
let isInSet id = Set.contains id idsToWaitFor
let notDone = not <| (Set.difference idsToWaitFor collected = Set.empty)
let accountLoadedHandler id =
collected <- collected.Add id
printfn "Id loaded %s, waiting for %A\n" id (Set.difference idsToWaitFor collected)
loadToSubsystem csvFileRows |> ignore
// wait for all the watcher events; filtering each event object for ids we care about
watcher.AccountLoaded
|> Observable.takeWhile (fun _ -> notDone)
|> Observable.filter (fun e -> isInSet e)
|> Observable.subscribe accountLoadedHandler
|> ignore
doMoreWork()
but that just continues to doMoreWork without waiting for all the events i need above.
Do I need to use a task or async? F# Agents?
Given that you are using Observable.takeWhile in your example, I'm assuming that you are using the FSharp.Control.Reactive wrapper to get access to the full range of reactive combinators.
Your approach has some good ideas, such as using takeWhile to wait until you collect all IDs, but the use of mutation is quite unfortunate - it might not even be safe to do this because of possible race conditions.
A nice alternative is to use one of the various scan function to collect a state as the events happen. You can use Observable.scanInit to start with an empty set and add all IDs; followed by Observable.takeWhile to keep accepting events until you have all the IDs you're waiting for. To actually wait (and block), you can use Observable.wait. Something like this:
let waitForRows(csvFileRows) =
let idsToWaitFor = parseUniqueIdsFromAllRows csvFileRows
let finalCollectedIDs =
watcher.AccountLoaded
|> Observable.scanInit Set.empty (fun collected id -> Set.add id collected)
|> Observable.takeWhile (fun collected -> not (Set.isSubset idsToWaitFor co llected))
|> Observable.wait
printfn "Completed. Final collected IDs are: %A" finalCollectedIDs
Please see last edit.
Apologies for the newbie question. I am trying to implement something in F# using Akka.net. I'm very new to F# and I have only used Akka from Scala. Basically I am trying to implement something that's pretty easy in Scala, namely making an Actor do different things based on the type of message it receives.
My code is below and it's a slight modification of the hello world example lifted from the akka.net website. I believe a first problem with my code is that it does record pattern matching instead of type pattern matching, however I was unable to write a type match one without compilation errors... Any help will be greatly appreciated. Thank you.
open Akka.FSharp
open Actors
open Akka
open Akka.Actor
type Entries = { Entries: List<string>}
let system = ActorSystem.Create "MySystem"
let feedBrowser = spawn system "feedBrowser" <| fun mailbox ->
let rec loop() = actor {
let! msg = mailbox.Receive()
match msg with
| { Entries = entries} -> printf "%A" entries
| _ -> printf "unmatched message %A" msg
return! loop()}
loop()
[<EntryPoint>]
let main argv =
feedBrowser <! "abc" // this should not blow up but it does
system.AwaitTermination()
0
Edit: the error is a runtime one, System.InvalidCastException, unable to cast object of type String to Entries.
Later edit: I got this to work with this change, downcasting to Object:
let feedBrowser = spawn system "feedBrowser" <| fun mailbox ->
let rec loop() = actor {
let! msg = mailbox.Receive()
let msgObj = msg :> Object
match msgObj with
| :? Entries as e -> printfn "matched message %A" e
| _ -> printf "unmatched message %A" msg
return! loop()}
loop()
Now these two lines work correctly
feedBrowser <! "abc"
feedBrowser <! { Entries = ["a"; "b"] }
the first one prints "unmatched message abc" and the second outputs the entries.
Is there a better way of going about this, without the cast? Does akka.net have something specifically for this case?
Thank you.
You should use a Discriminated Union (the Command type in this example). Then you can pattern match its options.
type Entries = { Entries: List<string>}
type Command =
| ListEntries of Entries
| OtherCommand of string
let stack() =
let system = ActorSystem.Create "MySystem"
let feedBrowser = spawn system "feedBrowser" <| fun mailbox ->
let rec loop() = actor {
let! msg = mailbox.Receive()
match msg with
| ListEntries { Entries = entries} -> printf "%A" entries
| OtherCommand s -> printf "%s" s
return! loop() }
loop()
And to send the message you should use:
feedBrowser <! OtherCommand "abc"
feedBrowser <! ListEntries { Entries = ["a"; "b"] }
It's important to say that the send operator has the following signature:
#ICanTell -> obj -> unit
So, if you pass an message with a different type, like a string, it'll raise an exception.
I can't work out why the following code is hanging at the call to GetTotal. I don't seem to be able to debug inside the MailboxProcessor, so it's hard to see what's going on.
module Aggregator
open System
type Message<'T, 'TState> =
| Aggregate of 'T
| GetTotal of AsyncReplyChannel<'TState>
type Aggregator<'T, 'TState>(initialState, f) =
let myAgent = new MailboxProcessor<Message<'T, 'TState>>(fun inbox ->
let rec loop agg =
async {
let! message = inbox.Receive()
match message with
| Aggregate x -> return! loop (f agg x)
| GetTotal replyChannel ->
replyChannel.Reply(agg)
return! loop agg
}
loop initialState
)
member m.Aggregate x = myAgent.Post(Aggregate(x))
member m.GetTotal = myAgent.PostAndReply(fun replyChannel -> GetTotal(replyChannel))
let myAggregator = new Aggregator<int, int>(0, (+))
myAggregator.Aggregate(3)
myAggregator.Aggregate(4)
myAggregator.Aggregate(5)
let totalSoFar = myAggregator.GetTotal
printfn "%d" totalSoFar
Console.ReadLine() |> ignore
It seems to work fine when using an identical MailboxProcessor directly, rather than wrapping in the Aggregator class.
The problem is that you did not start the agent. You can either call Start after you create the agent:
let myAgent = (...)
do myAgent.Start()
Alternatively, you can create the agent using MailboxProcessor<'T>.Start instead of calling the constructor (I usually prefer this option, because it looks more functional):
let myAgent = MailboxProcessor<Message<'T, 'TState>>.Start(fun inbox -> (...) )
I suppose that you couldn't debug the agent, because the code inside agent wasn't actually running. I tried adding printfn "Msg: %A" message right after the call to Receive inside the agent (to print incoming messages for debugging) and I noticed that, after calling Aggregate, no messages were actually received by the agent... (It only blocked after calling GetTotal, which avaits reply)
As a side-note, I would probably turn GetTotal into a method, so you'd call GetTotal(). Properties are re-evaluated each time you access them, so your code does the same thing, but best practices don't recommend using properties that do complex work.
You forgot to start the mailbox:
open System
type Message<'T, 'TState> =
| Aggregate of 'T
| GetTotal of AsyncReplyChannel<'TState>
type Aggregator<'T, 'TState>(initialState, f) =
let myAgent = new MailboxProcessor<Message<'T, 'TState>>(fun inbox ->
let rec loop agg =
async {
let! message = inbox.Receive()
match message with
| Aggregate x -> return! loop (f agg x)
| GetTotal replyChannel ->
replyChannel.Reply(agg)
return! loop agg
}
loop initialState
)
member m.Aggregate x = myAgent.Post(Aggregate(x))
member m.GetTotal = myAgent.PostAndReply(fun replyChannel -> GetTotal(replyChannel))
member m.Start() = myAgent.Start()
let myAggregator = new Aggregator<int, int>(0, (+))
myAggregator.Start()
myAggregator.Aggregate(3)
myAggregator.Aggregate(4)
myAggregator.Aggregate(5)
let totalSoFar = myAggregator.GetTotal
printfn "%d" totalSoFar
Console.ReadLine() |> ignore