使用 Bind 传递的 F# 计算表达式透明状态
F# computation expression transparent state passing with Bind
我有以下代码尝试使用通常的 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}
}
因此,readStream[asInt] [numBytes] [offset] 函数 returns 一些 [data] 或 None 如果数据尚未到达 NetworkStream。 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 阴影效果很好,但如果在循环中添加读取,就无法保持不变性,对吗?那么任务就变得微不足道了。
@bytebuster 在自定义计算表达式的可维护性方面做得很好,但我仍然认为我演示了如何将 State
和 Maybe
monad 组合成一个。
在 "traditional" 语言中,我们对组合值(如整数)有很好的支持,但我们 运行 在开发解析器时遇到了问题(从二进制流生成值本质上是解析)。对于解析器,我们希望将简单的解析器函数组合成更复杂的解析器函数,但这里 "traditional" 语言通常缺乏良好的支持。
在函数式语言中,函数与值一样普通,因为值可以被组合,显然函数也可以。
首先让我们定义一个StreamReader
函数。 StreamReader
采用 StreamPosition
(流 + 位置)并生成更新的 StreamPosition
和 StreamReaderResult
(读取值或失败)。
type StreamReader<'T> =
StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>)
(这是最重要的一步。)
我们希望能够将简单的 StreamReader
函数组合成更复杂的函数。我们要维护的一个非常重要的 属性 是组合操作是 StreamReader
下的 "closed" 意味着组合的结果是一个新的 StreamReader
,它又可以无限组合。
为了读取图像,我们需要读取宽度和高度、计算乘积并读取字节。像这样:
let readImage =
reader {
let! width = readInt32
let! height = readInt32
let! bytes = readBytes (width*height)
return width, height, bytes
}
因为合成被关闭 readImage
是 StreamReader<int*int*byte[]>
。
为了能够像上面那样组成StreamReader
,我们需要定义一个计算表达式,但在我们这样做之前,我们需要定义操作Return
和Bind
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
应该 return 给定值并且不更新 StreamPosition
.
Bind
更具挑战性,但描述了如何将两个 StreamReader
函数组合成一个新函数。 Bind
运行 是第一个 StreamReader
函数并检查结果,如果失败则 return 失败,否则它使用 StreamReader
结果计算第二个StreamReader
和 运行 在更新流中的位置。
Yield
只是创建了 StreamReader
函数并 运行 了它。 Yield
由 F# 在构建计算表达式时使用。
最后让我们创建计算表达式构建器
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
函数。
完整示例:
open System
open System.IO
// The result of a stream reader operation is either
// Success of value
// Failure of list of failures
type StreamReaderResult<'T> =
| Success of 'T
| Failure of (string*StreamPosition) list
and StreamPosition =
{
Stream : byte[]
Position : int
}
member x.Remaining = max 0 (x.Stream.Length - x.Position)
member x.ReadBytes (size : int) : StreamPosition*StreamReaderResult<byte[]> =
if x.Remaining < size then
x, Failure ["EOS", x]
else
let nsp = StreamPosition.New x.Stream (x.Position + size)
nsp, Success (x.Stream.[x.Position..(x.Position + size - 1)])
member x.Read (converter : byte[]*int -> 'T) : StreamPosition*StreamReaderResult<'T> =
let size = sizeof<'T>
if x.Remaining < size then
x, Failure ["EOS", x]
else
let nsp = StreamPosition.New x.Stream (x.Position + size)
nsp, Success (converter (x.Stream, x.Position))
static member New s p = {Stream = s; Position = p;}
// Defining the StreamReader<'T> function is the most important decision
// In this case a stream reader is a function that takes a StreamPosition
// and produces a (potentially) new StreamPosition and a StreamReadeResult
type StreamReader<'T> = StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>)
// Defining the StreamReader CE
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
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 ()
let read (StreamReader sr) (bytes : byte[]) (pos : int) : StreamReaderResult<'T> =
let sp = StreamPosition.New bytes pos
let _, sr = sr sp
sr
// Defining various stream reader functions
let readValue (converter : byte[]*int -> 'T) : StreamReader<'T> =
StreamReader <| fun sp -> sp.Read converter
let readInt32 = readValue BitConverter.ToInt32
let readInt16 = readValue BitConverter.ToInt16
let readBytes size : StreamReader<byte[]> =
StreamReader <| fun sp ->
sp.ReadBytes size
let readImage =
reader {
let! width = readInt32
let! height = readInt32
let! bytes = readBytes (width*height)
return width, height, bytes
}
[<EntryPoint>]
let main argv =
// Sample byte stream
let bytes = [|2;0;0;0;3;0;0;0;1;2;3;4;5;6|] |> Array.map byte
let result = read readImage bytes 0
printfn "%A" result
0
我有以下代码尝试使用通常的 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}
}
因此,readStream[asInt] [numBytes] [offset] 函数 returns 一些 [data] 或 None 如果数据尚未到达 NetworkStream。 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 阴影效果很好,但如果在循环中添加读取,就无法保持不变性,对吗?那么任务就变得微不足道了。
@bytebuster 在自定义计算表达式的可维护性方面做得很好,但我仍然认为我演示了如何将 State
和 Maybe
monad 组合成一个。
在 "traditional" 语言中,我们对组合值(如整数)有很好的支持,但我们 运行 在开发解析器时遇到了问题(从二进制流生成值本质上是解析)。对于解析器,我们希望将简单的解析器函数组合成更复杂的解析器函数,但这里 "traditional" 语言通常缺乏良好的支持。
在函数式语言中,函数与值一样普通,因为值可以被组合,显然函数也可以。
首先让我们定义一个StreamReader
函数。 StreamReader
采用 StreamPosition
(流 + 位置)并生成更新的 StreamPosition
和 StreamReaderResult
(读取值或失败)。
type StreamReader<'T> =
StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>)
(这是最重要的一步。)
我们希望能够将简单的 StreamReader
函数组合成更复杂的函数。我们要维护的一个非常重要的 属性 是组合操作是 StreamReader
下的 "closed" 意味着组合的结果是一个新的 StreamReader
,它又可以无限组合。
为了读取图像,我们需要读取宽度和高度、计算乘积并读取字节。像这样:
let readImage =
reader {
let! width = readInt32
let! height = readInt32
let! bytes = readBytes (width*height)
return width, height, bytes
}
因为合成被关闭 readImage
是 StreamReader<int*int*byte[]>
。
为了能够像上面那样组成StreamReader
,我们需要定义一个计算表达式,但在我们这样做之前,我们需要定义操作Return
和Bind
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
应该 return 给定值并且不更新 StreamPosition
.
Bind
更具挑战性,但描述了如何将两个 StreamReader
函数组合成一个新函数。 Bind
运行 是第一个 StreamReader
函数并检查结果,如果失败则 return 失败,否则它使用 StreamReader
结果计算第二个StreamReader
和 运行 在更新流中的位置。
Yield
只是创建了 StreamReader
函数并 运行 了它。 Yield
由 F# 在构建计算表达式时使用。
最后让我们创建计算表达式构建器
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
函数。
完整示例:
open System
open System.IO
// The result of a stream reader operation is either
// Success of value
// Failure of list of failures
type StreamReaderResult<'T> =
| Success of 'T
| Failure of (string*StreamPosition) list
and StreamPosition =
{
Stream : byte[]
Position : int
}
member x.Remaining = max 0 (x.Stream.Length - x.Position)
member x.ReadBytes (size : int) : StreamPosition*StreamReaderResult<byte[]> =
if x.Remaining < size then
x, Failure ["EOS", x]
else
let nsp = StreamPosition.New x.Stream (x.Position + size)
nsp, Success (x.Stream.[x.Position..(x.Position + size - 1)])
member x.Read (converter : byte[]*int -> 'T) : StreamPosition*StreamReaderResult<'T> =
let size = sizeof<'T>
if x.Remaining < size then
x, Failure ["EOS", x]
else
let nsp = StreamPosition.New x.Stream (x.Position + size)
nsp, Success (converter (x.Stream, x.Position))
static member New s p = {Stream = s; Position = p;}
// Defining the StreamReader<'T> function is the most important decision
// In this case a stream reader is a function that takes a StreamPosition
// and produces a (potentially) new StreamPosition and a StreamReadeResult
type StreamReader<'T> = StreamReader of (StreamPosition -> StreamPosition*StreamReaderResult<'T>)
// Defining the StreamReader CE
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
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 ()
let read (StreamReader sr) (bytes : byte[]) (pos : int) : StreamReaderResult<'T> =
let sp = StreamPosition.New bytes pos
let _, sr = sr sp
sr
// Defining various stream reader functions
let readValue (converter : byte[]*int -> 'T) : StreamReader<'T> =
StreamReader <| fun sp -> sp.Read converter
let readInt32 = readValue BitConverter.ToInt32
let readInt16 = readValue BitConverter.ToInt16
let readBytes size : StreamReader<byte[]> =
StreamReader <| fun sp ->
sp.ReadBytes size
let readImage =
reader {
let! width = readInt32
let! height = readInt32
let! bytes = readBytes (width*height)
return width, height, bytes
}
[<EntryPoint>]
let main argv =
// Sample byte stream
let bytes = [|2;0;0;0;3;0;0;0;1;2;3;4;5;6|] |> Array.map byte
let result = read readImage bytes 0
printfn "%A" result
0