3

In C# it is possible to construct object trees in a rather succint syntax:

var button = new Button() { Content = "Foo" };

Is there an idiomatic way to do something similar in F#?

Records have nice syntax:

let button = { Content = "Foo" }

Object construction would appear to be a different matter, as far as I can tell. Normally I would write code such as:

let button = new Button()
button.Content <- "Foo"

Or even:

let button =
    let x = new Button()
    x.Content <- "Foo"
    x

One way to solve the problem is to use a custom fluent composition operator:

// Helper that makes fluent-style possible
let inline (.&) (value : 'T) (init: 'T -> unit) : 'T =
    init value
    value

let button = new Button() .& (fun x -> x.Content <- "Foo")

Is there built-in syntax to achieve this - or another recommended approach?

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

2 Answers2

6

F# lets you set properties right in the constructor call, so I think this should work for you:

let button = Button(Content = "Foo")
Brian Berns
  • 15,499
  • 2
  • 30
  • 40
  • That is so nice! I will leave the question open for a little while in case there are any suggestions about less direct situations like e.g. let stackPanel = new StackPanel() .& (fun x -> x.Children.Add(...) ... ) and then I will close with this as the answer. – Bent Rasmussen Dec 11 '21 at 22:28
  • We don't have a concept of open and close in the sense you're describing. If we close a question, it's because there's a problem with it, and then no more answers can be added, unless reopened. What you describe is actually that you will accept as best answer, and you can change your mind of which answer is the best at any time. – Bent Tranberg Dec 12 '21 at 07:15
  • I should have written "accepted" or "marked". Sorry for the confusion. :-) Also good to know that it is okay to change accepted answer. – Bent Rasmussen Dec 12 '21 at 11:04
3

In C#, this nice syntax is called object initializer and then the () can be removed (1). To change an object "inline" (fluent style) after its initialization, I like to have a With() extension method similar to your .& operator (2):

var button = new Button { Content = "Foo" }; // (1)

// (2)
public static T With<T>(this T @this, Action<T> update)
{
    change(@this);
    return @this;
}

var button2 = button.With(x => x.Content = "Bar")

In F#, for those who prefer piping instead of operator, the function can be named tap (see RxJs) or tee (by Scott Wlaschin here):

// f: ('a -> 'b) -> x: 'a -> 'a
let inline tee f x =
    f x |> ignore
    x

let button =
    Button(Content = "Foo")
    |> tee (fun x -> x.Color <- Blue)
    |> tee (fun x -> x.Content <- "Bar")
Romain Deneau
  • 2,841
  • 12
  • 24
  • I have also have written that With extension method in C#. I just prefer the "operator" infix style in F#. For most cases I could now avoid using the operator given the accepted answer from Brian but for other cases it is still necessary. – Bent Rasmussen Dec 12 '21 at 11:17