简体   繁体   English

F#“​​有状态”计算表达式

[英]F# “Stateful” Computation Expression

I'm currently learning F# and hitting a few stumbling blocks; 我正在学习F#并且遇到了一些绊脚石; I think a lot of it is learning to think functionally. 我认为很多是学习功能性思考。

One of the things I'm learning at the moment are computation expressions, and I want to be able to define a computation expression that handles some tracking state, eg: 我目前正在学习的一件事是计算表达式,我希望能够定义一个处理某些跟踪状态的计算表达式,例如:

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

I want to be able to have it so that myOptions is a Option<'T> list , so each let! 我希望能够拥有它,以便myOptions是一个Option<'T> list ,所以每个人都let! bind operation effectively causes the builder to "track" the defined options as it goes along. 绑定操作有效地使构建器在其进行时“跟踪”定义的选项。

I don't want to have to do it using mutable state - eg having a list maintained by the builder and updated with each bind call. 我不想使用可变状态来执行此操作 - 例如,具有由构建器维护的列表并使用每个bind调用进行更新。

Is there some way of having it so that this is possible? 有没有办法让它成为可能?


Update : The resultant Option<'T> list type is just representative, in reality I'll likely have an OptionGroup<'T> type to contain a list as well as some additional information - so as Daniel mentioned below, I could use a list comprehension for a simple list. 更新 :结果Option<'T> list类型只是代表性的,实际上我可能有一个OptionGroup<'T>类型来包含列表以及一些其他信息 - 所以Daniel在下面提到,我可以使用列表理解为一个简单的列表。

I wrote a string builder computation expression 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 ()

Noticed the underlying type is immutable (the contained StringBuilder is mutable, but it doesn't have to be). 注意到底层类型是不可变的(包含的StringBuilder是可变的,但它不一定是)。 Instead of updating the existing data, each yield combines the current state and the incoming input resulting in a new instance of StringBuilderUnion You could do this with an F# list since adding an element to the head of the list is merely the construction of a new value rather than mutating the existing values. 而不是更新现有数据,每个yield组合当前状态和传入输入,从而产生StringBuilderUnion的新实例。您可以使用F#列表执行此操作,因为向列表头部添加元素仅仅是构造新值而不是改变现有的价值观。

Using the StringBuilderCE looks like this: 使用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

Noticed the yield instead of let! 注意到yield而不是let! since I don't actually want to use the value inside the computation expression. 因为我实际上并不想使用计算表达式中的值。

SOLUTION

With the base-line StringBuilder CE builder provided by mydogisbox, I was able to produce the following solution that works a charm: 使用mydogisbox提供的基线StringBuilder CE构建器,我能够生成以下具有魅力的解决方案:

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

F# supports list comprehensions out-of-the-box. F#支持开箱即用的列表推导。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM