【问题标题】:Difficult sequence handling with history具有历史记录的困难序列处理
【发布时间】:2019-01-23 08:03:51
【问题描述】:

我想我忘记了如何使用 F# :(

我正在实现一个控制台界面。由于它们可以持续很长时间,因此我使用输入的行产生的序列。在遇到退出命令时,产生停止(也适用于 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

我想在程序退出时返回第一个不正常的代码(即 > 0),但所有行都应该被处理。

assert 命令是个例外,它可能会也可能不会阻止后面的命令被执行,这取决于它的参数和之前的命令。但是当它退出时,程序返回应该是崩溃命令之前的最后一个值。所以说我愿意

cry
assert 2
sing

程序哭了,但这是意料之中的,所以它也应该执行 sing 命令并以 0 退出。断言任何其他数字应该停止执行,返回 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 []

但我无法将其转换为列表,因为它可能是无限的。而且每次输入后我都不会得到输出。

我知道我不应该以递归/循环的方式同时使用 Seq.itemSeq.headSeq.tail,因为这可能会很快二次爆发。

也许我可以得到seq 表达式与可变变量的工作,但没有更好/更实用的解决方案吗?我觉得我错过了一些东西。

【问题讨论】:

  • 一个尾递归函数怎么样,你可以同时传递状态(可能是带有第一个错误代码的Option&lt;Return&gt;,或者Returns的序列?)和函数的其余行,它评估当前行,或者以相同的状态递归,以新的状态递归,或者终止并打印状态?我可能也会将Quit 设为Return 案例之一,以便在尾递归函数中更容易处理。

标签: f# sequence infinite


【解决方案1】:

这就是我现在拥有的:

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
    | _ -> ()
}

它有效,但我仍然希望有一种更“功能上令人愉悦”的方式——即使它只是为了学习。

【讨论】:

    【解决方案2】:

    您可以创建一个 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 层。

    【讨论】:

    • 您的 withHistory(::) 完全一致,不是吗?
    • 好点。我以为有这样的功能,但我找不到。我已经更新了我的答案以简化实施。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 2012-07-22
    • 1970-01-01
    • 2020-10-10
    • 1970-01-01
    • 2018-05-03
    相关资源
    最近更新 更多