A better way to use Seq.fold in F#

308 views Asked by At

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?

1

There are 1 answers

0
Brian Berns On BEST ANSWER

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

Related Questions in F#