2014-12-12 2 views
2

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

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

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

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

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

Есть ли способ иметь это, чтобы это было возможно?


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

+0

Две вещи: я бы предлагайте против создания нового типа с именем 'Option', поскольку один из них уже существует в языке. Может быть, 'OptionWithItem' или что-то в этом роде. Во-вторых, если у вас есть андер, вы должны опубликовать его с другими ответами. Вы по-прежнему можете отметить тот, который помог вам попасть туда как «ответ», но он все еще четко отделяет вопрос от ответа. – mydogisbox

ответ

4

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

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 изменчиво, но оно не должно быть). Вместо обновления существующих данных каждый доход объединяет текущее состояние и входящий ввод, что приводит к новому экземпляру 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!, так как я на самом деле не хочу, чтобы использовать значение в выражении вычисления.

+0

Отмечено как ответ, так как это привело меня к созданию CE-строителя, который использует почти идентичную систему. – Clint

2

F # поддерживает список понятий из коробки.

let myOptions = 
    [ 
     yield computeOptionValue() 
     yield computeOptionValue() 
    ] 
+0

Спасибо за это, я знал об этом, я должен, возможно, изменить свой вопрос, чтобы указать, что результирующий список является всего лишь примером, и на самом деле список будет представлен некоторым типом контейнера дополнительными методами и т. Д. – Clint

+0

В этом случае , более простой вариант может определять функцию 'ofList' для вашего типа. – Daniel

+0

действительно может быть, но если я хочу делать какие-то конкретные манипуляции прозрачно, тогда мне нужно выражение вычисления, нет? Например, если я хочу быть в состоянии сделать 'let! x = ("n", 12) "и автоматически преобразует кортеж в тип" Option ". – Clint

3

РЕШЕНИЕ

С базовой линии 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 
+0

В 'Combine', упорядочивается результирующий список? Если нет, то вам следует избегать использования '@', где это возможно, поскольку это очень дорогостоящая операция. В трех случаях, когда у вас есть «OptionItem», вы можете просто пропустить ('::') элемент со списком, подобным этому: 'F :: G' (если F - это элемент, а G - список). – mydogisbox

+0

@mydogisbox Я заметил, что когда я запустил его в Linqpad - заказ не важен для примера, который я дал, но я бы действительно обеспечил, чтобы единственная часть комбинации оказалась новой головой списка, чтобы обеспечить упорядочение. – Clint

+0

@mydogisbox Я только что внес изменения в использование менее дорогостоящего '::' и сделаю так, чтобы элемент попал в верхнее положение. – Clint

Смежные вопросы