An F# implementation of something DCI-ish

66 views
Skip to first unread message

Rune Lund-Søltoft

unread,
Sep 8, 2021, 5:17:11 AM9/8/21
to object-co...@googlegroups.com
Hi,
It's been a long time with not too much activity here but I thought I'd share some reason work with you. As some of you know my preferred læanguage is F# and it's felt like it should be possible to support something like DCI. "Like" because due to the limitations of the CLR I can't alter objects. However I've found a way to get pretty close to the gold standards of trygve. As far as I can see there's one way to break the abstractions in my library but that is limited to the code that declares the role methods. Outside of that declaration I have not been able to break the identity trick that I've used. (Though I have not tried djikstra yet). The MoenyTransfer could look something like this:

type FromRole =
abstract Withdraw : (string * decimal) -> decimal option
type SinkRole =
abstract Accept : decimal -> string option

type Account() =
let mutable balance = 0m
member __.Balance with get() = balance
member __.Deposit(amount) = balance <- balance + amount
member __.Withdraw(amount) = balance <- balance - amount
type MoneyTransferContext(fromAccount : Account,``to`` : Account) =
let from : FromRole =
Role() {
bind fromAccount
define <@ fun f -> f.Withdraw @> (fun this ->
fun (msg,amount) ->
if this.Balance >= amount then
this.Withdraw amount
printfn "%s" msg
Some amount
else
None
)
}
let sink : SinkRole =
let acc = Account()
Role() {
bind ``to``
define <@ fun acc -> acc.Accept @> (fun this ->
fun amount ->
this.Deposit amount
Some <| sprintf "Deposited %.2M. Balance is %.2M" amount this.Balance
)
}
member __.Execute(msg,amount) =
match from.Withdraw(msg,amount)
|> Option.bind sink.Accept with
None -> printfn "Insufficient funds"
| Some msg -> printfn "%s" msg

There are some issues with the signatures.How you define a role method is weird. First you provide the name and signature by a quotation <@ fun f -> f.Withdraw @> then you provide a function that takes 'this' as the only argument and returns the function used as the method. This is not as readable as I would like it to be. The upside is that the type inference/type checking does a lot of heavy lifting for me. 

Matthew Browne

unread,
Sep 8, 2021, 6:56:34 PM9/8/21
to object-co...@googlegroups.com
Congrats, Rune! This is an exciting development.

I don't know a whole lot about F#, but I was wondering, do you think it would be possible to accomplish something like the trygve language's 'stage prop' in F#? I assume that all primitives and simple data structures in F# are immutable, but I thought this question might still be relevant for role-playing objects.


--
You received this message because you are subscribed to the Google Groups "object-composition" group.
To unsubscribe from this group and stop receiving emails from it, send an email to object-composit...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/object-composition/CAF1P%2BymJ-mTPexmcgU_zXuFj-BiL20YeQtjvhqDNzdQez_rm8g%40mail.gmail.com.

Rune Lund-Søltoft

unread,
Sep 14, 2021, 3:43:40 AM9/14/21
to object-co...@googlegroups.com
Sorry I responded from my other email (which is not a part of the group). What exactly would you like to accomplish?

Matthew Browne

unread,
Sep 14, 2021, 8:04:31 AM9/14/21
to object-co...@googlegroups.com

Hi Rune,
I was thinking of something like this:
https://github.com/jcoplien/trygve/blob/master/examples/july_money_transfer_2.k#L307

In that example, you can't change the balance of the source account or mutate it in any way, but the object can still be mutated when it's passed to the TransferMoneyContext because it's a role there and not a stageprop.

So I was wondering if in F# there would be some way to "lock" an object when it's in a particular scope, but thinking more about it and how it works in trygve I'm realizing that's probably unlikely. But at least in F#, it looks like object properties are immutable by default unless you mark them as mutable, so that still reduces the overall chance of unintended side effects.

Matthew Browne

unread,
Sep 14, 2021, 8:07:07 AM9/14/21
to object-co...@googlegroups.com

Also, @Cope, if you're still following this group...

When I was looking up the trygve examples, I noticed that the listing on the website is missing a lot of the latest examples:
http://fulloo.info/Examples/TrygveExamples/

Maybe it would make more sense to point the link directly to GitHub so it's always up-to-date?
https://github.com/jcoplien/trygve/tree/master/examples

Rune Lund-Søltoft

unread,
Sep 14, 2021, 8:46:28 AM9/14/21
to object-co...@googlegroups.com
Hi Matt,
Yeah I'll leave that up to F#. Most objects are immutable by default, so you can't mutate a role player anyways unless you explicitly specifies that in the role contract so the differences between a "stage prop" and a role is in the contract. Coming from an FP background that's also likely to be why I don't like the stage prop in the first place. I think immutability is (and should be) the default :-) also from a philosophical point of view. You can't change data, you can only change want is considered the current state by "pointing" to something else. E.g. if I told you that I live on fifth avenue and then later told you that now I live on H.C Andersen Boulevard I have not change 5th avenue to be HCA boulevard nor have I change the object being me. I have associated the object being me with the object being HCA boulevard and disassociated myself with 5th Avenue no objects were changed, only the association between objects

Br
Rune


Rune Lund-Søltoft

unread,
Sep 14, 2021, 8:55:46 AM9/14/21
to object-co...@googlegroups.com
As an aside the F# stuff I made also shows the problems with wrappers even though it avoids most of them. When looking at the implementation it's absolutely clear that it's actually two distinct objects. The role player isn't technically wrapped. The wrapper is a collection of functions that are all partially applied (accepting 'this' as the first argument). So a reference comparison would show the differences between the two but it's only technically possible to do that _inside_ the library implementation or if passing the role object as an argument to another method. I think this can all be avoided If I used quotations instead because then I could analyse the expression tree and do what I've done in maroon for ruby. 

Matthew Browne

unread,
Sep 15, 2021, 7:41:16 AM9/15/21
to object-co...@googlegroups.com
(Forgot to CC the group on my previous reply)

Thanks. I assumed that in your example code, the 'balance' property of the Account object could be mutated from anywhere thanks to the 'mutable balance'. Is that not the case? Is there something that's making it mutable specifically for the FromRole and SinkRole (and not any other roles), without which those roles would be unable to mutate it?

On Wed, Sep 15, 2021, 1:19 AM Lund Soltoft <lunds...@gmail.com> wrote:
It’s the other way around using my F# stuff. You’d need to explicitly specify that you want to mutate the player (and the player needs to be mutable)

Mvh
Rune

Den 15. sep. 2021 kl. 05.17 skrev Matthew Browne <mbro...@gmail.com>:



Hi Rune,
That makes sense.

To take your address example, let's suppose we have a ChangeAddress context and also a GenerateAddressReport context, and a user's address should be changed in the ChangeAddress context of course, but never in the GenerateAddressReport. How would you enforce that? Or is it necessary to enforce that?

I'm not sure I ever fully understood the 'stage prop' concept—after all, the context author can simply avoid mutating properties that shouldn't be mutated in that context. OTOH, in a custom language like trygve, the stage prop could also be designed to prevent mutation even outside the context if it's within the scope of a call that originated from inside the stage prop (I can't remember off the top of my head if trygve already does that)...but I doubt that would be possible in existing languages.

Anyway, this is a fairly minor consideration in the context of your F# implementation, which is totally usable as-is.

Rune Lund-Søltoft

unread,
Oct 7, 2021, 4:23:50 PM10/7/21
to object-co...@googlegroups.com
Sorry Matt, Seems I wrote the answer but never actually mailed it, however in the meantime the answer has become somewhat irrelevant. The reason for that I will post in a new thread

Trygve Reenskaug

unread,
Oct 10, 2021, 2:02:58 AM10/10/21
to object-co...@googlegroups.com, Rune Lund-Søltoft
It's great to see that DCI is well and thriving.😁😁😁
--Trygve

Rune Lund-Søltoft

unread,
Nov 3, 2021, 8:45:29 AM11/3/21
to Trygve Reenskaug, object-co...@googlegroups.com
I've updated the small DCI library I created for the previous example and wanted to share one important finding. We basically need to rewrite the "books" on DCI and .NET. It is indeed possible to support DCI without wrappers and the like in C#, F# and other .NET languages as long as they play nice with the DLR. With the DLR it's possible to emit the role methods inline at the call sight, which will ensure that 'this' is always a reference to the player object. Debuggingwise it's left to the debugger how the role methods are displayed but there's one important drawback. Translating the role methods into expression trees at the call site means that you can't do single stepping in those methods. That's a limitation of the debugger not the approach but it might be an issue at times. Especially when doing DCI as I've proposed earlier where the application would basically be divided into records or contexts. In such an application it wouldn't be possible to do single step debugging at all and you'd have to rely on print statements. Which of course would be a noticable set back

/Rune
Reply all
Reply to author
Forward
0 new messages