重构 F# 的“可变”while-do代码为函数式的“不可变”代码

3

我一直在尝试将我的一个简单的C#控制台应用程序改写为纯函数式F#代码(如果可能的话)。到目前为止,我已经成功地使用 Seq 将 while 块重新编写为“不变”的F#代码(类似于 Seq.initInfinite sequenceGenerator |> Seq.takeWhile condition | & gt; Seq.iter bodyOfWhileCycle )- 网站“ F# for Fun and Profit”是我的灵感来源。
然而,这次我遇到了一个简单的 while 块,在“可变”的F#中看起来像这样(并且它可以工作):

printfn "Type low OP number"
let mutable lowLimit = parseMe (Console.ReadLine())                    
printfn "Type high OP number"
let mutable highLimit = parseMe (Console.ReadLine())                                            
let mutable myCondition = true
if highLimit > lowLimit then myCondition <- false            
while myCondition do 
      printfn "Type low OP number again"
      lowLimit <- parseMe (Console.ReadLine())                                  
      printfn "Type high OP number again"
      highLimit <- parseMe (Console.ReadLine())
      if highLimit > lowLimit then myCondition <- false

有没有人能帮我想办法将这个“可变的”while-do块重构成函数式风格?我的尝试并没有按照我想要的方式工作-我不知道如何从bodyOfWhileCycle函数中获取lowLimit1/highLimit1值。以下是我失败的一次尝试:

printfn "Type low OP number"
let lowLimit = parseMe (Console.ReadLine())                       
printfn "Type high OP number"
let highLimit = parseMe (Console.ReadLine())
let verifyingInputValues: unit = 
    let bodyOfWhileCycle _=    
                            printfn "Type low OP number again"
                            let lowLimit1 = parseMe (Console.ReadLine())                                  
                            printfn "Type high OP number again"
                            let highLimit1 = parseMe (Console.ReadLine())
                            ()                
    fun _ -> highLimit - lowLimit 
    |>  Seq.initInfinite 
    |>  Seq.takeWhile ((>) 0)
    |>  Seq.iter bodyOfWhileCycle   
verifyingInputValues

注意到您是新手,如果您对我们的答案感到满意,请标记其中一个答案为已接受。我认为我的答案直接展示了一种习惯用语的方式来不可变地实现while循环,并对您的其他解析代码进行了简单的重构。@ChaosPandion的解决方案直接调试了您的Seq方法,并且还对您的解析代码进行了更大(更好)的重构。由您决定。 - Martin Freedman
由于前两个答案对我的学习过程都非常有帮助,所以很难将其中一个标记为比另一个更好的答案(但如果stackoverflow要求标记,我将不得不在一段时间内这样做)。我非常感谢ChaosPandion和@Martin Freedman的帮助。我稍微改编了Martin Freedman的答案(使用“match”代替“if”),并将两个原则都用于我的代码中。 - Miroslav Husťák
2个回答

2

看起来您希望在highLimit>lowLimit时完成while循环,并且在其他情况下重复请求,然后做一些后续操作。

在这种情况下,您需要一个返回元组(highLimit, lowLimit)而不是unit()的函数。递归函数可以处理调用之间的表面状态或IO变化,而无需可变性,只需将新状态作为参数传入即可。

let fullParse: () -> int *int =
    let parseHighLow again = 
       printfn "Type low OP number %s" again
       let lowLimit = parseMe (Console.ReadLine())                                  
       printfn "Type high OP number %s" again
       let highLimit = parseMe (Console.ReadLine())
       highLimit, lowLimit
        
    let rec verify (high, low) = 
       if high > low  then high, low else verify (parseHighLow "again")
    verify (parseHighLow "")

let (high, low) = fullParse ()

2
请注意使用skip而不是take,因为我们正在等待用户接收到我们想要的内容。我们想要的是一个东西,所以我们将head函数应用于序列。总体而言,您的尝试很接近,但您的序列没有产生值。
open System

let rec p name = 
    printfn "Input %s Value" name
    let text = Console.ReadLine()
    let (ok, i) = Int32.TryParse text
    if ok then i else p name
     
let ps () = 
    Seq.initInfinite (fun _ -> p "High", p "Low")
    |> Seq.skipWhile (fun (high, low) -> high <= low)
    |> Seq.head

let (high, low) = ps ()

我注意到你的代码中有“skip”和“head”。谢谢。看起来“takeWhile”和“iter”只能用于替换“while-do”循环,产生“unit”(至少在我的代码中有效),但不能产生值。 - Miroslav Husťák

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接