在 F# 中使用 Seq.fold 的更好方法
我编写了一个小型控制台应用程序,可以在不使用任何可变变量的情况下更新类型记录。对于经验丰富的函数式程序员来说,这看起来很简单,但对我来说却是一项艰巨的工作。 它有效,但有一件事我不满意。但在此之前,让我们从代码开始:
open System
//------------------------------------------------------------------------------------
// Type, no data validation to keep it simple
//------------------------------------------------------------------------------------
[<StructuredFormatDisplay("{FirstName} {LastName} is a {Age} year old {Sex}")>]
type Student = {
FirstName: string
LastName : string
Sex : char
Age: int
}
//------------------------------------------------------------------------------------
// I/O functions
//------------------------------------------------------------------------------------
let getConsoleChar message =
printf "\n%s" message
Console.ReadKey().KeyChar
let getConsoleString message =
printf "\n%s" message
Console.ReadLine()
let getConsoleInt = getConsoleString >> Int32.Parse //no tryparse to keep it simple, I'm sure you can type an integer
let isValidCommand command = [ 'f'; 'l'; 's'; 'a'; 'x'] |> List.contains command
let isStopCommand = (=) 'x'
let processCommand student command =
match command with
| 'f' -> { student with FirstName = (getConsoleString "First Name: ")}
| 'l' -> { student with LastName = (getConsoleString "Last Name: ")}
| 's' -> { student with Sex = (getConsoleChar "Sex: ")}
| 'a' -> { student with Age = (getConsoleInt "Age: ")}
| 'x' -> student
| _ -> failwith "You've just broken the Internet, theorically you cannot be here"
//------------------------------------------------------------------------------------
// Program
//------------------------------------------------------------------------------------
let initialStudent = {
FirstName = String.Empty
LastName = String.Empty
Sex = Char.MinValue
Age = 0
}
let commands = seq {
while true do
yield getConsoleChar "Update [f]irst name, [l]ast name, [s]ex, [a]ge or e[x]it: " }
let finalStudent =
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
printfn "\n<<<< %A >>>>\n" finalStudent
我的问题是,
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
将 char
序列转换为 Student*char
以便能够将其插入Seq.fold
。另外,如果使用 initialStudent
作为 Seq.fold
的起点是合乎逻辑的,那么在映射转换中使用它会感觉很奇怪(我不确定有人会理解)将此代码推送到产品中的逻辑)。
是否有更好的方法来处理命令序列,或者该代码在功能世界中是否标准且可以接受?
I wrote a small console app that update a Type record without using any mutable variable. If that looks simple for seasoned functional programmers, it was quite a hard work for me.
It works, but there is one thing I am not happy with. But before that, let's start with the code:
open System
//------------------------------------------------------------------------------------
// Type, no data validation to keep it simple
//------------------------------------------------------------------------------------
[<StructuredFormatDisplay("{FirstName} {LastName} is a {Age} year old {Sex}")>]
type Student = {
FirstName: string
LastName : string
Sex : char
Age: int
}
//------------------------------------------------------------------------------------
// I/O functions
//------------------------------------------------------------------------------------
let getConsoleChar message =
printf "\n%s" message
Console.ReadKey().KeyChar
let getConsoleString message =
printf "\n%s" message
Console.ReadLine()
let getConsoleInt = getConsoleString >> Int32.Parse //no tryparse to keep it simple, I'm sure you can type an integer
let isValidCommand command = [ 'f'; 'l'; 's'; 'a'; 'x'] |> List.contains command
let isStopCommand = (=) 'x'
let processCommand student command =
match command with
| 'f' -> { student with FirstName = (getConsoleString "First Name: ")}
| 'l' -> { student with LastName = (getConsoleString "Last Name: ")}
| 's' -> { student with Sex = (getConsoleChar "Sex: ")}
| 'a' -> { student with Age = (getConsoleInt "Age: ")}
| 'x' -> student
| _ -> failwith "You've just broken the Internet, theorically you cannot be here"
//------------------------------------------------------------------------------------
// Program
//------------------------------------------------------------------------------------
let initialStudent = {
FirstName = String.Empty
LastName = String.Empty
Sex = Char.MinValue
Age = 0
}
let commands = seq {
while true do
yield getConsoleChar "Update [f]irst name, [l]ast name, [s]ex, [a]ge or e[x]it: " }
let finalStudent =
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
printfn "\n<<<< %A >>>>\n" finalStudent
My problem is with
|> Seq.map (fun cmd -> (initialStudent, cmd))
|> Seq.fold (fun student studentAndCommand -> processCommand student (snd studentAndCommand)) initialStudent
It looks bizarre to transform a sequence of char
into a Student*char
to be able to plug it with aSeq.fold
. Also, if using the initialStudent
as a starting point for Seq.fold
is logical, it feel weird to use it in the mapping transformation (I'm not sure anyone would understand the logic if this code was pushed in prod).
Is there a better way to treat the sequence of commands or is this code standard and acceptable in the functional world?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以摆脱
map
并大大简化fold
:我不确定为什么您认为必须映射
seq
> 进入seq
开始。由于您立即使用 snd 从元组中提取 char 并撤消映射,因此元组的第一个元素将被完全忽略。更干净,可以从一开始就避免创建元组You can get rid of the
map
and simplify thefold
considerably:I'm not sure why you thought you had to map the
seq<char>
into aseq<Student * char>
to begin with. Since you immediately usesnd
to extract thechar
from the tuple, undoing the map, the tuples' first elements are totally ignored. Much cleaner to simply avoid creating tuples in the first place