3
\$\begingroup\$

I read recently that ENIAC originally was capable of 5000 additions per second. Naturally, I had to test my own laptop's capabilities and this became an instructive experiment in F#, which I suck at.

So, I am looking for feedback in two areas:

  • What is the most concise way to write this in F#, or at least most closely aligned with the functional paradigm?

  • Did I miss something for performance? Can this be done in a much more performant fashion?

module Program

open System
open System.Diagnostics

[<AutoOpen>]
module timingStuff = 
    let pickRunmode() = 
        Console.WriteLine("choose number of processes to spawn (default 1)")
        Console.ReadLine()

    let feedback (input : string) = 
        Console.WriteLine("you picked {0}", input)
        Console.WriteLine("now running")

    let quitOrContinue() = 
        Console.WriteLine("q to quit else continue with keypress")
        Console.ReadKey().KeyChar <> 'q'

    let rec addOne x stop = 
        let r = x + 1
        if stop() then r
        else addOne r stop

    let finished stopticks = (fun _ -> Stopwatch.GetTimestamp() >= stopticks)
    let secondsToTicks x = x * 1000L * 10000L
    let prettyNumbers (x : int) = String.Format("{0:#,0}", x)

    let printResult result start = 
        let final = (Stopwatch.GetTimestamp() - start |> TimeSpan).TotalSeconds
        let pretty = prettyNumbers result
        sprintf "performed %s operations in %f seconds" pretty final |> Console.WriteLine

    let op duration start = 
        duration
        |> secondsToTicks
        |> (+) start
        |> finished
        |> addOne 0

    let opAsync duration start procs = 
        [ for i in [ 1..procs ] -> async { return op duration start } ]
        |> Async.Parallel
        |> Async.RunSynchronously

[<EntryPoint>]
let main argv = 
    let loop() = 
        let input = 
            match Int32.TryParse(pickRunmode()) with
            | true, i -> i
            | _ -> 1
        input
        |> string
        |> feedback
        |> ignore
        let start = Stopwatch.GetTimestamp()
        let result = opAsync 1L start input |> Seq.sum
        printResult result start |> ignore
        quitOrContinue()
    while loop() do
        ()
    0
\$\endgroup\$
2
  • \$\begingroup\$ Hi, welcome to Code Review! This is a great first question and I hope you receive great answers! \$\endgroup\$
    – Tunaki
    Commented Apr 18, 2016 at 8:50
  • \$\begingroup\$ Instead of sprintf .... |> Console.WriteLine, you can simply do printfn ...., which does the same thing. \$\endgroup\$ Commented Apr 21, 2016 at 18:37

1 Answer 1

1
\$\begingroup\$

So for my skill level there are some minor recommendations. So for me Feedback would be a little bit more readable in this form:

let feedback input = 
    [   sprintf "you picked %s" input
        "now running" ] |> List.iter Console.WriteLine

There are some brackets that can be reduced, so simple write.

Console.WriteLine "q to quit else continue with keypress"

And finally there is no need to ignore unit, since ignore is a code smell and you actually don not need them so just ignore them.

And that is what I changed it to:

module Program

open System
open System.Diagnostics

[<AutoOpen>]
module timingStuff = 
    // no side effects
    let secondsToTicks = (*) (1000L * 10000L)

    let prettyNumbers (x:int) = String.Format("{0:#,0}", x)

    // no side effects if stop has side effect (but here it has)
    let rec addOne x stop = 
        let r = x + 1
        if stop() then r
        else addOne r stop

    // with side effects    
    let pickRunmode() = 
        "choose number of processes to spawn (default 1)" |> Console.WriteLine
        Console.ReadLine()

    let feedback input = 
        [   sprintf "you picked %s" input
            "now running" ] |> List.iter Console.WriteLine 

    let quitOrContinue() = 
        Console.WriteLine "q to quit else continue with keypress"
        Console.ReadKey().KeyChar <> 'q'


    let finished stopticks = fun _ -> Stopwatch.GetTimestamp() >= stopticks

    let printResult result start = 
        let final = (Stopwatch.GetTimestamp() - start |> TimeSpan).TotalSeconds
        let pretty = prettyNumbers result
        sprintf "performed %s operations in %f seconds" pretty final |> Console.WriteLine

    let op duration start = 
        duration
        |> secondsToTicks
        |> (+) start
        |> finished
        |> addOne 0

    let opAsync duration start procs = 
        [ for i in [ 1..procs ] -> async { return op duration start } ]
        |> Async.Parallel
        |> Async.RunSynchronously

[<EntryPoint>]
let main argv = 
    let loop _ = 
        let input = 
            match Int32.TryParse(pickRunmode()) with
            | true, i -> i
            | _ -> 1
        input |> string |> feedback
        let start = Stopwatch.GetTimestamp()
        let result = 
            opAsync 1L start input 
            |> Seq.sum
        printResult result start 

        quitOrContinue() 

    while loop() do ()
    0
\$\endgroup\$
2
  • \$\begingroup\$ Why is ignore a "code smell"? In the current case, the code can be written so that it's not needed, but when using Framework/third party code that requires using it, there's nothing wrong with it. \$\endgroup\$ Commented Apr 21, 2016 at 18:45
  • \$\begingroup\$ Usually you should be interested in the result of a function call. If you get back none or null something did not work as expected, and you can react to it, ignoring it can lead to code that is not reasonable why predictability is important. \$\endgroup\$ Commented Apr 22, 2016 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.