I'd be interested in contributing here.
Great! There are at least two problems here we are aware of.
@sergeybykov - thanks for raising the async issue
@vasily-kirichenko - thanks for the fix - i'll try it out
My thoughts on the second issue are as follows:
1) The issue of idiomatic F# over Orleans is quite orthogonal to just the async support. My take on idiomatic F# is that the functional programming model prefers stateless functions operating on data which can be pipelined successively between functions, whilst the Orleans programming model has well encapsulated state and well defined semantics for updating internal state. I'm not sure how we are going to get a solution on this level that is both idiomatically F# and idiomatically Orleans.
2) Digging a level deeper, I think the Orleans _runtime_ has the makings of a streaming data provider, and with judicious use of stateless functions and stateful objects, we might be able to write distributed F# programs that take advantage of the scalability and reliability afforded by the Orleans runtime platform. (There is probably a lot to be gained on this front by looking at competing platforms like Apache Spark, which provide a Scala-based functional programming model over a highly-available redundant data storage layer)
We can also take guidance how existing distributed compute frameworks are surfaced
mbrace and Vagabond and FsPickler
One thing they all have in common is using Computation Expressions to hide all the boiler plate code. Taking place of the code Gen on the C# side.
Another issue is, there is already a concept of an Actors in F# as MailboxProcessors. Ideally we would map, Interface methods to a DU.
For example the Game Grain could be mapped to
type gamegrain =
| AddPlayer of Guid
| GetState of AsyncReplyChannel
| GetMoves of AsyncReplyChannel
| SetName of string
| ....
@TonyAbell +1 :)
@TonyAbell
One thing they all have in common is using Computation Expressions to hide all the boiler plate code. Taking place of the code Gen on the C# side.
Ye, but that requires having a non-static way to define an actors. Something which Orleans is currently lacking. You will need to define an interface first using C# and that immediately knocks-off F# fans :)
Another issue is, there is already a concept of an Actors in F# as MailboxProcessors. Ideally we would map, Interface methods to a DU.
'MailboxProcessor' and Akka's actor API - have a _uniform interface_. I'm not sure what will be the point of creating a mapper which will map from uniform interface to a non-uniform and vice versa. You either have one or another.
DU - is a Message. You do Message Handler selection yourself (via pattern matching). Which means that all of your _functional_ grain interfaces will ever have 2 fully generic methods, at max. Something like this:
Task ReceiveTell(TRequest m);
Task<TResponse> ReceiveAsk(TRequest m);
Which raises a point: why you need to define any interfaces at all?!?
@johnazariah We have tried Async.AwaitTask and it works perfectly. We've also introduced Async.AwaitVoidTask to await Tasks, that don't return results. And it covers the whole F# TPL-based async story. That is all.
I believe @alan-geller also had some thoughts about F# integration.
In the filthy hack code, on which scheduler does the last Async.StartAsTask run? If my memory serves me well (well, it can fail too), it doesn't run in the Orleans scheduler and according to documentation there isn't a way to specify a scheduler using Async like this.
@yevhen , @veikkoeeva that's precisely the issue - the Async.StartAsTask runs on a different scheduler as does the async {} monad, so making a grain call another grain (asynchronously) in F# does not work out of the box if you use the idiomatic async {} monad to sequence the calls...
@yevhen I think that having a way to create ad-hoc actor implementations (a-la mbrace, akka,net) and having a way to dynamically distribute them over the Orleans run-time so that you get the best of both worlds looks like the desired outcome! For this, there will have to be a way to dynamically create Orleans objects (with or without interfaces to define the capabilities) and ship them over the runtime...
maybe we could implement an orleansasync computational expression with the desired behavior.
@TonyAbell that's the simplest fix I could think of for the first issue, but perhaps vasily's solution is equally effective! :)
@johnazariah
I think that having a way to create ad-hoc actor implementations (a-la mbrace, akka,net) and having a way to dynamically distribute them over the Orleans run-time so that you get the best of both worlds looks like the desired outcome! For this, there will have to be a way to dynamically create Orleans objects (with or without interfaces to define the capabilities) and ship them over the runtime...
We're working on this :) ...
@yevhen :+1:
@johnazariah have you looked into ActorFx? Maybe what you're looking for lies in the intersection between ActorFx and Orleans. The model can certainly be hosted within Orleans, with a little finagling. @sergeybykov, @gabikliot & I were talking about this a few days ago, actually.
We could have a generic, delegating implementation of an interface and provide a Become or SetImplementation method on it which takes a runtime implementation of its interface - certainly we would need to come up with a solid design, but it would be a heck of a lot of fun to work on :)
@reubenbond I feel another learning session coming on :)
ActorFx seems dead or forgotten as the last check-in was Mar 19, 2014
How would you compare/contrast the ActorFx / m-brace frameworks.
I'm not familiar enough with m-brace (@johnazariah knows more), but ActorFx provides a system for dynamically creating actors at runtime using a variety of languages (.NET, JavaScript, Python, afaik).
Actors can have their state automatically replicated (I believe they replicate the state of each actor after each method invocation, but I don't know). They are observable (actors can subscribe to other actors). And they are referenced by name.
It may not be active any more, but there are some concepts and possibly code which can be lifted & shifted into Orleans - perhaps as an optional dependency.
M-Brace looks like a distributed dataflow system, so maybe it's more similar to EventHubs than Orleans. Orleans could provide this model using steams as edges & grains as nodes.
Does M-Brace checkpoint flows, or are they restarted from scratch when there's a failure?
Does M-Brace checkpoint flows, or are they restarted from scratch when there's a failure?
Taking a quick look at the docs it looks like the flows aren't explicitly checkpointed. From the Programming Model page
Clearly, one of the child computations will fail on account of an invalid url, creating an exception. In general, uncaught exceptions bubble up through Cloud.Parallel triggering cancellation of all outstanding child computations (just like Async.Parallel).
The interesting bit here is that the exception handling clause will almost certainly be executed in a different machine than the one in which it was originally thrown. This is due to the interpreted nature of the monadic skeleton of cloud workflows, which allows exceptions, environments, closures to be passed around worker machines in a seemingly transparent manner.
The MBrace manual goes deeper into details on what happens upon node failures or exceptions.
An important feature supported by cloud workflows is exception handling. Exceptions can be raised and caught in cloud workflows just as in any other piece of F# or .NET code.
cloud {
try
let! x = cloud { return 1 / 0 }
return x + y
with :? System.DivideByZeroException as e ->
// log and reraise
do! Cloud.Logf "error: %O" e
return raise e
}
This snippet will execute in the anticipated fashion, recording a message to the log before completing the computation by re-raising the exception. It is, however, the distributed nature of MBrace that makes its exception handling mechanism particularly interesting. With cloud workflows, the symbolic execution stack winds across multiple machines. Thus as exceptions are raised, they are passed around multiple nodes before they are, if ever, returned to the user as a failed
computation result.This is a good demonstration of what is one of the strengths of MBrace. Error handling in particular and computation state in general have a global and hierarchical scope rather than one that is fragmented and localized. This is achieved thanks to symbolic and distributed interpretation of what is known as monadic trampolines[10, 11], also known as the
βmonadic skeletonβ of a cloud workflow.
Having this mind, I was a few weeks ago thinking about MBrace and using Recalled to checkpoint calculations. There's a video of Recalled (1:15:55 onwards) and I was quite impressed in Helsinki F# meetup of the performance that saturated the memory bus when doing (arbitrary) checkpointed calculations. Recalled is imporessive, as is Hopac (of which @vasily-kirichenko knows more) on which it's built on.
As an aside, it would be interesting to combine MBrace, Recalled and something like FSCL (OpenCL accelerated computing, say, with FPGAs) and with a quick look it's like Spark on steroids, only in F# and .NET. Naturally with Orleans (somehow).
/cc @VesaKarvonen
As an aside, it would be interesting to combine MBrace, Recalled and something like FSCL (OpenCL accelerated computing, say, with FPGAs) and with a quick look it's like Spark on steroids, only in F# and .NET. Naturally with Orleans (somehow).
I don't see why we need Orleans in this mix at all :) MBrace has its own integration with Azure.
+1
ΠΏΠΎΠ½Π΅Π΄Π΅Π»ΡΠ½ΠΈΠΊ, 2 ΡΠ΅Π²ΡΠ°Π»Ρ 2015 Π³. ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ Vasily Kirichenko Π½Π°ΠΏΠΈΡΠ°Π»:
As an aside, it would be interesting to combine MBrace, Recalled and
something like FSCL (OpenCL accelerated computing, say, with FPGAs) and
with a quick look it's like Spark on steroids, only in F# and .NET.
Naturally with Orleans (somehow).I don't see why we need Orleans in this mix at all :) MBrace has its own
integration with Azure.β
Reply to this email directly or view it on GitHub
https://github.com/dotnet/orleans/issues/38#issuecomment-72427861.
I don't see why we need Orleans in this mix at all :) MBrace has its own integration with Azure.
Orleans doesn't look like being a good fit for computationally heavy, or algorithmic work, but fits well for more "infrastructure kind" of work, I feel. Plus I'm still a lot more skilled cranking C# code, like many others. :)
For what its worth, I contemplated on creating an on-premises cluster of GPU boxes (or FPGAs if the prices of OpenCL accompanied ones come down) as there isn't GPGPU in Azure and use this from MBrace. Then I'd farm out computational work from an Orleans cluster. Naturally it's a long way to action here, but one can always envision...
@vasily-kirichenko Orleans has more than the function shipping element. For example, mbrace's data sharing mechanism is though external means (like Azure storage) which isn't great for data locality! Orleans also has built-in mechanisms for dealing with dynamic scale in terms of compute...
I've glanced over this thread and have one particular request
There are a number of references to the interaction between F# Async and custom task schedulers, e.g. manifest here: https://gist.github.com/johnazariah/527287a190a14a73b35d#file-callergrain-fs with a link to a possible adjustment to FSharp.Core here: https://github.com/fsprojects/fsharpx/blob/7246e314e3fdeae07f3465b9126f2bc22faa0cd5/src/FSharpx.Core/Async.fs#L43
What I'd like to ask is - should we be making a fix and/or extension to FSharp.Core here for 4.4.0.0 to address this problem? Would adding a new overload to AwaitTask be appropriate?
In the meantime, is the linked FSHarpx method (copied below) a sufficient workaround?
type Async =
static member AwaitTask(task:Tasks.Task, ?cancellationToken) =
let cancel = defaultArg cancellationToken Async.DefaultCancellationToken
Async.AwaitTask (task.ContinueWith<unit>((fun t -> ()), cancel))
Please give your feedback ASAP by proposing a course of action at http://github.com/Microsoft/visualfsharp
That AwaitTask swallows exceptions thrown by Task:
module AwaitVoidTask
open System.Threading.Tasks
open System.Threading
type Async with
static member AwaitVoidTask(task: Task, ?cancellationToken: CancellationToken) =
let cancel = defaultArg cancellationToken Async.DefaultCancellationToken
Async.AwaitTask <| task.ContinueWith<_>((fun _ -> ()), cancel)
static member AwaitVoidTask'(task: Task, ?cancellationToken: CancellationToken) =
let cancel = defaultArg cancellationToken Async.DefaultCancellationToken
Async.AwaitTask <| task.ContinueWith<_>((fun t ->
if t.IsFaulted then raise t.Exception
else ()), cancel)
Task.Factory.StartNew (fun () -> failwith "error")
|> Async.AwaitVoidTask
|> Async.RunSynchronously
// val it : unit = ()
Task.Factory.StartNew (fun () -> failwith "error")
|> Async.AwaitVoidTask'
|> Async.RunSynchronously
(* >
System.AggregateException: ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΠ΄Π½Π° ΠΈΠ»ΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΎΡΠΈΠ±ΠΎΠΊ. ---> System.AggregateException: ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΠ΄Π½Π° ΠΈΠ»ΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΎΡΠΈΠ±ΠΎΠΊ. ---> System.Exception: error
at [email protected]() in D:\git\HopacTest\HopacTest\AwaitVoidTask.fs:line 23
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at FSI_0006.Async-AwaitVoidTask'[email protected](Task arg) in D:\git\HopacTest\HopacTest\AwaitVoidTask.fs:line 14
at System.Threading.Tasks.ContinuationResultTaskFromTask`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)
at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at <StartupCode$FSI_0011>.$FSI_0011.main@()
---> (Inner Exception #0) System.AggregateException: ΠΡΠΎΠΈΠ·ΠΎΡΠ»Π° ΠΎΠ΄Π½Π° ΠΈΠ»ΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΎΡΠΈΠ±ΠΎΠΊ. ---> System.Exception: error
at [email protected]() in D:\git\HopacTest\HopacTest\AwaitVoidTask.fs:line 23
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
> --- End of inner exception stack trace ---
at FSI_0006.Async-AwaitVoidTask'[email protected](Task arg) in D:\git\HopacTest\HopacTest\AwaitVoidTask.fs:line 14
at System.Threading.Tasks.ContinuationResultTaskFromTask`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
---> (Inner Exception #0) System.Exception: error
at [email protected]() in D:\git\HopacTest\HopacTest\AwaitVoidTask.fs:line 23
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()<---
<---
Stopped due to error
*)
So I suggest to use the latter func instead of the one from FSharpx.Core. I'm not sure whether this approach preserves full stack trace, if it does not, we should do something with it (all solutions I've seen is very hacky though).
I remember Vladimir Matveev, or desco provided an alternative implementation at SO thread Async.Await not catching Task exception. I'll just replicate it here (but see the comment made by eulerfx on the original thread):
open System.Threading.Tasks
module Async =
let AwaitTask (t: Task) =
Async.FromContinuations(fun (s, e, c) ->
t.ContinueWith(fun t ->
if t.IsCompleted then s()
elif t.IsFaulted then e(t.Exception)
else c(System.OperationCanceledException())
)
|> ignore
)
I had a related question Translating async-await C# code to F# with respect to the scheduler and I got quite afar with Orleans and F# by using FSharpx. Ultimately, though, when I got into more complex code, I got the same code running more smoothly just with plain C# (I'm a rather n00b with F#).
_I think_ one of the problems I came across with FSharpx was that it called task.Result in the TaskBuilder, which caused a deadlock in the grains code. Then a myriad of other things, one of which was this awaiting tasks. Unfortunately I haven't had time to go deeper into this.
Calling Task.Result or Task.Wait() on an uncompleted task would definitely be a problem as it would block the thread and violate the very thing we are trying to do in the scheduler - run lots of non-blocking async operations on a small one-thread-per-core thread pool.
I don't see why we need Orleans in this mix at all :) MBrace has its own integration with Azure.
Orleans doesn't look like being a good fit for computationally heavy, or algorithmic work, but fits well for more "infrastructure kind" of work, I feel. Plus I'm still a lot more skilled cranking C# code, like many others. :) For what its worth, I contemplated on creating an on-premises cluster of GPU boxes (or FPGAs if the prices of OpenCL accompanied ones come down) as there isn't GPGPU in Azure and use this from MBrace. Then I'd farm out computational work from an Orleans cluster. Naturally it's a long way to action here, but one can always envision...
Albeit off-topic, but as mentioned already, I'll add a quote here OneNet looks like being just this. Would this be a reason to start learning F# in anger and perhaps Recalled and Hopac too? :) Interesting video from 1:16 onwards. Perhaps Microsoft should hire Vesa to do some OneNet work -- especially as he understands both hardware and software, library writing, theoreticals and who knows what (yes, I praise too much, it's just that those libraries feel awesome still). /cc @VesaKarvonen
Hello all, sorry I haven't yet read this thread entirely, I'm willing to push some efforts on this task to help have F# as a standard supported language for all aspects of Orleans.
UPDATE: It seems there are a lot of discussion going on about the serialization and generating dynamic assemblies to replace static code generation, so I'm unsure if supporting other languages the way it is done for the 3 supported is going to see major investments until this decision gets sorted out...
Right now I've worked on adding project template for F# Grain interfaces and looking at the unimplemented methods in FSharpCodeGenrator.
Regarding all the discussions about diverging from what C#/VB.NET are doing, I think it would be better in a first release of full F# support to just do what is done in the two other languages:
Once we have that, and the complete set of project templates, F# will be a first class citizen and we can consider extending the code generator to make it expose other ways to interact with the grain in a F# idiomatic way.
My two cents.
For now I'm experimenting with adding crude implementation of missing methods in code generator, to make the Grain Interface project template.
Please let me know if those efforts are useless or if we agree that first milestone is having F# exposing same features as the two other languages, even if it's not the most polished.
Thanks for your feedback.
That's a great plan @smoothdeveloper! We are all for it. And thank you for your help!
@smoothdeveloper I agree. Thank you!
It looks like the incompatibility with async/await and TPL custom schedulers is the immediate blocker here. It causes a deadlock in a grain that tries to make an async call, which appears like an F# issue that @dsyme, @vasily-kirichenko, and @veikkoeeva were discussing above.
Just don't use async {}. Instead use task {}, That is all. It works 100% and code looks much better. It's absolutely the same and don't require useless conversion from TPL to Async, back and forth.
@yevhen I think this entire conversation is about _proper_ or _idiomatic_ F# support. It's clear that we can use Tasks directly (or create a syntactic sugar in form of a task {} CE), but nobody does this in F#. TPL is rarely used (directly).
@vasily-kirichenko Are you saying it's not worth it to fix the TPL integration issue before/in parallel with the idiomatically proper integration with F#?
@sergeybykov @vasily-kirichenko @yevhen I very much support the plan outlined above - to first get "working" F# support in Orleans, and then progress to consider "idiomatic" F# support.
The F# computation expression support was definitely designed with permitting task { ... } in mind. We never defined a task-builder because it isn't often needed in F#. However it seems it is needed when interacting with frameworks that make heavy use of customized TPL task schedulers - of which Orleans is the most significant at the moment.
So in this context it's fine to define and use a task { .. } builder.
@gabikliot short notice on what I'm up to so far:
FSharpCodeGenerator class to replace the NotImplementedException with stubs to allow me to step through the codeCodeSnippetTypeMember in GetBasicReferenceMethod, this apparently get called through the logic in base classOutput method to itterate through the ReferencedNamespace.Types and their members to append to the stream.So it seems I'm able to get started with trying to hack that side of the code generation, and will see if I can get it to a point where I have code that compiles for the FSharpHelloWorld project.
I'll push a branch in my fork to get help / review from you guys and keep track of small progresses I can make.
Basic question, if my changes break something in the FSharp grain implementation side, is there any tests that would light up? Are there any tests for the ClientGenerator project? I don't see anything in the main solution in "Show Project Dependency Diagram".
@smoothdeveloper Take a note of some issues mentioned earlies with regard to plain Tasks and FSharpx task builder. I may have used task builder wrong as I got code using it deadlock easier than just with 'await' (running Orleans with, say, XS PaaS instances).
Otherwise I'm all for F# if I can feel sure that when I code the execution doesn't leak off from the Orleans scheduler. Maybe this is even easier with F# when the groundwork has been laid down.
@sergeybykov A tangential question, what's the feeling if one were to introduce something like F# for testing or Fake for building, that is more on the development process and perhaps some helper libraries? I'm not suggesting, but just probing the general feeling if in the future someone would have something that useful in Orleans.
@veikkoeeva Personally, I'd like to learn more about both. F#-based tests seem like all goodness. I assume we'll need to fix the Task issue first because most meaningful tests (whether we call them unit or integration tests :-)) will involve making calls to grains and awaiting results.
Is Fake in any way compatible with MSBuild/VS? With all idiosyncrasies of MSBuild, it is relatively easy to have a nice F5 experience. How does it work with Fake?
If there is enough interest, we should probably create a separate issue for this.
@sergeybykov regarding FAKE, it's very easy to call a solution build from it, it also figures out the evironment paths, it means you can run it from a console.
At my work, it took me very short time to get started with FAKE scripts to do tasks like building, running tests, creating database, generating code.
The main advantage is that it's basically F# code using a nice helper library, so when you edit the script in VS you get feedback if you type something which doesn't make sense.
@smoothdeveloper The plan sounds good.
Regarding the tests, we do have some, but we need to clean them up first. They are mixing code gen testing and programing model testing. We'll do it, but it will take some time. For now, you can use the FSharpHelloWorld sample and the existing unit tests and samples.
T4-like templating tool with support for F#. Might be useful if not going for F# Compilers Services ("Roslyn") route.
How is the current state?
@ShalokShalom Support for F# types has improved (though F# specific types aren't generated) and https://github.com/OrleansContrib/Orleankka has progressed significantly and is used in production. The Orleankka people likely have the most accurate picture with the current usage. @ReubenBond can probably tell something about the ideas with Roslyn. As you know already (from F# GH issues), there is this Roslyn, project spaces etc. thingamajig that would make the overall picture more seamless.
In addition to Orleankka, we have an F# sample in the Samples directory which makes use of a task Computation Expression from Giraffe for async. As far as code generation is concerned, there are two options:
[KnownAssembly("MyFSharpProject")] attribute and including the Microsoft.Orleans.OrleansCodeGenerator.Build package,WithCodeGeneration() extension method on IApplicationPartsManager: http://dotnet.github.io/orleans/Documentation/2.0/Configuration2.0.html#application-partsApache Storm uses OS isolation and Akka pure cooperative objects on OS threads.
How does Orlean do this?
@ShalokShalom do you mean to ask, "How does Orleans isolate user grains from each other?"
That's not related to F# support, but we don't use any strict enforcement. Instead, we make doing so unnatural. There is no natural way for grains to pass CLR references to each other. Of course, any model can be circumvented - but that has never been an issue as far as I'm aware. That is, we've never seen a user somehow unintentionally have grains directly reference each other, breaking isolation. If you'd like, we can discuss this in Gitter: https://gitter.im/dotnet/orleans
EDIT: in addition to the above, there are also checks to guard against undesired thread-hopping. For example, if you attempt to access something while not executing under the correct context, then an exception is thrown.
I've been trying to get a simple hello world sample running in F#, and have run into this very ridiculous problem:
[<Interface>]
type IHandShakeGrain =
abstract DoHandShake: name: string -> Task<string>
type HandShakeGrainState() =
member val LastHandShake = "" with get, set
type HandShakeGrain() =
inherit Grain<HandShakeGrainState>()
interface IHandShakeGrain with
member me.DoHandShake name = task {
me.State.LastHandShake <- name
do! me.WriteStateAsync()
return (sprintf "Hello, %s" name)
}
results in:
FS0491 The member or object constructor 'State' is not accessible. Private members may only be accessed from within the declaring type. Protected members may only be accessed from an extending type and cannot be accessed from inner lambda expressions.
on both the assignment and the WriteState call. This is because computation expressions are translated into a whole lot of lambdas, and private stuff is not accessible there it seems.
There is no grain state in the F# sample. How would we get around this?
@Arshia001
member this.MyWriteState() = this.WriteState()
then use MyWriteState in the interface implementation. I did the same when I implemented a custom stage in Akka.Net. Yes, itβs ugly, but it works.
@vasily-kirichenko I prefer (a generic version of) this one, which has the added benefit of forcing you to make a decision about serializing state when it changes:
member me.ModifyState (f: HandShakeGrainState -> unit) =
f me.State
me.WriteStateAsync()
member me.ModifyState (f: HandShakeGrainState -> bool) =
if f me.State then me.WriteStateAsync() else Task.CompletedTask
interface IHandShakeGrain with
member me.DoHandShake name = task {
do! me.ModifyState (fun s ->
if s.LastHandShake <> name then
s.LastHandShake <- name
true
else false
)
return (sprintf "Hello, %s" name)
}
@Arshia001
member this.MyWriteState() = this.WriteState()
then use MyWriteState in the interface implementation. I did the same when I implemented a custom stage in Akka.Net. Yes, itβs ugly, but it works.
With the [<PersistentState("State")>] attribute and IPersistentState<'T> types you no longer need to do these kind of hacks. It makes the usage from F# significantly cleaner.
BTW, I also think Orleans' programming model is in complete contrast to idiomatic F# (fully object-oriented vs fully functional). If we had a way to separate grain methods out into functions and have grain data become record types, that'd be what F# is all about.
I'm thinking something like this would work:
// In Orleans:
type GrainModuleAttribute(transientState: System.Type, persistedState: System.Type) = inherit System.Attribute()
type GrainData<'Transient, 'Persisted> = { transient: 'Transient; persisted: 'Persisted }
// In user code:
type MyGrain = { SomeTransientData: int }
type MyGrainState = { SomePresistedData: string }
[<GrainModule(typeof<MyGrain>, typeof<MyGrainState>)>]
module MyGrainMethods =
let someFunc myData param1 = async {
let result = myData.transient.SomeTransientData + param1
return { myData with transient = { SomeTransientData = 10 }}, result
}
@ashtonkj I've never heard of those. Care to shed some light?
I can't find a sample in the Orleans samples, but here is one I use in a personal project
public class FeatureManagementGrain : Grain, IFeatureManagementGrain
{
private readonly IPersistentState<FeatureManagementState> m_State;
private IManagementMonitorGrain m_ManagementMonitorGrain;
private bool GetStatusOrDefault(string featureName)
{
if (m_State.State.Features.ContainsKey(featureName))
{
return m_State.State.Features[featureName];
}
else
{
return false;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="FeatureManagementGrain"/> class.
/// </summary>
/// <param name="state">The state.</param>
public FeatureManagementGrain([PersistentState("State")]IPersistentState<FeatureManagementState> state)
{
m_State = state;
}
/// <summary>
/// This method is called at the end of the process of activating a grain.
/// It is called before any messages have been dispatched to the grain.
/// For grains with declared persistent state, this method is called after the State property has been populated.
/// </summary>
/// <returns></returns>
public override Task OnActivateAsync()
{
m_ManagementMonitorGrain = GrainFactory.GetGrain<IManagementMonitorGrain>(0);
return base.OnActivateAsync();
}
/// <summary>
/// Adds the feature asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <param name="enabled">if set to <c>true</c> [enabled].</param>
/// <returns></returns>
public async Task AddFeatureAsync(string featureName, bool enabled)
{
m_State.State.Features[featureName] = enabled;
await m_State.WriteStateAsync();
await m_ManagementMonitorGrain.RecordFeatureAddedAsync(featureName, enabled);
}
/// <summary>
/// Disables the feature asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <returns></returns>
public async Task DisableFeatureAsync(string featureName)
{
var oldStatus = GetStatusOrDefault(featureName);
if (oldStatus)
{
m_State.State.Features[featureName] = false;
await m_State.WriteStateAsync();
await m_ManagementMonitorGrain.RecordFeatureStatusChangedAsync(featureName, oldStatus, false);
}
}
/// <summary>
/// Enables the feature asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <returns></returns>
public async Task EnableFeatureAsync(string featureName)
{
var oldStatus = GetStatusOrDefault(featureName);
if (!oldStatus)
{
m_State.State.Features[featureName] = true;
await m_State.WriteStateAsync();
await m_ManagementMonitorGrain.RecordFeatureStatusChangedAsync(featureName, oldStatus, true);
}
}
/// <summary>
/// Gets the features asynchronously.
/// </summary>
/// <returns></returns>
public Task<IDictionary<string, bool>> GetFeaturesAsync()
{
return Task.FromResult(m_State.State.Features);
}
/// <summary>
/// Gets the feature status asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <returns></returns>
public Task<bool> GetFeatureStatusAsync(string featureName)
{
return Task.FromResult(GetStatusOrDefault(featureName));
}
/// <summary>
/// Toggles the feature asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <returns></returns>
public async Task ToggleFeatureAsync(string featureName)
{
var oldStatus = GetStatusOrDefault(featureName);
m_State.State.Features[featureName] = !oldStatus;
await m_State.WriteStateAsync();
await m_ManagementMonitorGrain.RecordFeatureStatusChangedAsync(featureName, oldStatus, !oldStatus);
}
/// <summary>
/// Removes the feature asynchronously.
/// </summary>
/// <param name="featureName">Name of the feature.</param>
/// <returns></returns>
public async Task RemoveFeatureAsync(string featureName)
{
if (m_State.State.Features.ContainsKey(featureName))
{
m_State.State.Features.Remove(featureName);
await m_State.WriteStateAsync();
await m_ManagementMonitorGrain.RecordFeatureRemovedAsync(featureName);
}
}
}
They must be new then, I'm not seeing them in 2.2.0. Anyway, F# looks like it should be a perfect match for any distributed computation scenario. This issue is over 4 years old, but is the feature still being considered? If it is, I'd love to help make it happen.
@Arshia001 We are very open to contributions towards making Orleans more F# friendly. If there's something that could be migrated down from Orleankka, that would benefit both projects. @yevhen was interested in that for a long time now. Any help is more than welcome.
I am happy to help as well.
I'm happy to help as well!
On Tue, Jul 16, 2019, 01:11 Sergey Bykov notifications@github.com wrote:
@Arshia001 https://github.com/Arshia001 We are very open to
contributions towards making Orleans more F# friendly. If there's something
that could be migrated down from Orleankka
https://github.com/orleanscontrib/orleankka, that would benefit both
projects. @yevhen https://github.com/yevhen was interested in that for
a long time now. Any help is more than welcome.β
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/orleans/issues/38?email_source=notifications&email_token=AAGKWZZEHMENNU6QIGFPR63P7THGJA5CNFSM4A3BUM32YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZ6X7KY#issuecomment-511541163,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGKWZYIMKW2RHS4YYQVLJLP7THGJANCNFSM4A3BUM3Q
.
@sergeybykov merging ideas/code from Orleankka up to Orleans is what I've always been dreaming)
@sergeybykov I'd love to do just that. I'm currently coding a toy project with F#, noting problems I run into along the way. IMHO the first thing that should be corrected in some way is that Orleans is the polar opposite of idiomatic F#. We need a functional interface to Orleans.
I can start looking into the details by myself, but I don't think soloing the design would be a good idea. I think we should all collaborate on deciding what it's going to look like, and then some of us can start working towards an implementation. I'm eagerly waiting for your opinion on how to proceed.
Just out of curiosity: Why does somebody decide to implement a project with a huge focus on concurrency with primarily object-oriented techniques?
Based on the pure properties of object-oriented/imperative code, it seems rather counter-intuitive to base state-sensitive code on it.
Is there any scientific reasoning behind this?
@ShalokShalom that sort of question would be much better suited for a separate, dedicated discussion.
@Arshia001
I can start looking into the details by myself, but I don't think soloing the design would be a good idea. I think we should all collaborate on deciding what it's going to look like, and then some of us can start working towards an implementation. I'm eagerly waiting for your opinion on how to proceed.
Great! I think opening issues to discuss specific design ideas is the way to go, so that all interested parties can bring their feedback before those ideas are turned into PRs.
@ShalokShalom I've in fact been thinking about that since last night. Mutations (as one of the primary properties of OO code) are dangerous because they could cause invalid state, and because they could interfere with concurrency.
With Orleans, concurrency is not a concern, as grains are single-threaded. You simply can't have a bad lock or a race condition. That leaves us with invalid state, which may very well exist even in pure functional code, unless you disallow it at the type level.
Then there's pureness: functional code is supposed to be side-effect free. This is not true of Orleans grains, which may cause cascading state updates with a single call, so you can get side-effects:
// pseudo-code!
class Grain1
{
int i;
void Increment() => ++i;
}
class Grain2
{
void IAmHarmless(int i) => GetGrain<Grain1>(i).Increment();
}
var g1 = GetGrain<Grain1>(0);
var g2 = GetGrain<Grain2>(0);
g2.IAmHarmless(0);
However, I don't think it's reasonable or at all possible to have a pure version of Orleans. Besides, Orleans is an actor model framework, not a functional programming framework. Those are two different things, so we're basically comparing apples and oranges here.
The question we should ask is this: why would F# be a better match for Orleans? Here are a few points I came up with:
I have a feeling I'm forgetting some key points, but all in all, I think an F# interface to Orleans would be a good thing to have. This doesn't mean C# is a bad choice of language, F# just sounds better ATM.
@sergeybykov @jyizheng @johnazariah @yevhen I just posted some ideas on this subject over at #5772, I'd love your feedback on it.
Most helpful comment
@sergeybykov I'd love to do just that. I'm currently coding a toy project with F#, noting problems I run into along the way. IMHO the first thing that should be corrected in some way is that Orleans is the polar opposite of idiomatic F#. We need a functional interface to Orleans.
I can start looking into the details by myself, but I don't think soloing the design would be a good idea. I think we should all collaborate on deciding what it's going to look like, and then some of us can start working towards an implementation. I'm eagerly waiting for your opinion on how to proceed.