【问题标题】:F# Immutability, pure function and side effectF# 不变性、纯函数和副作用
【发布时间】:2017-07-27 20:53:50
【问题描述】:

我是 F# 的新手,我正在编写一些小挑战来学习有关该语言的基本细节。我认为由于不可变性,我遇到了问题。

场景: 我必须在控制台中读取高度线,每行包含一个整数。该整数代表一座山的大小。 阅读输入后,我需要写下最高山脉的行号。 如果给定的索引是最高的山,那么大小设置为零,否则我会松动。 重复该场景,直到所有山脉的大小都设置为零。

这里是我写的代码:

open System

type Mountain = {Id:int; Height:int}

let readlineInt() = int(Console.In.ReadLine())
let readMountainData id = {Id = id; Height = readlineInt()}
let readAllMountainsData = [ for a in 0 .. 7 do yield readMountainData a ]

let rec mainLoop () =
     let mountains = readAllMountainsData
     let highestMountain = mountains |> List.maxBy (fun x -> x.Height)

     printfn "%i" highestMountain.Id
     mainLoop()

mainLoop()

这段代码会进入死循环,我相信是因为

let readlineInt() = int(Console.In.ReadLine())

是不可变的,因此该值设置一次,之后它就不会再停止读取该行。我尝试为

放置“可变”关键字
let mutable readAllMountainsData = [ for a in 0 .. 7 do yield readMountainData a ]

但这并没有改变任何事情。 你有什么想法吗?

编辑: 我知道这段代码会进入一个无限循环,因为在将日志记录添加到主循环之后,如下所示:

let rec mainLoop () =
     let mountains = readAllMountainsData
     Console.Error.WriteLine("Mountain Count:{0} ", mountains.Length)
     mountains |> List.iter (fun x -> Console.Error.WriteLine("Mountain Id:{0} Height:{1}", x.Id, x.Height))
     let highestMountain = mountains |> List.maxBy (fun x -> x.Height)

     printfn "%i" highestMountain.Id
     mainLoop()

然后我在输出中有这个:

Standard Error Stream:

Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
Mountain Id:3 Height:6
Mountain Id:4 Height:5
Mountain Id:5 Height:4
Mountain Id:6 Height:3
Mountain Id:7 Height:2
Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
Mountain Id:3 Height:6
Mountain Id:4 Height:5
Mountain Id:5 Height:4
Mountain Id:6 Height:3
Mountain Id:7 Height:2
Mountain Count:8 
Mountain Id:0 Height:9
Mountain Id:1 Height:8
Mountain Id:2 Height:7
etc...

为什么要重读值?因为这些值是由外部来源提供的。所以工作流程如下:

Loop one:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

Loop two:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

Loop three:
I read 8 values for the height of the mountains in the console
I output the value of the highest mountain

etc

【问题讨论】:

  • 为什么你认为代码进入了无限循环?
  • 我刚刚编辑了帖子来回答您的问题。这是因为如果我打印错误流中的数据,我可以看到它。

标签: f# immutability purely-functional


【解决方案1】:

let readlineInt () = ... 定义了一个函数。每次调用它时都会执行它的主体。在这种情况下,主体有副作用,每次执行主体时都会执行该副作用(从标准输入读取)。所以这不是你的问题。

readAllMountainsData 被定义为一个包含七座山脉数据的列表。每座山都有自己的高度(因为每座山都会调用一次readLineInt())。此列表只计算一次,之后不会更改。每次使用 readAllMountainsData 时都不会重新计算它,因为它是一个变量,而不是一个函数(即使名称可能暗示其他情况)。这似乎是完全明智的,因为每次重新读取山区数据是没有意义的。

mutable 关键字添加到定义允许您重新分配变量。也就是说,它允许您稍后在程序中编写readAllMountainsData <- someNewValue 来更改变量的值。由于您从未真正这样做过,因此没有任何变化。

你的程序无限循环的原因是mainLoop总是再次调用自己。它没有退出条件。因此,要解决这个问题,您应该决定要循环的频率/在什么条件下要退出,然后相应地实现该逻辑。


在您的编辑中,您澄清说您确实想重新读取您的值,因此您只需通过给readAllMountainsData 一个参数列表(let readAllMountainsData () = ...)将其作为函数调用,然后将其作为函数调用。这样,您将在每次迭代中获得新数据,但除非您添加退出条件,否则循环仍将是无限的。

【讨论】:

  • 主循环调用自身,因为我希望重复该步骤直到结束(所有山高都设置为 0)。我尝试通过添加 () 创建一个带有 readAllMountainsData 的函数,但它没有改变任何东西。
  • @CedricRoyer-Bertrand 如果您想调用自己直到所有高度都为零,则该条件需要在代码中。目前,您永远称自己为永远,直到高度为零。
  • 好吧,你发现了这个bug,确实是readAllMountainsData不是一个函数。我在之前的尝试中犯了一个错误。我已经添加了在 8 点停止的条件。也感谢对 mutable 的解释。我接受你的帖子作为答案。再次感谢。
猜你喜欢
  • 2019-12-04
  • 2018-01-06
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
相关资源
最近更新 更多