Выражение вычислений с отслеживанием состояния F#

В настоящее время я изучаю F # и натыкаюсь на несколько камней преткновения; Я думаю, что многое из этого заключается в том, чтобы научиться мыслить функционально.

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

let myOptions = optionListBuilder {
    let! opt1 = {name="a";value=10}
    let! opt2 = {name="b";value=12}
}

Я хочу, чтобы myOptions было Option<'T> list, поэтому каждая операция привязки let! эффективно заставляет сборщика «отслеживать» определенные параметры по мере их продвижения.

Я не хочу делать это, используя изменяемое состояние - например. наличие списка, поддерживаемого разработчиком и обновляемого при каждом вызове bind.

Есть ли какой-то способ получить это так, чтобы это было возможно?


Обновление: результирующий тип Option<'T> list является просто репрезентативным, на самом деле у меня, вероятно, будет тип OptionGroup<'T>, содержащий список, а также некоторую дополнительную информацию, поэтому, как Даниэль упомянул ниже, я мог бы использовать понимание списка для простого списка.


person Clint    schedule 12.12.2014    source источник
comment
Две вещи: я бы посоветовал не создавать новый тип с именем Option, так как один из них уже существует в языке. Может быть, OptionWithItem или что-то в этом роде. Во-вторых, если у вас есть ответ, вы должны опубликовать его вместе с другими ответами. Вы по-прежнему можете отметить тот, который помог вам добраться туда, как ответ, но он по-прежнему четко отделяет вопрос от ответа.   -  person N_A    schedule 12.12.2014


Ответы (3)


Я написал выражение вычисления построителя строк здесь.

open System.Text

type StringBuilderUnion =
| Builder of StringBuilder
| StringItem of string

let build sb =
    sb.ToString()

type StringBuilderCE () =
    member __.Yield (txt : string) = StringItem(txt)
    member __.Yield (c : char) = StringItem(c.ToString())
    member __.Combine(f,g) = Builder(match f,g with
                                     | Builder(F),   Builder(G)   ->F.Append(G.ToString())
                                     | Builder(F),   StringItem(G)->F.Append(G)
                                     | StringItem(F),Builder(G)   ->G.Append(F)
                                     | StringItem(F),StringItem(G)->StringBuilder(F).Append(G))
    member __.Delay f = f()
    member __.Zero () = StringItem("")
    member __.For (xs : 'a seq, f : 'a -> StringBuilderUnion) =
                    let sb = StringBuilder()
                    for item in xs do
                        match f item with
                        | StringItem(s)-> sb.Append(s)|>ignore
                        | Builder(b)-> sb.Append(b.ToString())|>ignore
                    Builder(sb)

let builder1 = new StringBuilderCE ()

Заметил, что базовый тип неизменяем (содержащийся StringBuilder является изменяемым, но это не обязательно). Вместо обновления существующих данных каждый yield объединяет текущее состояние и входящие входные данные, что приводит к новому экземпляру StringBuilderUnion. Вы можете сделать это со списком F #, поскольку добавление элемента в начало списка — это просто создание нового значения. а не изменять существующие значения.

Использование StringBuilderCE выглядит так:

//Create a function which builds a string from an list of bytes
let bytes2hex (bytes : byte []) =
    string {
        for byte in bytes -> sprintf "%02x" byte
    } |> build

//builds a string from four strings
string {
        yield "one"
        yield "two"
        yield "three"
        yield "four"
    } |> build

Заметил yield вместо let!, так как на самом деле я не хочу использовать значение внутри выражения вычисления.

person N_A    schedule 12.12.2014
comment
Помечено как ответ, так как это привело меня к созданию компоновщика CE, который использует почти идентичную систему. - person Clint; 12.12.2014

РЕШЕНИЕ

С базовым компоновщиком StringBuilder CE, предоставленным mydogisbox, я смог создать следующее решение, которое прекрасно работает:

type Option<'T> = {Name:string;Item:'T}

type OptionBuilderUnion<'T> =
    | OptionItems of Option<'T> list
    | OptionItem of Option<'T>

type OptionBuilder () =
    member this.Yield (opt: Option<'t>) = OptionItem(opt)
    member this.Yield (tup: string * 't) = OptionItem({Name=fst tup;Item=snd tup})
    member this.Combine (f,g) = 
        OptionItems(
            match f,g with
            | OptionItem(F), OptionItem(G) -> [F;G]
            | OptionItems(F), OptionItem(G) -> G :: F
            | OptionItem(F), OptionItems(G) -> F :: G
            | OptionItems(F), OptionItems(G) -> F @ G
        )
    member this.Delay f = f()
    member this.Run (f) = match f with |OptionItems items -> items |OptionItem item -> [item]

let options = OptionBuilder()

let opts = options {
        yield ("a",12)
        yield ("b",10)
        yield {Name = "k"; Item = 20}
    }

opts |> Dump
person Clint    schedule 12.12.2014
comment
В Combine важен ли порядок результирующего списка? Если нет, то вам следует по возможности избегать использования @, так как это очень дорогая операция. В трех случаях, когда у вас есть OptionItem, вы можете просто констатировать (::) элемент со списком следующим образом: F::G (при условии, что F — это элемент, а G — список). - person N_A; 12.12.2014
comment
@mydogisbox Я заметил, что, когда я запускал его в Linqpad, порядок не важен для приведенного мной примера, но я действительно хотел убедиться, что единственная часть комбинации оказалась в качестве нового заголовка списка, чтобы обеспечить порядок. - person Clint; 12.12.2014
comment
@mydogisbox Я только что внес изменения, чтобы использовать менее дорогой :: и сделать так, чтобы элемент оказывался в верхней позиции. - person Clint; 12.12.2014

F# поддерживает готовые генераторы списков.

let myOptions =
    [
        yield computeOptionValue()
        yield computeOptionValue()
    ]
person Daniel    schedule 12.12.2014
comment
Спасибо за это, я знал об этом, возможно, мне следует отредактировать свой вопрос, чтобы указать, что результирующий список является просто примером, и на самом деле список будет представлен некоторым типом контейнера с дополнительными методами и т. д. - person Clint; 12.12.2014
comment
В этом случае более простым вариантом может быть определение функции ofList для вашего типа. - person Daniel; 12.12.2014
comment
это действительно может быть, но если я хочу выполнить какую-то конкретную манипуляцию прозрачно, тогда мне как бы нужно вычислительное выражение, нет? Например, если я хочу иметь возможность выполнять let! x = ("n",12) и автоматически преобразовывать кортеж в тип Option<int>. - person Clint; 12.12.2014
comment
Поскольку вы, очевидно, занимаетесь черной магией, вы предоставлены сами себе. :) - person Daniel; 12.12.2014