在 F# 中使用 Seq.fold 的更好方法

发布于 2025-01-16 12:48:13 字数 2840 浏览 1 评论 0原文

我编写了一个小型控制台应用程序,可以在不使用任何可变变量的情况下更新类型记录。对于经验丰富的函数式程序员来说,这看起来很简单,但对我来说却是一项艰巨的工作。 它有效,但有一件事我不满意。但在此之前,让我们从代码开始:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

栀梦 2025-01-23 12:48:14

您可以摆脱 map 并大大简化 fold

commands
    |> Seq.filter isValidCommand
    |> Seq.takeWhile (not << isStopCommand)
    |> Seq.fold processCommand initialStudent

我不确定为什么您认为必须映射 seq > 进入 seq 开始。由于您立即使用 snd 从元组中提取 char 并撤消映射,因此元组的第一个元素将被完全忽略。更干净,可以从一开始就避免创建元组

You can get rid of the map and simplify the fold considerably:

commands
    |> Seq.filter isValidCommand
    |> Seq.takeWhile (not << isStopCommand)
    |> Seq.fold processCommand initialStudent

I'm not sure why you thought you had to map the seq<char> into a seq<Student * char> to begin with. Since you immediately use snd to extract the char from the tuple, undoing the map, the tuples' first elements are totally ignored. Much cleaner to simply avoid creating tuples in the first place

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文