I would like to know if there is some more "elegant" way to write these 3 functions. Any tip or idea is welcome.

open System

module Calculus =
    let area f a b =
        let dx = 0.001
        seq { for x in a .. dx .. b -> (f x) * dx }
        |> Seq.sum

    let rotate f x =
        let y = f x
        y * y * Math.PI

    let volume f =
        area (rotate f)

let main args =
    let f x = x * 2.0

    printfn "Running..."

    printfn "%f" (Calculus.area f 1.0 10.0)
    printfn "%f" (Calculus.volume f 1.0 10.0)



I understand the method for integration I am using here is not the best around, but it is not the point here. Let's focus on its implementation.

  • 2
    \$\begingroup\$ Looks good to me. Not much to review! One small possible improvement: f |> rotate |> area \$\endgroup\$ – TheQuickBrownFox Sep 27 '17 at 19:28

Here's how I would rewrite this:

First, pipe everything that you can. It's easier to follow the linear transition of a |> b |> c rather than c (b a).

Second, compose where possible. Compositions are powerful, and can allow you to abstract things away much more effectively.

Third, order function parameters by most -> least generic. We see f a b, but F# sees let f = f a; f b, so make sure your parameters are ordered by least-significant first.

module Calculus =
    let area f a b =
        let dx = 0.001
        { a .. dx .. b }
        |> Seq.sumBy (f >> (*) dx)

    let rotate f x =
        let multX = x |> f |> (*)
        Math.PI |> multX |> multX

    let volume = rotate >> area

let main args =
    let shape = (2.0 |> (*), 1.0, 10.0)
    printfn "%f" (shape |||> Calculus.area)
    printfn "%f" (shape |||> Calculus.volume)

This ends up being three lines longer, but it's clearer. It's completely obvious what is happening, and the piping makes it easier to follow. We replaced the custom sequence generator with a basic one, and used sumBy to make our intent clearer. We want to sum f(x) * dx.

This also ends up one line shorter, and that's only because we rewrote volume as a pure composition, and pushed it to a one-line definition. I recommend doing this for things that are 2-3 steps. (So even let volume f = f |> rotate |> area would be acceptable.)

Instead of building the f in the main, we build the shape itself, and then pipe all three arguments of that shape to our area and volume methods. (Since the arguments are the same for each call.)

This means our shape is reusable, and we could build a function:

let printShape s =
    printfn "%f" (s |||> area)
    printfn "%f" (s |||> volume)

let shapes =
    let f = 2.0 |> (*)
    [(f, 1.0, 10.0); (f, 2.0, 10.0)]
shapes |> List.iter printShape

Now we can build more test-cases pretty easily and functionally. Because printShapes takes a single parameter of a (float -> float) * float * float, we can pass the entire shape to it and then pass each portion of the tuple as individual parameters.

  • \$\begingroup\$ I really like your version of volume, but I find your version of rotate overly complicated. Anyway, thank you for showing me some different ways to think functionally. \$\endgroup\$ – Gabriel Sep 30 '17 at 3:16

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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