Синтаксис дерева объектов F #

#c# #syntax #f# #object-construction

#c# #синтаксис #f# #построение объекта

Вопрос:

В C # можно создавать деревья объектов в довольно сжатом синтаксисе:

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

Есть ли идиоматический способ сделать что-то подобное в F #?

Записи имеют хороший синтаксис:

 let button = { Content = "Foo" }
 

Насколько я могу судить, построение объекта — это совсем другое дело. Обычно я бы написал такой код, как:

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

Или даже:

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

Один из способов решения проблемы — использовать пользовательский оператор fluent composition:

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

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

Существует ли встроенный синтаксис для достижения этого — или другой рекомендуемый подход?

Ответ №1:

F # позволяет устанавливать свойства прямо в вызове конструктора, поэтому я думаю, что это должно сработать для вас:

 let button = Button(Content = "Foo")
 

Комментарии:

1. Это так приятно! Я оставлю вопрос открытым на некоторое время на случай, если есть какие-либо предложения о менее прямых ситуациях, таких как, например, let StackPanel = new StackPanel() .amp; (fun x -> x.Children.Add(…) … ), а затем я закончу с этим в качестве ответа.

2. У нас нет концепции открытия и закрытия в том смысле, который вы описываете. Если мы закрываем вопрос, это потому, что с ним возникла проблема, и тогда больше нельзя добавлять ответы, если они не будут открыты повторно. То, что вы описываете, на самом деле является тем, что вы примете как лучший ответ, и вы можете изменить свое мнение о том, какой ответ является лучшим в любое время.

3. Я должен был написать «принято» или «помечено». Извините за путаницу. 🙂 Также полезно знать, что можно изменить принятый ответ.

Ответ №2:

В C # этот приятный синтаксис называется инициализатором объекта, а затем () может быть удален (1). Чтобы изменить объект «встроенным» (в свободном стиле) после его инициализации, мне хотелось бы иметь With() метод расширения, аналогичный вашему .amp; 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")
 

В F #, для тех, кто предпочитает конвейер вместо operator, функция может быть названа tap (см. RxJS) или tee (Скотт Влашин здесь):

 // 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")
 

Комментарии:

1. Я также написал это с помощью метода расширения в C #. Я просто предпочитаю стиль инфикса «operator» в F #. В большинстве случаев я мог бы теперь избежать использования оператора, учитывая принятый ответ Брайана, но для других случаев это все равно необходимо.