3

Is there a language difference between F# and C# here that is blocking the use of Microsoft Coyote from F# code?

Because the OnEventDoActionAttribute is defined in the inherited type Microsoft.Coyote.Actors.Actor with a protected access modifier, it looks like it is still accessible in inherited actor types in C# but not so in F#.

Hello world sample converted to F#:

type SetupEvent(serverId : ActorId) =
    inherit Event()
    member this.ServerId = serverId

type PingEvent(callerId : ActorId) = 
    inherit Event()
    member this.Caller = callerId

type PongEvent() =
    inherit Event()

// both attribute spec fail
//[<Microsoft.Coyote.Actors.Actor.OnEventDoAction(typeof<PingEvent>, "HandlePing")>] // Error: The type 'OnEventDoActionAttribute' is not accessible from this code location
[<OnEventDoAction(typeof<PingEvent>, "HandlePing")>] // Error: The type 'OnEventDoAction' is not defined
type Server() =
    inherit Actor()

    member this.HandlePing(e : Event) =
        let ping = e :?> PingEvent
        printfn "Server handling ping"
        printfn "Server sending pong back to caller"
        this.SendEvent(ping.Caller, new PongEvent());

// both attribute spec fail
//[<Microsoft.Coyote.Actors.Actor.OnEventDoAction(typeof<PongEvent>, "HandlePong")>] // Error: The type 'OnEventDoActionAttribute' is not accessible from this code location
[<OnEventDoAction(typeof<PongEvent>, "HandlePong")>] // Error: The type 'OnEventDoAction' is not defined
type Client() =

    inherit Actor()

    let mutable serverId : ActorId = null

    override this.OnInitializeAsync(initialEvent : Event) : System.Threading.Tasks.Task =
        printfn "%A initializing" this.Id
        serverId <- (initialEvent :?> SetupEvent).ServerId
        printfn "%A sending ping event to server" this.Id
        this.SendEvent(serverId, new PingEvent(this.Id))
        base.OnInitializeAsync(initialEvent)

    member this.HandlePong() =
        printfn "%A received pong event" this.Id

[<Test>]
let Execute (runtime : IActorRuntime) =
    let serverId = runtime.CreateActor(typeof<Server>)
    runtime.CreateActor(typeof<Client>, new SetupEvent(serverId)) |> ignore
    runtime.CreateActor(typeof<Client>, new SetupEvent(serverId)) |> ignore
    runtime.CreateActor(typeof<Client>, new SetupEvent(serverId)) |> ignore

let runtime = RuntimeFactory.Create()
Execute(runtime) |> ignore
Console.ReadLine() |> ignore

Not sure what to do in order to work around this issue.

LINQPad document URI to try out the code directly: http://share.linqpad.net/a9rif7.linq

Bent Rasmussen
  • 5,538
  • 9
  • 44
  • 63

2 Answers2

4

Yes it was to reduce scope pollution, and to ensure you only get intellisense for these types when you are in a valid place to use them inside Visual Studio. Cool idea to build an F# example though...

Chris
  • 1,101
  • 8
  • 13
  • 1
    Reasonable choice when working with C# semantics - and I didn't expect a language difference here - but perhaps this implementation characteristic could be reconsidered if there are no other more intrinsic reason that make it infeasible for F# use. – Bent Rasmussen May 10 '20 at 09:09
  • I don't know F# very well but there is no way to bridge these nested types in a way for F# to be able to use them? I don't really want to pollute the C# namespace in order to support F#... – Chris May 13 '20 at 00:23
  • 1
    Cross-reference to F# compiler issue: https://github.com/dotnet/fsharp/issues/3172. – Bent Rasmussen May 14 '20 at 13:38
3

Unfortunately, they declared the attribute to be a protected sealed nested inside Actor. While F# cannot declare anything as protected, it can follow access restriction - usually people enforce access restriction for a reason.

The other way you could have done this is to have a top-level class which inherits from Actor and implement the clients as nested classes. But F# doesn't support nesting classes either.

I don't see any particular reason for it to be declared protected other than scope pollution.

Probably forking and changing the access modifier is the easiest option right now.

Asti
  • 12,447
  • 29
  • 38
  • Afaik, F# can consume protected members, it just can't declare them. So this is a bug in F# wrt attributes. – Abel May 14 '20 at 18:49
  • This probably was an added feature in C#. It shouldn't behave any differently, so yeah it's a bug. – Asti May 14 '20 at 20:01