在 F# 中使用 Seq.fold 的更好方法
A better way to use Seq.fold in F#
我编写了一个小型控制台应用程序,无需使用任何可变变量即可更新类型记录。如果这对于经验丰富的函数式程序员来说看起来很简单,那对我来说就是一项艰巨的工作。
它有效,但有一件事我不满意。但在此之前,让我们从代码开始:
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
的起点是合乎逻辑的,那么在映射转换中使用它会感觉很奇怪(我不确定如果将此代码推入,是否有人会理解逻辑产品)。
是否有更好的方法来处理命令序列,或者此代码是否标准且在功能世界中可以接受?
您可以去掉 map
并大大简化 fold
:
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.fold processCommand initialStudent
我不确定您为什么认为必须首先将 seq<char>
映射到 seq<Student * char>
。由于您立即使用 snd
从元组中提取 char
,撤消映射,元组的第一个元素将被完全忽略。更简洁,简单地避免首先创建元组
我编写了一个小型控制台应用程序,无需使用任何可变变量即可更新类型记录。如果这对于经验丰富的函数式程序员来说看起来很简单,那对我来说就是一项艰巨的工作。 它有效,但有一件事我不满意。但在此之前,让我们从代码开始:
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
的起点是合乎逻辑的,那么在映射转换中使用它会感觉很奇怪(我不确定如果将此代码推入,是否有人会理解逻辑产品)。
是否有更好的方法来处理命令序列,或者此代码是否标准且在功能世界中可以接受?
您可以去掉 map
并大大简化 fold
:
commands
|> Seq.filter isValidCommand
|> Seq.takeWhile (not << isStopCommand)
|> Seq.fold processCommand initialStudent
我不确定您为什么认为必须首先将 seq<char>
映射到 seq<Student * char>
。由于您立即使用 snd
从元组中提取 char
,撤消映射,元组的第一个元素将被完全忽略。更简洁,简单地避免首先创建元组