Learning F#, created a somewhat elaborate fizzbuzz-builder implementation to see if I'm starting to "get" F# and thinking functionally:
type FBCase = FBCase of (int * string)
let makeFBCase num = FBCase (num, "")
let unpackFBCase case =
match case with
| FBCase (num, str) when str = "" -> num |> string
| FBCase (_, str) -> str
let fbBuilder (num) (str:string) input =
match input with
| FBCase (x, y) when x % num = 0 -> FBCase (x, y+str)
| _ -> input
let createFBFunc input = fbBuilder (fst input) (snd input)
let createFBPipeline inputList =
inputList
|> List.map createFBFunc
|> List.reduce (>>)
let createFBPattern inputList =
makeFBCase
>> (inputList |> createFBPipeline)
>> unpackFBCase
let fizzBuzz = createFBPattern [(3, "Fizz"); (5, "Buzz")]
let fizzBuzzBaz = createFBPattern [(3, "Fizz"); (5, "Buzz"); (7, "Baz")]
let fizzBuzzBazQuux = createFBPattern [(3, "Fizz"); (5, "Buzz"); (7, "Baz"); (11, "Quux")]
[1..100]
|> List.iter (fizzBuzz >> printfn "%A")
[1..100]
|> List.iter (fizzBuzzBaz >> printfn "%A")
[1..100]
|> List.iter (fizzBuzzBazQuux >> printfn "%A")
Where is it obvious that I'm not thinking functionally? What could I change to make a more idiomatic F#-style program? (Note that I don't really "get" monads or workflows yet, although I'm aware they exist).
1 Answer 1
I think it all seems quite functional. So nothing to argue about there. I like your use of List.reduce (>>)
.
Instead of the use of the union type FBCase, I would use the tuples directly in a way like this (some of the functions then may need a renaming):
let makeFBCase num =
(num, "")
let unpackFBCase (num, txt) =
match txt with
| "" -> num |> string
| _ -> txt
// Instead of input I use the tuple (num, text) to make it possible to call the elements directly
let fbBuilder (denom, str) (num, txt) =
match num % denom with
| 0 -> (num, txt + str)
| _ -> (num, txt)
let createFBPipeline inputList =
inputList |> List.map fbBuilder |> List.reduce (>>)
let createFBPattern inputList =
makeFBCase >> (inputList |> createFBPipeline) >> unpackFBCase
let fizzBuzz = createFBPattern [(3, "Fizz"); (5, "Buzz")]
let fizzBuzzBaz = createFBPattern [(3, "Fizz"); (5, "Buzz"); (7, "Baz")]
let fizzBuzzBazQuux = createFBPattern [(3, "Fizz"); (5, "Buzz"); (7, "Baz"); (11, "Quux")]
In this way I don't think you lose any clarity.
Although your efforts show a good understanding of details of functional programming, I think you make it all too complicated. The overall task is to map a list of integers to either the integer itself or a text in respect to one or more predicates. That can be done like this:
let fizzBuzz fizzBuzzes data =
let checkFizzBuzzes value =
let result = fizzBuzzes |> List.fold (fun acc (num, txt) -> if value % num = 0 then sprintf "%s%s" acc txt else acc) ""
match result with
| "" -> value |> string
| _ -> result
data |> List.map checkFizzBuzzes
let data = [1..30]
data |> fizzBuzz [ (3, "Fizz"); (5, "Buzz"); (7, "Baz"); (11, "Quux") ] |> List.iter (printf "%s ")