Closed tjaskula closed 4 years ago
Absolutely, that would be interesting :) I'm not very experienced with F# yet, so I can't really give any opinions right now design-wise, except that I think it should be built as an extension library and shouldn't introduce anything special in the core libraries.
Agree with @cpx86 . The main issue I've seen so far with F# and Proto.Actor is the lack of decent ProtoBuf support in F#. You can use Protobuf libs with mutable types in F#, but it seems to be an issue to map F# special types such as records or DU's to protobuf.
If that is possible using Froto, or possible some other way, that would be awesome
Thanks for your feedback. Definietly, I take a note on extension library, that would be an add-on to what already exists and on serialization with protobuf.
I'll try to spike something and keep you posted.
I've been doing some investigation testing different approaches for protobuf serialization and F#. Unfortunately there are no libraries that support DU's serialization at all and speaking to the community there are no plans to support it. Unless some unexpected contribution which for now is unlikely to happen. Froto isn't maintained since months and it doesn't support DUs either. The work to add them since huge.
I've managed to serialize and deserialize simple DU with Mark Gravell library protobuf-net
annotating F# types with attributes and with some hacky code, but I'm not sure what are the limits with this approach I can run into in the feature. Without speaking about supporting .proto files and code generation.
So I'm not sure, where to go from that. Would be a protobuf-net
instead Google.protobuf
library be a valid choice for F# Api? Of course with a limited support to the attribute annotations.
Hi, I've tried Proto.Actor for my pet f# project and experimented with some more idiomatic API. Now I have something like this:
let helloWorldActor =
actorOf ( fun ctx->
ctx.Message
>>| fun(msg:Started) -> printfn "Hello World!"
) |> spawn
It's almost the same as Actor.FromFunc(..)
but with infix operator (>>|)
in actor body. It just help to match message to specific type.
More complex example:
type HttpAction =
| GET of string
| POST of uri:string*content:string
type HttpActionResult =
| Ok of uri:string
| Error of uri:string*error:string
let doRequest (pid:PID) action =
async {
let client = new HttpClient()
let (task, uri) =
match action with
| GET uri -> (client.GetAsync(uri), uri)
| POST (uri, content) ->
let postBody = new FormUrlEncodedContent(dict["key", content])
(client.PostAsync(uri, postBody), uri)
printfn "about starting download %s" uri
try
let! result = task |> Async.AwaitTask
pid <! Ok(uri)
with
| exn as ex -> pid <! Error(uri, ex.ToString())
return ()
}
let httpActor =
actor {
receive (fun ctx ->
ctx.Message
>>| fun (msg:HttpAction) ->
msg |> doRequest ctx.Sender |> Async.RunSynchronously
)
}
let urls = ["https://github.com/";"https://docs.microsoft.com";"https://www.tut.by"]
let mutable downloadActors = Map.empty
let mainActor =
actor {
receive (
fun ctx ->
ctx.Message
>>= (fun (_:Started) ->
for url in urls do
let child = httpActor |> spawnFromContext ctx
downloadActors <- downloadActors |> Map.add url child
child <? (GET(url), ctx.Self)
)
>>| (fun (msg:HttpActionResult)->
match msg with
| Ok uri ->
printfn "ok %s" uri
downloadActors.[uri] <! Stop.Instance
| Error (uri, error) -> printfn "error %s %s" error uri
)
)
} |> spawnNamed "main"
Here I created mainActor
for making some http requests. There is some computation expression actor{..}
instead of function actorOf
. It's almost the same. Here we have one more infix operator (>>=)
for matching message to specific type, but with piping message further as result of function. It helps compose logic with a lot of branches. Also we have (<!)
Tell-operator and (<?)
Request-operator. The same as in FSharpAPI for Akka.net
. Also there is (<??)
RequestAsync-operator.
Configurations in computation expression:
let helloWorldActor =
actor {
//takes IContext->unit function
receive (fun ctx->
ctx.Message
>>| fun(msg:Started) -> printfn "Hello World!")
//takes unit->IMailbox function
mailbox (fun()->Mailbox.UnboundedMailbox.Create())
//takes IDispatcher
dispatcher Mailbox.Dispatchers.DefaultDispatcher
//takes ISupervisorStrategy
supervisorStrategy Supervision.DefaultStrategy
//takes list of IContext->unit functions
receiveMiddlewares [
fun ctx-> printfn "middleware 1";
fun ctx-> printfn "middleware 2"
]
//takes list of ISenderContext->PID->MessageEnvelope->unit functions
sendMiddlewares [
fun ctx pid envelope -> printfn "sender middleware 1";
fun ctx pid envelope -> printfn "sender middleware 2"
]
}
Here we make some basic configuration, including middlewares. Also there are additional functions for props configuration such as let withDispatcher d (props:Props) = props.WithDispatcher d
. They are copy computation expression members, but good for using with (|>)
-operator.
The most tricky part was behaviors. To have possibilities switching between behaviors I made them as mutually recursive functions.
Here is example:
let getBehaviors (behavior:BehaviorSwitcher) =
let rec on (ctx:IContext) =
ctx.Message
>>| (fun (msg:BulbMessage)->
match msg with
| On-> printfn "It's already turned on!"
| Off->
printfn "Turning it off."
behavior.become off
)
and off (ctx:IContext) =
ctx.Message
>>| (fun (msg:BulbMessage)->
match msg with
| On->
printfn "Turning it on."
behavior.become on
| Off-> printfn "It's already turned off!"
)
[off;on]
let bulbActor =
actor {
behaviors getBehaviors
receive (fun ctx ->
ctx.Message
>>= fun (_:Started) -> printfn "Bulb actor started. State is off!"
>>| fun (msg:BulbMessage) ->
match msg with
| On -> printfn "Turned on!"
| Off -> printfn "Turned off!"
)
} |> spawn
So in actor-computation we need to call behaviors function which is takes BehaviorSwitcher->MessagesHandler list
. MessagesHandler
is simple IContext->unit
. BehaviorSwitcher
is record with basic behavior operations:
type BehaviorSwitcher = {
become:(IContext->unit)->unit
becomeStacked:(IContext->unit)->unit
unbecomeStacked:unit->unit
}
So, to setup behaviors we need a function which takes BehaviorSwitcher
and returns list of handlers, which are know each other as they are mutually recursive functions.
Also there is equivalent function for creating actor with behaviors
let behaviorActor = behaviorOf (fun (behavior:BehaviorSwitcher) ->
let rec on (ctx:IContext) =
ctx.Message
>>| (fun (msg:BulbMessage)->
match msg with
| On-> printfn "It's already turned on!"
| Off->
printfn "Turning it off."
behavior.become off
)
and off (ctx:IContext) =
ctx.Message
>>| (fun (msg:BulbMessage)->
match msg with
| On->
printfn "Turning it on."
behavior.become on
| Off-> printfn "It's already turned off!"
)
[off;on]
)
I didn't add spawner configuration, because I'm not sure how useful it is (I'm still investigating this framework). Also there are no builders and helpers for persistence and remoting, because I didn't touch it yet. So what do you think? If you are interested in it then I can make PR.
@p69 Nice, but does this play with protobuf and DU/records?
I would be really interested in this and would test the F# Api if help is needed. Im not as firm with F# to implement it myself at the moment.
@p69 Could you publish logic you wrote for others to play around with?
Recently I've found interesting project: https://github.com/mastoj/protoactor-fsharp.git
As mentioned above. I put together my first stab at an F# api a while back. Didn’t read through this thread before I implemented it.
Regarding the protobuf issue I don’t see that as problem that should be solved in the F# api, that is a problem that I think we should keep outside the api imo. Internally, when running on the same machine it seems like DUs works just fine.
I would be nice if there were a good protobuf serializer that supported DUs for 100% F# scenarios, but as soon as you interact with any other language I think the DUs should be avoided.
Regarding serialization, we don't need to be sticklers for using Protobuf all the way. It's a good default since it's both performant and platform-agnostic, but if you don't intend to run a polyglot solution, then plugging in a native serializer (e.g Wire for .NET) is perfectly fine. For building F#-only solutions, perhaps a FsPickler plugin could be a good solution?
Outdated, F# API will have to live in Contrib
Fair enough
On Sun, 6 Sep 2020 at 11:41, Roger Johansson notifications@github.com wrote:
Outdated, F# API will have to live in Contrib
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/AsynkronIT/protoactor-dotnet/issues/217#issuecomment-687740490, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAD2NMABDGOVKFDHOQ7U77DSENKLJANCNFSM4DKGNL2A .
Hi, I wonder if contributing to an F# API is something you might be interested in ? Before starting anything I wanted to have a feedback.