F#计算表达式透明状态与Bind一起传递

时间:2015-06-13 12:53:56

标签: f# computation-expression

我有以下代码尝试使用通常的MaybeBuilder从网络流中读取可能不完整的数据(例如图像数据):

let image = maybe {
    let pos = 2 //Initial position skips 2 bytes of packet ID
    let! width, pos = readStreamAsInt 2 pos
    let! height, pos = readStreamAsInt 2 pos
    let! data, pos = readStream (width*height) pos
    advanceInStream pos
    return {width = width; height = height; pixels = data}
}

因此,如果数据尚未到达NetworkStream,则readStream [asInt] [numBytes] [offset]函数返回一些[data]或None。读取整个网络数据包时执行advanceInStream函数。

我想知道是否有某种方法可以编写一些自定义计算表达式构建器来隐藏来自其用户的pos,因为它始终是相同的 - 我读取了一些数据并在流中定位并将其作为最后一个传递给下一个读取函数参数。

P.S。 MaybeBuilder使用:

type MaybeBuilder() =    
    member x.Bind(d,f) = Option.bind f d
    member x.Return d = Some d
    member x.ReturnFrom d = d
    member x.Zero() = None
let maybe = new MaybeBuilder()

P.P.S

第二个想法似乎我使pos可变,因为在阅读中可能有“for”或“while”循环。简单吧!使用pos Bind阴影可以正常工作,但是如果你在循环中添加读数就无法保持不变性,对吧?那么任务变得微不足道。

1 个答案:

答案 0 :(得分:3)

@bytebuster正在为自定义计算表达式提供良好的可维护性,但我仍然认为我演示了如何将StateMaybe monad合并为一个。

在“传统”语言中,我们很好地支持组合诸如整数之类的值,但是在开发解析器时会遇到问题(从二进制流生成值本质上是解析)。对于解析器,我们希望将简单的解析器函数组合成更复杂的解析器函数,但这里的“传统”语言通常缺乏良好的支持。

在函数式语言中,函数与值一样普通,因为值可以组成,显然函数也可以。

首先让我们定义一个StreamReader函数。 StreamReader需要StreamPosition(流+位置)并生成更新的StreamPositionStreamReaderResult(读取值或失败)。

type StreamReader<'T> = 
  StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>)

(这是最重要的一步。)

我们希望能够将简单的StreamReader函数组合成更复杂的函数。我们想要维护的一个非常重要的属性是撰写操作在StreamReader下“关闭”,这意味着组合的结果是一个新的StreamReader,而这反过来可以无限地组成。

为了阅读图像,我们需要读取宽度和宽度。高度,计算产品并读取字节数。像这样:

let readImage = 
  reader {
    let! width  = readInt32 
    let! height = readInt32 
    let! bytes  = readBytes (width*height)

    return width, height, bytes
  }

由于合成被关闭readImageStreamReader<int*int*byte[]>

为了能够像上面那样组成StreamReader,我们需要定义一个计算表达式,但在我们能够做到这一点之前,我们需要为{{定义ReturnBind的操作1}}。事实证明StreamReader也很好。

Yield

module StreamReader = let Return v : StreamReader<'T> = StreamReader <| fun sp -> sp, (Success v) let Bind (StreamReader t) (fu : 'T -> StreamReader<'U>) : StreamReader<'U> = StreamReader <| fun sp -> let tsp, tr = t sp match tr with | Success tv -> let (StreamReader u) = fu tv u tsp | Failure tfs -> tsp, Failure tfs let Yield (ft : unit -> StreamReader<'T>) : StreamReader<'T> = StreamReader <| fun sp -> let (StreamReader t) = ft () t sp 非常简单,因为Return应该返回给定值而不会更新StreamReader

StreamPosition更具挑战性,但介绍了如何将两个Bind函数组合成一个新函数。 StreamReader运行第一个Bind函数并检查结果,如果失败则返回失败,否则它使用StreamReader结果计算第二个StreamReader并运行更新流位置。

StreamReader只需创建Yield函数并运行它。构建计算表达式时,F#使用StreamReader

最后让我们创建计算表达式构建器

Yield

现在我们构建了组合type StreamReaderBuilder() = member x.Return v = StreamReader.Return v member x.Bind(t,fu) = StreamReader.Bind t fu member x.Yield(ft) = StreamReader.Yield ft let reader = StreamReaderBuilder () 函数的基本框架。另外,我们需要定义原始StreamReader函数。

完整示例:

StreamReader