具有历史记录的困难序列处理
Difficult sequence handling with history
我想我忘记了如何使用 F#:(
我正在实现一个控制台界面。因为它们可以持续很长时间,所以我使用一个序列来生成输入的行。在遇到 quit 命令时,yield 停止(也是在 EOF 时,对管道很重要)。
open System
type Return =
| Code of int
| Assert of int
let rec consoleLines () = seq {
printf ">>> "
match Console.ReadLine() with
| null -> ()
| line ->
match line.Trim().ToLowerInvariant() with
| "quit" -> printfn "Bye!"
| line ->
yield line
yield! consoleLines()
}
let handle (str:string) = async {
if str.StartsWith "noop" then return Code 0
elif str.StartsWith "assert" then
let num = str.Substring(7) |> int
return Assert num
elif str.StartsWith "sing" then
printfn "La la la"
return Code 0
elif str.StartsWith "cry" then
printfn "Boo hoo"
return Code 2
//elif other commands...
else
printfn "Unknown command: %s" str
return Code -1
}
let results =
consoleLines()
|> Seq.map handle
|> Seq.map Async.RunSynchronously
let firstBad =
results
// Want to execute all lines, not just up to the first not-ok.
|> Seq.toList
|> List.tryFind ((<>)(Code 0))
match firstBad with
| Some (Code error) ->
eprintfn "Exiting with error code %i." error
exit error
| _ ->
printfn "Exiting normally."
exit 0
我想 return 程序退出时的第一个 not-ok 代码(即 > 0),但应处理所有行。
例外是 assert 命令,它可能会或可能不会阻止后续命令的执行,具体取决于它的参数和之前的命令。但是当它退出时,程序 return 应该是崩溃命令 之前的最后一个值 。所以说我愿意
cry
assert 2
sing
程序哭了,但这是预料之中的,所以它也应该执行 sing 命令并以 0 退出。断言任何其他数字应该停止执行,return 2 并且不会让程序唱歌。
我尝试了很多东西,比如
Seq.pairwise
似乎是个好主意,但重复了(而且不起作用)
Seq.map
后跟 Seq.concat
Seq.unfold
但不知道
- 各种
let rec loop () = seq { … loop() }
- 几个
seq { for (l, r) in … }
但我无法让它发挥任何作用。如果我有一个列表,我想这会很简单:
let matchExceptions =
let rec matchExceptions acc = function
| [] -> acc |> List.rev
| Code code :: Assert ass :: rest when code = ass ->
matchExceptions (0 :: acc) rest
| Code code :: Assert _ :: _ ->
[code]
| Assert _ :: rest ->
// Unmatched assert is useless.
matchExceptions acc rest
| Code code :: rest ->
matchExceptions (code :: acc) rest
matchExceptions []
但我无法将其转换为列表,因为它可能是无限的。我也不会在每次输入后得到输出。
我知道我不应该以 recursive/looping 的方式将 Seq.item
或 Seq.head
与 Seq.tail
一起使用,因为那样可能会迅速呈二次方爆炸。
可能我可以获得 seq
可变变量的表达式,但是没有 nicer/more 函数式解决方案吗?我觉得我错过了什么。
您可以创建一个 Seq
辅助函数,它使用 Seq.scan
保存先前值的历史记录(感谢您的建议):
module Seq =
let withHistory xs =
([], xs)
||> Seq.scan (fun history x -> x :: history )
|> Seq.skip 1
Seq.initInfinite id |> Seq.withHistory
// val it : seq<int list> = seq [[0]; [1; 0]; [2; 1; 0]; [3; 2; 1; 0]; ...]
只有一个列表可以有效地将值附加到前面,并且只有一个额外的 seq
层。
嗯,这就是我现在拥有的:
let matchExceptions (source:seq<_>) = seq {
let mutable prev = None
let mutable stop = false
use enm = source.GetEnumerator()
while not stop && enm.MoveNext() do
match prev, enm.Current with
| Some (Code code), Assert exp ->
if code = exp then
printfn "Expectation matched, this calls for celebration."
yield 0
else
printf "Expected %i but got %i." exp code
stop <- true
yield code
| Some (Code code), _ ->
yield code
| _ -> ()
prev <- Some enm.Current
// Don't forget the last one.
match prev, stop with
| Some (Code code), false -> yield code
| _ -> ()
}
它有效,但我仍然希望有一种更“功能上令人愉悦”的方式——即使它只是为了学习。
我想我忘记了如何使用 F#:(
我正在实现一个控制台界面。因为它们可以持续很长时间,所以我使用一个序列来生成输入的行。在遇到 quit 命令时,yield 停止(也是在 EOF 时,对管道很重要)。
open System
type Return =
| Code of int
| Assert of int
let rec consoleLines () = seq {
printf ">>> "
match Console.ReadLine() with
| null -> ()
| line ->
match line.Trim().ToLowerInvariant() with
| "quit" -> printfn "Bye!"
| line ->
yield line
yield! consoleLines()
}
let handle (str:string) = async {
if str.StartsWith "noop" then return Code 0
elif str.StartsWith "assert" then
let num = str.Substring(7) |> int
return Assert num
elif str.StartsWith "sing" then
printfn "La la la"
return Code 0
elif str.StartsWith "cry" then
printfn "Boo hoo"
return Code 2
//elif other commands...
else
printfn "Unknown command: %s" str
return Code -1
}
let results =
consoleLines()
|> Seq.map handle
|> Seq.map Async.RunSynchronously
let firstBad =
results
// Want to execute all lines, not just up to the first not-ok.
|> Seq.toList
|> List.tryFind ((<>)(Code 0))
match firstBad with
| Some (Code error) ->
eprintfn "Exiting with error code %i." error
exit error
| _ ->
printfn "Exiting normally."
exit 0
我想 return 程序退出时的第一个 not-ok 代码(即 > 0),但应处理所有行。
例外是 assert 命令,它可能会或可能不会阻止后续命令的执行,具体取决于它的参数和之前的命令。但是当它退出时,程序 return 应该是崩溃命令 之前的最后一个值 。所以说我愿意
cry
assert 2
sing
程序哭了,但这是预料之中的,所以它也应该执行 sing 命令并以 0 退出。断言任何其他数字应该停止执行,return 2 并且不会让程序唱歌。
我尝试了很多东西,比如
Seq.pairwise
似乎是个好主意,但重复了(而且不起作用)Seq.map
后跟Seq.concat
Seq.unfold
但不知道- 各种
let rec loop () = seq { … loop() }
- 几个
seq { for (l, r) in … }
但我无法让它发挥任何作用。如果我有一个列表,我想这会很简单:
let matchExceptions =
let rec matchExceptions acc = function
| [] -> acc |> List.rev
| Code code :: Assert ass :: rest when code = ass ->
matchExceptions (0 :: acc) rest
| Code code :: Assert _ :: _ ->
[code]
| Assert _ :: rest ->
// Unmatched assert is useless.
matchExceptions acc rest
| Code code :: rest ->
matchExceptions (code :: acc) rest
matchExceptions []
但我无法将其转换为列表,因为它可能是无限的。我也不会在每次输入后得到输出。
我知道我不应该以 recursive/looping 的方式将 Seq.item
或 Seq.head
与 Seq.tail
一起使用,因为那样可能会迅速呈二次方爆炸。
可能我可以获得 seq
可变变量的表达式,但是没有 nicer/more 函数式解决方案吗?我觉得我错过了什么。
您可以创建一个 Seq
辅助函数,它使用 Seq.scan
保存先前值的历史记录(感谢您的建议):
module Seq =
let withHistory xs =
([], xs)
||> Seq.scan (fun history x -> x :: history )
|> Seq.skip 1
Seq.initInfinite id |> Seq.withHistory
// val it : seq<int list> = seq [[0]; [1; 0]; [2; 1; 0]; [3; 2; 1; 0]; ...]
只有一个列表可以有效地将值附加到前面,并且只有一个额外的 seq
层。
嗯,这就是我现在拥有的:
let matchExceptions (source:seq<_>) = seq {
let mutable prev = None
let mutable stop = false
use enm = source.GetEnumerator()
while not stop && enm.MoveNext() do
match prev, enm.Current with
| Some (Code code), Assert exp ->
if code = exp then
printfn "Expectation matched, this calls for celebration."
yield 0
else
printf "Expected %i but got %i." exp code
stop <- true
yield code
| Some (Code code), _ ->
yield code
| _ -> ()
prev <- Some enm.Current
// Don't forget the last one.
match prev, stop with
| Some (Code code), false -> yield code
| _ -> ()
}
它有效,但我仍然希望有一种更“功能上令人愉悦”的方式——即使它只是为了学习。