When calling a HTTP API or doing other long-running tasks, one might need to support cancellation. This could be explicit using a "Cancel" button, or implicit, e.g. cancelling any ongoing requests when signing out of the app.
Is there a recommended pattern for cancellation in Fabulous? Should CancellationTokenSources for any unique cancellable request be stored in the model and replaced when cancelled, or are there better ways? (And would that cause issues if serializing/deserializing the whole model, including CancellationTokenSources?)
@TimLariviere's debounce code in ElmishContacts/ElmishExtensions.fs is a nice example of using CancellationTokenSource in a Fabulous app:
```f#
namespace ElmishContacts
open Fabulous.Core
open System.Collections.Generic
open System.Threading
module Cmd =
let ofAsyncMsgOption (p: Async<'msg option>) : Cmd<'msg> =
[ fun dispatch -> async { let! msg = p in match msg with None -> () | Some msg -> dispatch msg } |> Async.StartImmediate ]
module Extensions =
let debounce<'T> =
let memoizations = Dictionary
fun (timeout: int) (fn: 'T -> unit) value ->
let key = fn.GetType()
// Cancel previous debouncer
match memoizations.TryGetValue(key) with
| true, cts -> cts.Cancel()
| _ -> ()
// Create a new cancellation token and memoize it
let cts = new CancellationTokenSource()
memoizations.[key] <- cts
// Start a new debouncer
(async {
try
// Wait timeout to see if another event will cancel this one
do! Async.Sleep timeout
// If still not cancelled, then proceed to invoke the callback and discard the unused token
memoizations.Remove(key) |> ignore
fn value
with
| _ -> ()
})
|> (fun task -> Async.StartImmediate(task, cts.Token))
let debounce250<'T> = debounce<'T> 250
```
Thanks. That method seems to work fine for a debounced data stream, but I'm having a hard time transferring that to e.g. cancellation of API requests, where the cancellation is not controlled from inside a data stream, but from outside - for example, by the user.
In more concrete terms: The code above works because the debounce wrapper knows that there can be no simultaneous invocations of any single function/lambda through the debounce wrapper. Thus the debounce wrapper itself can cancel any previous invocation. Furthermore, this is the only time an invocation should be cancelled. This is AFAIK not generalizable to cases where an invocation (e.g. of a HTTP request) may be cancelled at any time by the user or other parts of the program.
Please let me know if I'm still being unclear.
(By the way, using Dictionary instead of e.g. ConcurrentDictionary seems to make the implementation thread-unsafe.)
You are not still being unclear, @cmeeren.
```f#
module Cmd =
// Found in Tim Lariviere's ElmishContacts/ElmishExtensions.fs file
let ofAsyncMsgOption (p: Async<'msg option>) : Cmd<'msg> =
[ fun dispatch -> async { let! msg = p in match msg with None -> () | Some msg -> dispatch msg } |> Async.StartImmediate ]
module App =
type Model =
{ .. }
let init () = { .. }
type Msg =
| Start
| Cancel
let mutable cts = new System.Threading.CancellationTokenSource()
// Any long-running work e.g. HTTP request
let work =
async {
while true do
printfn "Working..."
do! Async.Sleep 1000
}
let start = async {
cts.Cancel()
cts <- new System.Threading.CancellationTokenSource()
printfn "Start"
Async.StartImmediate(work, cts.Token)
return None
}
let cancel = async {
printfn "Cancel"
cts.Cancel()
return None
}
let update msg model =
match msg with
| Start -> model, Cmd.ofAsyncMsgOption start
| Cancel -> model, Cmd.ofAsyncMsgOption cancel
let view (model: Model) dispatch =
View.ContentPage(
content = View.StackLayout(padding = 20.0, verticalOptions = LayoutOptions.Center,
children = [
View.Button(text = "Start", command = (fun () -> dispatch Start), horizontalOptions = LayoutOptions.Center)
View.Button(text = "Cancel", command = (fun () -> dispatch Cancel), horizontalOptions = LayoutOptions.Center)
]))
```
The entire file can be found in the following gist: FabTestCTS.fs
Please let me know if that's an inadequate pattern or if you have suggestions for performance improvements.
TimLariviere's debounce code in ElmishContacts/ElmishExtensions.fs is a nice example of using CancellationTokenSource in a Fabulous app:
@rastreus By the way, ofAsyncMsgOption and debounce are now part of Fabulous. Though debounce is a little different. Will update ElmishContacts with the latest Fabulous version.
(By the way, using Dictionary instead of e.g. ConcurrentDictionary seems to make the implementation thread-unsafe.)
@cmeeren Thanks for noticing it. I'll fix it!
Regarding the initial question, I can't think of a good pattern to handle this gracefully.
Storing the CancellationTokenSource in the model is a possibility but it's not great at all, because first the CTS is time-limited (only has meaning when the action is ongoing) and it's not serializable (so you won't be able to store your model for later use)
Personally, I would have gone the same way @rastreus did in his second example, with a mutable field.
It seems ok because there won't be ton of cases where you will need to cancel something.
Also it's very specific to a certain part of your app, so makes sense to keep it close to your page.
Thanks. I have an app that is very API-based, where there is no technical reason why there can't be multiple requests in flight at the same time (even to the same endpoint). So I'd need a lot of mutable fields. Could probably encapsulate the "cancel and set a new CancellationTokenSource" behaviour in a helper class. But dealing with multiple simultaneous requests to the same endpoint would still need some list-based storage, not mutable-field-per-operation.
I'll figure out something, though. When/if I ever get to rewriting the app using Fabulous. :) I.e. probably not before #383/#241.
My question is answered, so feel free to close this whenever or keep open to encourage more discussion.
I'm not sure if it would apply to your situation, but I have had great success managing long running web operations with MailboxProcessors. That way you can have a replaceable cancellation token without a mutable field, you can post start / stop messages etc to manage the connection and you can easily spin up multiple instances to run requests in parallel.
There are loads of ways to adapt it to the MVU loop, if you would like me to suggest something I would be happy to but don't want to spam the thread!
Since my original question is more or less answered (there does not currently exist a definitive recommended pattern for cancellation in an Elmish architecture), there isn't really much to derail here. So yes please, I'd appreciate any input. :)
(I'll leave it to the maintainers to choose when to close the task, since they may or may not want to keep it open as a reminder to create some recommendations here.)
Ok cool :) I'll start with a little caveat that I haven't actually used Fabulous itself as we don't use Xamarin Forms at work, but I have recreated it for Xamarin Native by pinching heavily from here.
I'll describe a simple one-endpoint-at-a-time version but you could expand this out to handle concurrent requests using some kind of dictionary or list – again, if you would like me to try to elaborate on that I would be happy to.
So, to start with I would add an argument to your update function to pass in the API function you want to call. This API function would have a mailbox processor pre partially applied. That means of course that every time you provide the input argument, it will always get posted to the same mailbox.
That mailbox processor in turn will need a reference to the main Program dispatch baked in so that it can post results back to the updater.
The arguments to the API function can be along the lines of Run of Request * ResponseMsg or Cancel etc.
The API function will simply post the message to the mailbox – this means you can start it with Cmd.ofFunc.
Internally the mailbox would maintain a cancellation token, a function to execute the API request, and a match to handle the incoming messages.
Every time the mailbox receives a message, it will cancel its existing token, aborting any running job, and create a new one. It will then switch on the incoming message – if Cancel, job done and return.
If it is a Run request, it will bundle up the Program dispatch, the Request and the ResponseMsg and run them immediately, not awaiting the result, and move on to pull the next message from its inbox. When the response returns it will be dispatched back to the update function.
One thing that isn’t so great here is that the update func needs to know its position in the hierarchy in order to construct the appropriate message to return – if it is a child of a child of an updater, it will need to wrap its return message appropriately so that it can make its way back from dispatch. In my example below I have assumed it is top level for simplicity. I guess alternatively you could pass in the pre-constructed return message as a parameter to the update function, that way it would be easy to refactor and keeps the hierarchy knowledge outside of update.
If this needed to be parallel, the mailbox could hold a map of running operation IDs and cancel tokens or something, or perhaps delegate to child mailboxes. I'm sure you can probably work something out there, the update loop is the important thing, and the fact that you don’t need any mutable state.
I would recommend Vaughn Vernon's Reactive Messaging Patterns with the Actor Model as a great reference to all the cool ways you can use actors, and I think they fit really well with MVU. (Ignore the Scala / Akka mention - that is just the system he uses to demonstrate but most of the book is just reference patterns which apply anywhere.)
I haven’t run the example below of course, it is just psuedocode but it is pretty much valid F# and I have implemented very similar in my apps.
```f#
type ApiRequest = ApiRequest
type ResponseMsg = Result
type UpdateMessage =
| Run of ApiRequest
| Cancel
| Running of unit
| Cancelled of unit
| ApiResponse of ResponseMsg
| ApiError of exn
type ApiMessage =
| Run of ApiRequest * (ResponseMsg -> UpdateMessage)
| Cancel
let apiEndpointManager
apiClient
dispatch =
MailboxProcessor<ApiMessage>.Start (fun inbox ->
let rec innerLoop (cts : CancellationTokenSource) =
async {
let execute request handler =
async {
let! response = apiClient request
dispatch (handler response)
}
let! message = inbox.Receive()
cts.Cancel()
let newCts = new CancellationTokenSource()
match message with
| ApiMessage.Run (request, handler) ->
let op = execute request handler
Async.StartImmediate(op, newCts.Token)
| ApiMessage.Cancel -> ()
do! innerLoop newCts
}
innerLoop (new CancellationTokenSource()))
let apiFunc
(apiManager : MailboxProcessor<ApiMessage>)
apiMessage =
apiManager.Post apiMessage
let update
(apiFunc : ApiMessage -> unit)
msg
model =
match msg with
| UpdateMessage.Run rq ->
let newCommand = Cmd.ofFunc apiFunc (Run (rq,ApiResponse)) Running ApiError
model, newCommand
| UpdateMessage.Cancel ->
let newCommand = Cmd.ofFunc apiFunc Cancel Cancelled ApiError
model, newCommand
| UpdateMessage.Running -> model, Cmd.none
| UpdateMessage.Cancelled -> model, Cmd.none
| UpdateMessage.ApiResponse response -> model, Cmd.none
| UpdateMessage.ApiError e -> model, Cmd.none
```
Thanks!
If I understand correctly, then in general, this seems to be a significant complication of the simple Elmish architecture.
update function needs new a separate argument for this to work instead of using a canonical CmddispatchI find the simplicity of the Elmish architecture one of its most important benefits, so personally I probably won't explore your method further. I have no problems with "pragmatic impurity" (e.g. mutable fields) as long as it's contained/encapsulated and easily understandable. But thanks anyway for taking the time to do the write-up; it's good to have different methods in the discussion. :)
Well, I wouldn't say significant personally. I think it is using it how it is designed to be used, not complicating it.
Including functions that you want to call as arguments to update is recommended practice. You are _calling_ this function using Cmd, which is how Elmish should work. You partially apply these at composition time, so they are invisible in the main application.
Using dispatch to send messages through the update hierarchy is also the way things are supposed to be done - it is the message bus for the application. That is how Cmd.ofSub etc work. You could make a new command to handle it or something if you use it a lot.
As I suggested (in a later edit) you could also pass the return message constructor into the update function at composition time, then the knowledge about position in the hierarchy is pushed out to the edge of the application.
The bottom line is, you need to maintain and manage some state, and IMHO the best way to do that in a functional language is usually with an actor (i.e. mailbox), not a mutable field.
The example I gave also does not require the API functions to be in the same module as the update (to access the shared mutable field).
I think it is a pretty simple and idiomatic solution but I respect that you might not see it that way.
Cheers :)
This might help you a bit with the way you can bend Elmish to your needs (apologies if you know it all already, I have no idea what your experience is!) See the 'Everything is a function' section in particular : https://medium.com/@MangelMaxime/my-tips-for-working-with-elmish-ab8d193d52fd
The key thing to note here is that where he is calling other funcs in the module directly using Cmd.whatever, I am essentially suggesting you do the same.
I just pass them in to update as a parameter when composing the application rather than _directly_ calling one func from another in the same module, as otherwise you end up with coupled code and it also makes update unit testable (parameterise all the things, as Scott Wlaschin says!)
I would do something simple. Maybe something like this:
module Cmd =
let ofCancellableAsyncMsg (p: Async<'msg>) : (unit -> unit) * Cmd<'msg> =
let cts = new CancellationTokenSource()
(fun () ->
use source = cts
source.Cancel()),
[ fun dispatch -> Async.StartImmediate(async { let! msg = p in dispatch msg }, cts.Token) ]
Then it would be called with
let update msg model =
match msg with
| CaseThatCreatesSomeCancellable ->
let (cancel, cmd) = Cmd.ofCancellableAsyncMsg (*cancellable workflow here*)
{ model with ToCancel = cancel::model.ToCancel }, cmd
| CaseThatCancelRunningCancellables ->
for cancel in model.ToCancel do
cancel()
{ model with ToCancel = [] }, Cmd.none
That's cool but you are still left with a list of functions which have refs to the cancellation token in your model aren't you?
Yes, and also probably leak if cancel is not called. But it's simple and reusable. The CancellationToken is now hidden by an unit -> unit function that just cancels what is cancellable. That resulting cmd can be used with another functions that expect a Cmd, like Cmd.map and Cmd.batch.
Yeah, it is simpler than what I suggested for sure, although not by much if you squint for a minute and definitely not if you have to start including more complex request management.
All I can say is that using a mailbox to manage the cancellation state is robust enough to be used in a production application and is very flexible as the messages it receives form a nice API to manage the requests, and you can even use the queue to manage back pressure or prioritisation.
_How_ exactly you fit them into the loop is obviously very flexible - my solution was only a suggestion.
Also, notice that in my sample you will get callbacks to let you know that something is running or cancelled once you kick it off, in addition to the final result. This means you can update loading indicators or whatever in your model.
It's indeed better and well thought out, but I would be too lazy to use it. 😅
Haha fair enough :) , although I must say I honestly don't see what is complex about it - it is bare-bones MVU and simple F#. I only discovered MVU about 3 months ago and F# just over a year ago, I am by no means advanced.
If it is the usage of a Mailbox and the general idea of the actor model, then I highly recommend you dig a bit on it as it is honestly such a useful abstraction which allows you to manage state and concurrency in a safe way - they are the key that makes F# usable in a big production app for me and very complimentary to MVU.
@RyanBuzzInteractive, thanks for your clarifications. It seems I misunderstood a few important aspects (I was in a hurry and focused on your description, and skimmed the code).
Including functions that you want to call as arguments to update is recommended practice. You are _calling_ this function using Cmd, which is how Elmish should work. You partially apply these at composition time, so they are invisible in the main application.
Yes, you are absolutely right. Partial application is a nice way to deal with dependencies. (Personally I prefer dependency rejection, though my primary experience is with web APIs, not MVU apps, and I realize the update function might benefit to a larger extent from partial application to be more easily testable if one cares about testing Cmds.)
Using dispatch to send messages through the update hierarchy is also the way things are supposed to be done - it is the message bus for the application. That is how Cmd.ofSub etc work. You could make a new command to handle it or something if you use it a lot.
Yes, again you are right. I misunderstood what you meant by "baking in" the dispatch.
As I suggested (in a later edit) you could also pass the return message constructor into the update function at composition time, then the knowledge about position in the hierarchy is pushed out to the edge of the application.
Yes, this is possible. All else being equal, I would still prefer simple Cmd mapping though. But "all else being equal" is almost never true, so that's not particularly helpful of me 🙃
The bottom line is, you need to maintain and manage some state, and IMHO the best way to do that in a functional language is usually with an actor (i.e. mailbox), not a mutable field.
I don't necessarily agree. Granted, I have very limited experience with actors. My understanding is that in many cases, then yes, they are the right solution, and they are also very flexible. But they are also more heavy-weight than some impure alternatives, so pragmatically, I wouldn't necessarily implement an actor for functionality that a single properly encapsulated mutable field could accomplish. (Though this might be my limited experience with actors speaking.)
The example I gave also does not require the API functions to be in the same module as the update (to access the shared mutable field).
Neither does a solution using mutable fields, because the mutable field isn't required to be used directly by the update function. The mutable field can be private to the module containing your API calls, as long as you supply a cancel function that cancels and resets the CancellationTokenSource.
Also, notice that in my sample you will get callbacks to let you know that something is running or cancelled once you kick it off, in addition to the final result. This means you can update loading indicators or whatever in your model.
This is also possible when using "direct" API calls instead of messages/actors. Simplifying a bit, when the update function receives Msg.FooRequest of FooRequestData, it returns { model with IsBusy = true } and uses Cmd.Of... to call the API. The API function is a simple FooRequestData -> Result<FooSuccessData, FooError>, and the Cmd.Of... wraps this in Msg.FooResponse of Result<FooSuccessData, FooError>.
If you need even more flexibility (e.g. dispatching progress messages during the request), you can use a similar approach but use Cmd.ofSub, and let the API function accept a dispatch parameter. No actors needed here either.
Building off of the last point above, I guess my main point in all of this is the following:
Elm - even being purely functional - seems to manage TEA (The Elm Architecture) apps just fine without actor-like abstractions, instead just using Cmd. You may very well be right when you say that MVU and actors complement each other well, but since Elm seems to do fine without this added abstraction, some proof is needed in order to convince me (and others?) of the benefit of actors in TEA apps over simply sticking to idiomatic TEA abstractions.
(Note that I am no Elm expert, so it may be that Elm apps in fact often use actor-like abstractions and I've just completely missed it. I have my doubts though.)
Hello :)
Thanks for the detailed response.
I pretty much agree with everything you said to be honest, and appreciate some of the later points you made that I wasn't clear on previously.
Personally, I think that actors aren't really that big a deal - it is a state machine with a queue of messages really, not heavy at all. Performance wise they are supposed to be extremely lightweight. I think it is just a case of the right tool for the job, for me anyway.
There are a lot of new Cmds in Fabulous that aren't in Elmish - I think if we start with an 'if it isn't in Elmish, we don't need it' approach then we might lock out some great ideas that make things easier, but I totally get your point. How does Elmish deal with cancellation? Or state management in general?
I mentioned right at the top that I haven't actually used Fabulous (or Elmish or Elm or any of them), I just read the docs then rolled my own for Xamarin Native, and I think maybe that gives me a different view of the MVU architecture rather than one formed by a specific implementation. Whether that helps or hinders I am not sure haha.
I will have a ponder and see if I can think of some way to directly fit my idea into a more idiomatic (for Elm) Cmd wrapper, although to me it seems pretty light and simple already. I guess we all see things differently in that regard :)
Cheers :)
There are a lot of new Cmds in Fabulous that aren't in Elmish
Just to be clear, the last parts of my comment was about Elm, not Elmish.
How does Elmish deal with cancellation? Or state management in general?
I assume you are talking about Elm. That's a good question! I don't know. Googling "elm cancellation" wasn't particularly helpful. Will need to investigate more.
I mentioned right at the top that I haven't actually used Fabulous (or Elmish or Elm or any of them), I just read the docs then rolled my own for Xamarin Native, and I think maybe that gives me a different view of the MVU architecture rather than one formed by a specific implementation. Whether that helps or hinders I am not sure haha.
Fresh eyes are always appreciated - by looking at things differently and sharing our perspectives, we can gain new insights. Though from personal experience, I know that if I'm mimicking something that I don't have significant experience with, it's easy to misunderstand some important aspects of the thing I'm mimicking. The proof is in the pudding, as they say, and if you don't really taste the pudding...
This can even happen when properly using a technique but in a limited fashion. For example, there are many questions on the web about scaling Elm apps, from users who use Elm, but haven't created large apps yet. The natural inclination of devs when they first try Elm is to separate out child model/view/update components, because such separation is baked heavily into our mindsets. But in Elm, you don't need such a heavyweight solution. Since everything is functions, you can just delegate to helper functions and keep a single Msg type and Model type, as described by rtfeldman in his comments here and here.
(As a side note, this is one of the most challenging things with developing Elmish.WPF: The static views place their own constraints on the MVU architecture, and I have to find the right balance between the idiomatic, simple MVU, and convenience in the face of static views, all the while determining if feature requests from users are due to their misunderstanding of MVU or a genuinely missing feature in the library.)
I was talking about Elm or Elmish really - as I understand it, Elmish is basically 'Elm for F#' but a separate project like Fabulous ? I mean surely if you are making an Elmish application, you need to deal with cancellation and state management right? Or maybe I am missing something. I don't want to get off topic, sorry!
I also think that it is important to make the point that this seems to ultimately be a conversation around state management (external to the model, non-serialisable), although the OP was about cancellation, because that is an example of a state problem. There must be an established way of doing that in TEA apps?
I would love to 'taste the pudding' (that should totally be Fabulous's tag line btw haha) but I am working at a .Net shop that is currently exclusively C# MVVM for native Xamarin - I am very close to winning them over to F# for the core, but they will be using the Android and iOS designers for the forseeable future. To be fair, I also have no interest in Xamarin Forms, it just isn't a sensible proposition for most of our apps which need fine grained platform specific UI for picky clients. I will look at Elmish for the website to back my current app though.
MVU seems like a sensible way of dealing with the vm / core interaction in my apps using the half-elmish style (rather than the loose layered systems I had come up with before) as it is consistent, symmetrical, strongly structured but very flexible etc. The pattern is very easy to grasp, even for beginners, and I could recreate the whole system in a few lines of code with no external dependencies - a big win in my book and should help when onboarding new employees. It does mean that I am less worried about what has gone before so to speak (from a consistency perspective - obviously I don't want to reinvent the wheel or repeat mistakes), although I am very interested in all of these discussions as I learn a lot and would love to contribute to this project in the future. :)
Just back on the topic again - perhaps all of this isn't actually relevant to TEA, as in, TEA doesn't tell you what functions to run, rather how to run them. Using the mailbox to manage a piece of state is choosing what to run, then using update and a Cmd to run it is the how. Just another potential perspective on things.
Edit : Just reading the reddit threads you posted, really useful, thanks - I think you got me to them just quickly enough to save me a lot of wasted hassle from over-componentising! :)
Ok how about some potentially useful code for once instead of all my waffle:
All else being equal, I would still prefer simple Cmd mapping though.
How does this seem for a command that wraps up all the stuff I wrote originally, no need for applying the dispatch to the mailbox in advance, no need for the mailbox wrapper, no need for a component to know its position. Much more like standard TEA.
```f#
let ofActorAsync
(actor : MailboxProcessor<'a * ('b -> unit) * AsyncReplyChannel<'c>>)
(arg: 'a)
(ofComplete: 'b -> 'msg)
(ofRunning: 'c -> 'msg)
(ofError: _ -> 'msg) : Cmd<'msg> =
let bind (dispatch : Dispatch<'msg>) =
async {
let! r = actor.PostAndAsyncReply (fun asyncReplyChannel -> arg, ofComplete >> dispatch, asyncReplyChannel) |> Async.Catch
dispatch
(match r with
| Choice1Of2 x -> ofRunning x
| Choice2Of2 x -> ofError x)
}
[bind >> Async.StartImmediate]
```
I think this could be a nice way of managing non-model state with a little polish and thought maybe. It could probably be named more appropriately.
All you need to do now is design a mailbox to handle the state transformation and execute the callback, which is no more effort than writing the function/s you would have done previously to manipulate the mutable field. Just think of it as a function with a state bucket in this context.
If it would help, I could show how my original example dealing with cancellation would look under this system, but you can probably work it out.
I could also potentially adjust this to return an IDisposable token in the Running message, which would allow cancellation of the callback if necessary whilst allowing the operation to complete, basically making it an Observable.
Edit : Just reading the reddit threads you posted, really useful, thanks - I think you got me to them just quickly enough to save me a lot of wasted hassle from over-componentising! :)
Great! :) You can also check out my comment to this video and the links in that comment.
If it would help, I could show how my original example dealing with cancellation would look under this system
Please do!
I investigated a bit, and it seems that for cancellation, Elm might use mutable state, but it's abstracted away from the user in the Http module.
The relevant functions are:
request :
{ method : String
, headers : List Header
, url : String
, body : Body
, expect : Expect msg
, timeout : Maybe Float
, tracker : Maybe String
}
-> Cmd msg
cancel : String -> Cmd msg
When you create a request, you can supply a string as the tracker argument. This string can then be used to cancel the request. This of course leaves it entirely up to the user whether the string is stored in the model or simply hardcoded per unique request.
I imagine that the user-supplied tracker strings are stored in a mutable map with handles to the requests, but I haven't checked the implementation.
For completeness, let me mention that the tracker string can also be used to get progress updates using
track : String -> (Progress -> msg) -> Sub msg
where Progress is defined as
type Progress
= Sending
{ sent : Int
, size : Int
}
| Receiving
{ received : Int
, size : Maybe Int
}
I like this design because it means that, at most, you only have to store strings in the model, and the user is always in control of how to use the API. Writing a similar library is probably not that hard. I always abstract away HttpClient stuff anyway in an ad-hoc manner for each project I create.
Ah that is really interesting and may be useful to me as a pattern moving forward.
I will also check out the video / comments :)
What do you think of the cmd I posted above? I still think this is a better general purpose solution for when you need to maintain state outside of the model, not just a cancellation token.
This is really common in mobile apps, perhaps less so on the web side of things. I often need disposable tokens for observables, cached data from the database, objects which are being processed for whatever reason etc. These need to be stored somewhere other than the model. They do not need to be persisted or displayed, they are just objects that are in use at runtime to make the app function.
This is why I find the mailbox such a useful solution, and the cmd I suggested hides all of the implementation away in a nice Elm like manner.
I have a bit of a deadline today but will put up an example of it used to do the cancellation later, that should illustrate it better.
Looking forward to your example! I'm not that used to actors/mailboxes, so I need to see an example usage to be able to comment on it. No hurry, take your time.
I have found other solutions to the same problems. For example, a cache can essentially just be a configurable (and composable) function wrapper with enclosed mutable state (think of a basic memoization implementation). I manage caching (and other resilience patterns) using Polly, which I highly recommend (I wish it had a more F#-friendly API, though). For disposables and cancellation tokens, I have abstracted away mutable state in an ad-hoc manner similar to Elm's Http module as described above, though not as elegant.
As I said, I'm not used to actors, but AFAIK an actor is an addressable process, often a state machine, that encapsulates mutable state in a concurrency-safe manner. They therefore seem more heavy-weight than simple, composable functions - and actors are not easily composable. In the language of Tomas Petricek, an actor seems to have some similarities with a framework, since it controls how your code is run rather than the opposite.
Specifically regarding using actors for caching, I also came across the article Don't use Actors for concurrency, see the section "Caching is not state".
That was an interesting article. He makes some points which I am sure are valid on fully implemented actor based systems like Akka running at scale. I also totally agree that there is no point in using an actor if you don't need to manage state, just use a normal function. I think his definition of cache is a bit different to mine, I just mean some data needed at runtime but not stored on disk, or an in-memory reflection of some on-disk data to make access easier and faster, not a static store.
I will start here by saying that I am a fairly junior programmer, about 5 years pro, and only a year on F#, and mostly self taught, so I am probably a little off the mark on some things here and welcome any input from more knowledgeable people.
I think it is initially important to make a distinction between using actors and the actor model of software design.
At a very basic level, an actor is just a recursive loop with a message queue that runs on its own thread - no more, no less. I really like the fact that F# has a simple, lightweight and stripped down implementation included out-of-the-box, the MailboxProcessor. As it just uses a delegate that runs recursively, you can replace the state on each loop, and because it has an inbox you can post it messages and it will work through them as and when it can.
The actor _model_ isn’t a framework, it is a configuration-based model of computation and interaction, as opposed to a traditional state based turing machine model. Frameworks like Akka build on the concepts by providing tools such as an actor registry / resolution system, distribution across machines, failure handling, loads of stuff, but they aren’t the model themselves any more than Fabulous ‘is’ MVU.
If you are interested in the fundamental concepts and power of the model-proper, there is an essential talk by the guy who invented it, Carl Hewitt (interviewed by Eric Meijer) here: https://www.youtube.com/watch?v=7erJ1DV_Tlo
They explain (in an informal way) the essential axioms – that actors can only talk to other actors, and messages can be received at any time but and at most once etc etc. and what this brings to reasoning about interaction. The bit that really struck a chord with me is that it models _indeterminacy_ (as opposed to non-determinacy) in a real way, which you can’t do with direct method calls, and that in turn forces you to consider it in your system design. It makes you think in terms of domain events which can happen in any time in any order. You think about the interaction much more than the state.
There is also a great amount of info here:
https://doc.akka.io/docs/akka/current/guide/actors-intro.html
There is obviously a lot of potential to model complex applications using a full, strict system like this, and arguably you would get more out of it the more you embrace it, much like MVU and FP in general.
However, most of this is _way_ more than I need right now for a mobile app and certainly a whole world away from what I am suggesting in the context of this thread.
I think talking about the actor model is a bit like event sourcing – everyone has a different understanding (or lack thereof) of exactly what it is or how you do it or to what extent.
I have taken the same approach here as I did there – try to understand the fundamental concepts, then implement the bits I understand which are useful in a pragmatic way to improve my daily code, rather than try to go full bore into creating a pure event sourced or actor based application because it is the latest thing and making an expensive mistake, or alternatively ignore it altogether and keep on doing things the inefficient way I had always done them.
A little background: I create Xamarin apps at work, previously in C# exclusively. I received the Reactive Patterns w/ Actors book I linked to earlier as a gift, and found it fascinating although a little heavyweight as Akka is a full framework and overkill for my needs and Scala makes my eyes bleed. Most of the book however is just a general pattern reference, it isn’t that specific to the language or framework.
A little while later I found (fell in love with) F# and started using it for the core Xamarin work. I needed to manage some application state, and the mailbox processor was easy to use and worked really well for many things. It allowed me to make use of many of those patterns where appropriate whilst not marrying a large framework, everything I needed was right there in the language.
It also was a revolution when dealing with the database. I no longer need to worry about concurrent writes. I have a single mailbox which manages the write connection. No more locks or anything. My reads happen using separate actors – I currently have one per table as I have a generic implementation that I instantiate with a return message type. This is my own ad-hoc approach which fits my apps, and is a lot better than what I had before (locking a SQLite connection). I never need to worry about massive parallel access, as my apps don’t do that. If they did, I would research an appropriate solution for that scenario.
The one thing I did find on my last project was that without a formal way of connecting the mailboxes, it can be hard to follow the flow of control for anyone unfamiliar with the architecture in question. That is why I have embraced MVU – it gives a rigid backbone for the application which can call into side-processors as necessary, making it easy to navigate and reason about.
It may not be perfect, and it may not be strict MVU or Actor Model or FP or OO or whatever, but I know why I made every decision (and compromise), it works really well and I have basically no external dependencies. It is the best of all the worlds I know of done the best way I know how, which is all any of us can do hey. I am working on a proper project with all of this included right now which I am happy to share if anyone is interested, it will end up on Github eventually.
Ok, so all of that said, I will reiterate that I am just _simply talking about using the mailbox processor as a state bucket in this context,_ the most basic way you could think of. No big computing model or framework to deal with, just a small recursive loop with a stashed thing in it. The most basic way you can maintain state without a mutable field, and arguably simpler to reason about.
How would I implement the cancellation loop using the cmd I posted previously? Something like this:
```f#
type ApiRequest = ApiRequest
type ApiMessage =
| Run of ApiRequest
| Cancel
type ApiCallback =
| Reponse of Result<string,exn>
| Cancelled
type UpdateMessage =
| RunRequest of ApiRequest
| CancelRequest
| Running of unit
| Cancelling of unit
| ApiComplete of ApiCallback
| ApiError of exn
type Dispatch<'a> = 'a -> unit
type ApiEndpointManager<'a> = MailboxProcessor<ApiMessage*Dispatch<'a>*AsyncReplyChannel<unit>>
let apiEndpointManager
apiClient = // partially applied during construction
ApiEndpointManager.Start (fun inbox ->
let rec innerLoop (cts : CancellationTokenSource) =
async {
// Run the request and callback when done
let execute request dispatch =
async {
let! response = apiClient request
dispatch response
}
//Grab a message + dispatch and replychannel from the inbox. Dispatch already has return message precomposed in Cmd.exec.
let! message, dispatch, replyChannel = inbox.Receive()
// Cancel existing token and create a new one
cts.Cancel()
let newCts = new CancellationTokenSource()
// Switch on message and act appropriately
match message with
| ApiMessage.Run request ->
let op = execute request dispatch
Async.StartImmediate(op, newCts.Token)
| ApiMessage.Cancel -> ()
replyChannel.Reply() // Use this to reply with Running content
// Call self with new state
do! innerLoop newCts
}
// kick off the recursive loop
innerLoop (new CancellationTokenSource()))
let update
(apiFunc : ApiEndpointManager<ApiCallback>)
msg
model =
match msg with
| UpdateMessage.RunRequest rq ->
let newCommand = Cmd.ofActorAsync apiFunc (Run rq) ApiComplete Running ApiError
model, newCommand
| UpdateMessage.CancelRequest ->
let newCommand = Cmd.ofActorAsync apiFunc Cancel ApiComplete Cancelling ApiError
model, newCommand
| UpdateMessage.Running () -> model, Cmd.none
| UpdateMessage.Cancelling () -> model, Cmd.none
| UpdateMessage.ApiComplete callback -> model, Cmd.none
| UpdateMessage.ApiError e -> model, Cmd.none
```
I have included the types for completeness, it should actually compile if you import the cmd from earlier in the thread.
As previously mentioned, this example is a 'one request at a time' set-up where each cancels the last, but you could easily expand it, and adapt it to handle any kind of state.
The important bit is how nice it looks in the update func now, and if you strip all the comments out of the mailbox it is only a few lines. You are completely in control of how you use it, that depends on the mailbox body, and the Cmd is very generic, just like OfAsync but with an extra callback message.
On a side note, Polly looks really cool and I have bookmarked it but again way way more than I need for simple line-of-business Xamarin apps in general. Here is my own observable caching implementation using a mailbox, complete code:
```f#
type EventBus<'T> () =
let _event = new Event<_>()
let _onPublish = _event.Publish
[<CLIEvent>]
member this.OnPublish with get() = _onPublish
member this.Invoke(arg : 'T) = _event.Trigger(this, arg)
type Cache<'Tag,'Content> =
{
Content : 'Content
}
type CacheCommand<'T> =
| Update of 'T option
| Fetch
| Subscribe of (obj * 'T option -> unit)
type CacheResult<'T> =
| Updated
| Subscribed of IDisposable
| Cache of 'T option
type CacheOperation<'T> = CacheCommand<'T> * AsyncReplyChannel<CacheResult<'T>>
let cacheProcessor<'T> =
MailboxProcessor<CacheOperation<'T>>.Start (fun inbox ->
let rec innerLoop ((currentCache : 'T option),(eventBus : EventBus<'T option>)) =
async {
let! (command, replyChannel) = inbox.Receive()
let result, newCache =
match command with
| Update maybeItem ->
try
eventBus.Invoke(maybeItem)
with _ as ex -> () // Report to error tracking?
Updated, maybeItem
| Fetch ->
(Cache currentCache), currentCache
| Subscribe handler ->
let token = eventBus.OnPublish.Subscribe(handler)
try
handler(obj(), currentCache)
with _ as ex -> () // Report to error tracking?
(Subscribed token), currentCache
replyChannel.Reply(result)
do! innerLoop (newCache,eventBus)
}
innerLoop (None, new EventBus<'T option>()))
[<Preserve(AllMembers = true)>]
type ObservableCache<'T> () =
let mailbox = cacheProcessor<'T>
let postCacheCommand
(message : CacheCommand<'T>) =
mailbox.PostAndAsyncReply(fun asyncReplyChannel -> message, asyncReplyChannel)
interface IObservableCache<'T> with
member this.Post (command : CacheCommand<'T>) =
(postCacheCommand command) |> Async.StartAsTask
Which I register with Autofac like this, allowing me to create a unique cache channel on the fly by resolving it with a type.
```f#
builder.RegisterGeneric(typedefof<ObservableCache<_>>).As(typedefof<IObservableCache<_>>).SingleInstance() |> ignore
Again, no external dependencies (other than Autofac but we use that for the C# DI anyway), simple and does the job.
For example, if you want to receive customers, inject a Customer cache and post Subscribe, keeping hold of the returned disposable token. If you want to send, inject the same cache type and post Update. That's it.
Again, I may be biased by my unfamiliarity with actors/MailboxProcessor, but personally I find the following easier to understand. There's fewer types, no messages apart from the MVU loop, and no self-contained subsystems (actors) to understand. It's just functions calling functions. (I haven't done any "DI"/partial application in this example, but that's of course possible to adjust. Also, I'm not saying the exact specifics shown below are the optimal ones - this was just the first reasonable thing that came to mind that indicates my main point.)
```f#
[
module Domain =
type SignInRequest = { Username: string; Password: string }
type SignInResponse = { Token: string }
type SignInError = InvalidCredentials | Exn of exn
module Http =
module SignIn =
let mutable private cts = new CancellationTokenSource ()
let cancel () =
lock cts (fun () ->
cts.Cancel ()
cts <- new CancellationTokenSource ()
)
let private constructRequest
: SignInRequest -> HttpRequestMessage =
failwith "not implemented"
let private parseResponse
: HttpResponseMessage -> Async<Result<SignInResponse, SignInError>> =
failwith "not implemented"
let send (request: SignInRequest) : Async<Result<SignInResponse, SignInError>> =
async {
cancel ()
use client = new HttpClient ()
use req = constructRequest request
use! resp = client.SendAsync(req, cts.Token) |> Async.AwaitTask
return! parseResponse resp
}
type Model = { IsSigningIn: bool }
type Msg =
| SignInRequested of SignInRequest
| SignInCompleted of Result
| SignInCancelRequested
| SignInCanceled
let update msg model =
match msg with
| SignInRequested req ->
let cmd = Cmd.OfAsync.perform Http.SignIn.send req SignInCompleted
{ model with IsSigningIn = true }, cmd
| SignInCompleted _ ->
{ model with IsSigningIn = false }, Cmd.none
| SignInCancelRequested ->
let cmd = Cmd.OfFunc.perform Http.SignIn.cancel () (fun () -> SignInCanceled)
model, cmd
| SignInCanceled ->
{ model with IsSigningIn = false }, Cmd.none
Furthermore, it occurred to me that it feels a bit weird to start/run actors using `Cmd`. As I understand it, an actor may generally run indefinitely, so this seems to fit `Sub` better than `Cmd`. Using `Cmd` to start an actor seems like trying to fit the wrong abstraction into `Cmd`, at least superficially (if my understanding is correct).
---
As for caching: Yes, Polly supports a lot of use-cases, but basic usage is very simple:
```f#
let private cachePolicy =
Policy.Cache(
MemoryCacheProvider(MemoryCache(MemoryCacheOptions())),
TimeSpan.FromMinutes 5.)
let private someFunctionUncached () =
...
let someFunction =
cachePolicy.Execute((fun _ -> someFunctionUncached ()), Context("cacheKey"));
It also just as easily supports more advanced use-cases (async, cache management, callbacks on cache hit/miss, etc.).
In general, regarding Polly: Resilience patterns are simple at the fundamental level, but real-world concerns and edge cases can quickly make any implementation fairly complex (I tried, failed, looked at the Polly code for inspiration and then decided to cut my losses). I'm more than happy to let (what I understand to be) the de facto standard .NET resilience library handle that for me. :) As a big plus, plugging in other supported policies like circuit breakers, timeouts, fallbacks etc. and composing them together is very simple.
A final note on recursive async calls: Always use return! when recursing, not do!. Otherwise it won't be tail recursive, and may cause stack overflow for sufficiently many recursions. A common gotcha. :)
Great tip on the return!, thanks.
I guess you haven't watched the Carl Hewitt talk yet. There is a whole lot more you get from the simple abstraction, but you will need to let it sink in for a bit I expect, it took me a while. You have the state. What you are missing is the queue, and the thread. The fact that you get a proper model of an indeterminate system.
I'm not sure how you would spin up multiple requests at once with your module approach - you would need more mutable values. You can't instantiate a new module, you can spin up a new actor. Also threading might be an issue I guess? You could work around all that of course but your solution would soon look a lot more complex than mine I would wager.
I personally find the example I gave cleaner and easier to reason about, but I guess that is just personal preference.
Also, I would say again that I was only giving a simple example of how you might incorporate actors - let your imagination run wild. Just think of it as a safe way to handle state. It is so lightweight.
For instance, you mention Cmd.OfSub. Well, that is exactly how I use them in my app. You see my ObservableCache in the last post? I have a function that I call in update's init using Cmd.ofSub, which posts a subscribe message to a cache and returns the disposable to update. Any values emitted by the cache are then sent into my update function as messages. Again, this is like 15 lines of code. Both the cache and this func are generic so only have to be written once.
```f#
let initCacheObserver
(cache : IObservableCache<'Tag'Content>)
onCacheUpdated
onSubscribed
dispatch =
async {
let cacheUpdatedHandler (sender, (maybeCache : ('Tag'Content) option)) =
maybeCache
|> Option.map (fun (tag,data) -> data)
|> onCacheUpdated
|> dispatch
match! cache.Post(CacheCommand.Subscribe cacheUpdatedHandler) |> Async.AwaitTask with
| CacheResult.Subscribed token -> dispatch (onSubscribed token)
| _ -> ()
} |> Async.Start
...
```f#
match message with
| SurveyListMsg.Init ->
let subToSurveyCellsCmd = Cmd.ofSub (initSurveyCellModelCacheObserver SurveysUpdated SubscribedToSurveys)
return model, subToSurveyCellsCmd, surveyUpdatesToken
| SurveyListMsg.SubscribedToSurveys token -> return model, Cmd.none, Some token
| SurveyListMsg.SurveysUpdated maybeSurveys ->
let surveys = maybeSurveys |> Option.defaultValue Seq.empty
let newModel = {model with Surveys = surveys; Loading = false}
return newModel, Cmd.none, surveyUpdatesToken
I am working on a proper project with all of this included right now which I am happy to share if anyone is interested, it will end up on Github eventually.
@RyanBuzzInteractive I am _thoroughly_ interested in the actor model and would love to check out the project that you're working on. The project that I am working on at my day job will soon be refactored to a service-oriented architecture using the actor model (through Akka.Net) so I've been studying a lot lately. I'm also the sole Xamarin developer so incorporating actors into my mobile app is interesting.
You have the state. What you are missing is the queue, and the thread. The fact that you get a proper model of an indeterminate system.
I have read a bit about actors in the last few months and even went to an actor workshop on a conference recently. I'm certainly a beginner when it comes to actors, but I can appreciate that a proper model of a clearly encapsulated, indeterminate system is a useful abstraction in many contexts - for service-oriented architectures, to ensure concurrent-safe access to mutable state, to create immutable state machines (which AFAIK is the core of MVU - in fact, Elmish used MailboxProcessor previously), etc.
In this particular case, my desired functionality is a Cmd that calls an API and responds with a relevant message, and I want the API request to be cancelable. As my example shows, this can easily be done with simple functions. I don't see what I gain by having "a proper model of an indeterminate system"; it seems like an unnecessary complication in this case. The same goes for the caching. I get why actors are great in many other contexts; I just don't see – again, in these particular cases – what they add over simple functions, that is worth the tradeoff.
I am sure you agree that both in itself and because of the additional message types needed, an actor, modeling an indeterminate system, is a more complex abstraction than a simple function. Actors share some similarities with microservices communicating over some message bus: Both are isolated "processes" that only communicate asynchronously via messages. In light of this, why should I pay the microservice premium when the use-case is so simple that a "monolithic" architecture (normal, synchronous (whether async or not) function calls) solves the problem just as well? (Again, I'm talking about this particular case; I'm not saying there's no place for actors/MailboxProcessors in mobile apps.)
(Oh, and a concrete problem I am struggling with at the moment: Stack traces may end up being much less useful, and debugging is much harder, when using completely asynchronous stuff like MailboxProcessors.)
I'm not sure how you would spin up multiple requests at once with your module approach - you would need more mutable values. You can't instantiate a new module, you can spin up a new actor.
You said your example was single-request only, so that's what I modeled, too. I was unsure if your example was intended to handle generic requests or a specific request, but I guessed the latter and therefore decided to call it something concrete, i.e. SignIn. If I needed to support multiple parallel, cancelable requests (for other use-cases than sign-in), then I might create a solution similar to Elm as previously described, where I pass a tracker string that I can later use to cancel the request. The solution would only be slightly more complex: ConcurrentDictionary<string, CancellationTokenSource> instead of a single CTS, removing it after the request has completed, and adding the tracker string as a parameter to send. I'd say that's like 3–5 additional lines. (Also, the CTS store can then be written once in a dedicated module and used across all requests other cancelable operations.) Then you simply call send as many times as you want with the parameters you want. The string tokens can be stored in the model and, if needed, connected to the UI so that the user can cancel whichever request they desire.
Also threading might be an issue I guess? You could work around all that of course but your solution would soon look a lot more complex than mine I would wager.
I don't see any immediate threading problems in my example, and thus no need for additional complexity. Let me know if I've overlooked anything.
Ok, I think we have probably reached our conclusion here, and I have really enjoyed the chat but we don't want to bore people going around in circles!
Extremely interesting that Elm used a mailbox processor originally rather than the ring bus - my own MVU thing I am using is all using the Mailbox, so I am interested to know why they switched.
What you have described as the solution to a threaded and concurrent requesting problem is exactly what I thought - IMHO a far more complex setup with a mutable dictionary, or that string based approach which is _very limited to this particular application_, not state management in general.
That particular mailbox only dealt with one request at a time, but you could make many of them. They can hold any kind of multidimensional complex state you need, such as the event bus in my cache.
At the end of the day, if you want to use mutable state safely, you need to encapsulate it. If you want to use concurrency you need to duplicate it. If you want to have a single value accessed by multiple threads you would need a queue. And hey presto, you just re-invented the mailbox. You can't avoid it, you can only reinvent it in other ways.
If you are interested, have a peek at the F# guidelines here concerning mutability - pretty strict about how it should be used and contained (i.e. used for performance and very locally contained) : https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/conventions
I'm not encouraging you go crazy and re-architect your application exclusively around mailboxes. I am suggesting you use a built in language construct (they put it in the bcl for a reason) for the job it was intended to do, manage state simply and safely, and I am showing just a few basic ways that it fits neatly into the MVU pattern.
If you prefer the approach you have suggested, that's totally fair - I just think you are painting yourself into a sticky corner you don't need to be in, and I think that by using the mailbox you would make your life simpler, by I respect you don't currently share that opinion.
Thanks again for the conversation, and I have certainly learnt a few important things along the way!
@rastreus Awesome! To be honest you probably know more about it than me if you have been properly studying it. Would be well up for a chat and sharing code. Perhaps we should catch up on Gitter or something to avoid spamming this thread? Drop me an email if you fancy a chat and I will log in :)
Extremely interesting that Elm used a mailbox processor originally rather than the ring bus - my own MVU thing I am using is all using the Mailbox, so I am interested to know why they switched.
Elmish, not Elm. I have no idea what Elm uses. (Not MailboxProcessor of course, since that's F# specific). You can read about the reasons for the switch in this PR and the three linked issues: https://github.com/elmish/elmish/pull/160
What you have described as the solution to a threaded and concurrent requesting problem is exactly what I thought - IMHO a far more complex setup with a mutable dictionary, or that string based approach which is very limited to this particular application, not state management in general.
5 more simple-to-understand lines added to the already simple function-based solution is IMHO not vastly more complex - I'd say it's still very simple. Also, I'm trying to solve this particular case, not state management in general. As I said, I appreciate that actors/queues/etc. is a very powerful pattern and the right tool for the job in many contexts. :)
Thanks for the conversation! I'll make sure to investigate MailboxProcessor a bit more on my own time.
Elm / Elmish - sorry, early morning typo!
Seems like they dropped it because of React and Fable due to a fresh dispatch ref on each loop or something, but it was fine on .NET. At least it wasn't a performance thing, that is what I was concerened about down the line.
Cheers dude!
Just a quick addendum for anyone who gets this far down - I was reading F# Deep Dives which @tpetricek put together with loads of other people - there is a fantastic chapter (8) called 'Asyncronous and Agent Based Programming' which is a great expansion on all the stuff I was on about. The whole book is golden, highly recommended :)
Most helpful comment
That was an interesting article. He makes some points which I am sure are valid on fully implemented actor based systems like Akka running at scale. I also totally agree that there is no point in using an actor if you don't need to manage state, just use a normal function. I think his definition of cache is a bit different to mine, I just mean some data needed at runtime but not stored on disk, or an in-memory reflection of some on-disk data to make access easier and faster, not a static store.
I will start here by saying that I am a fairly junior programmer, about 5 years pro, and only a year on F#, and mostly self taught, so I am probably a little off the mark on some things here and welcome any input from more knowledgeable people.
I think it is initially important to make a distinction between using actors and the actor model of software design.
At a very basic level, an actor is just a recursive loop with a message queue that runs on its own thread - no more, no less. I really like the fact that F# has a simple, lightweight and stripped down implementation included out-of-the-box, the MailboxProcessor. As it just uses a delegate that runs recursively, you can replace the state on each loop, and because it has an inbox you can post it messages and it will work through them as and when it can.
The actor _model_ isn’t a framework, it is a configuration-based model of computation and interaction, as opposed to a traditional state based turing machine model. Frameworks like Akka build on the concepts by providing tools such as an actor registry / resolution system, distribution across machines, failure handling, loads of stuff, but they aren’t the model themselves any more than Fabulous ‘is’ MVU.
If you are interested in the fundamental concepts and power of the model-proper, there is an essential talk by the guy who invented it, Carl Hewitt (interviewed by Eric Meijer) here: https://www.youtube.com/watch?v=7erJ1DV_Tlo
They explain (in an informal way) the essential axioms – that actors can only talk to other actors, and messages can be received at any time but and at most once etc etc. and what this brings to reasoning about interaction. The bit that really struck a chord with me is that it models _indeterminacy_ (as opposed to non-determinacy) in a real way, which you can’t do with direct method calls, and that in turn forces you to consider it in your system design. It makes you think in terms of domain events which can happen in any time in any order. You think about the interaction much more than the state.
There is also a great amount of info here:
https://doc.akka.io/docs/akka/current/guide/actors-intro.html
There is obviously a lot of potential to model complex applications using a full, strict system like this, and arguably you would get more out of it the more you embrace it, much like MVU and FP in general.
However, most of this is _way_ more than I need right now for a mobile app and certainly a whole world away from what I am suggesting in the context of this thread.
I think talking about the actor model is a bit like event sourcing – everyone has a different understanding (or lack thereof) of exactly what it is or how you do it or to what extent.
I have taken the same approach here as I did there – try to understand the fundamental concepts, then implement the bits I understand which are useful in a pragmatic way to improve my daily code, rather than try to go full bore into creating a pure event sourced or actor based application because it is the latest thing and making an expensive mistake, or alternatively ignore it altogether and keep on doing things the inefficient way I had always done them.
A little background: I create Xamarin apps at work, previously in C# exclusively. I received the Reactive Patterns w/ Actors book I linked to earlier as a gift, and found it fascinating although a little heavyweight as Akka is a full framework and overkill for my needs and Scala makes my eyes bleed. Most of the book however is just a general pattern reference, it isn’t that specific to the language or framework.
A little while later I found (fell in love with) F# and started using it for the core Xamarin work. I needed to manage some application state, and the mailbox processor was easy to use and worked really well for many things. It allowed me to make use of many of those patterns where appropriate whilst not marrying a large framework, everything I needed was right there in the language.
It also was a revolution when dealing with the database. I no longer need to worry about concurrent writes. I have a single mailbox which manages the write connection. No more locks or anything. My reads happen using separate actors – I currently have one per table as I have a generic implementation that I instantiate with a return message type. This is my own ad-hoc approach which fits my apps, and is a lot better than what I had before (locking a SQLite connection). I never need to worry about massive parallel access, as my apps don’t do that. If they did, I would research an appropriate solution for that scenario.
The one thing I did find on my last project was that without a formal way of connecting the mailboxes, it can be hard to follow the flow of control for anyone unfamiliar with the architecture in question. That is why I have embraced MVU – it gives a rigid backbone for the application which can call into side-processors as necessary, making it easy to navigate and reason about.
It may not be perfect, and it may not be strict MVU or Actor Model or FP or OO or whatever, but I know why I made every decision (and compromise), it works really well and I have basically no external dependencies. It is the best of all the worlds I know of done the best way I know how, which is all any of us can do hey. I am working on a proper project with all of this included right now which I am happy to share if anyone is interested, it will end up on Github eventually.
Ok, so all of that said, I will reiterate that I am just _simply talking about using the mailbox processor as a state bucket in this context,_ the most basic way you could think of. No big computing model or framework to deal with, just a small recursive loop with a stashed thing in it. The most basic way you can maintain state without a mutable field, and arguably simpler to reason about.
How would I implement the cancellation loop using the cmd I posted previously? Something like this:
```f#
type ApiRequest = ApiRequest
```
I have included the types for completeness, it should actually compile if you import the cmd from earlier in the thread.
As previously mentioned, this example is a 'one request at a time' set-up where each cancels the last, but you could easily expand it, and adapt it to handle any kind of state.
The important bit is how nice it looks in the update func now, and if you strip all the comments out of the mailbox it is only a few lines. You are completely in control of how you use it, that depends on the mailbox body, and the Cmd is very generic, just like OfAsync but with an extra callback message.