Is it acceptable to use mutable values to avoid passing values through many ‘levels’ of code?

I have a number of values - for example, maximum number of decimal places that numbers should be printed with, etc. - which are defined by the user in the UI but are only used by the code at the ‘deepest’ level of the code, they are not needed elsewhere.

I don’t really want to have to pass these values in at the ‘top’ of the code and have to keep passing them down ‘inside’ the code until they get to the piece of code that actually uses them.

Is it acceptable to use a mutable value in the way I have it in the code below:

let mutable maximumNumberOfDecimalPlaces = 3 // Value set at application start-up.

let getMaximumNumberOfDecimalPlaces = (fun () -> maximumNumberOfDecimalPlaces)
let round (getPlaces :unit->int) (number :float) = Math.Round(number, getPlaces())
let printNumber rounding places (number :float) = sprintf "%A" ((rounding places) number)
let printRounded = printNumber round getMaximumNumberOfDecimalPlaces

let numbers2 = [ -1000.1; -200.32198; -40.0; 0.0; 0.000005; 1.2; 22.49; 345.5678 ]

let x = numbers2 |> List.map printRounded // This will be deep in the actual code.

maximumNumberOfDecimalPlaces <- 1 // Value is changed by the user.

let y = numbers2 |> List.map printRounded // This will be deep in the actual code.

val x: string list =
 ["-1000.1"; "-200.322"; "-40.0"; "0.0"; "0.0"; "1.2"; "22.49"; "345.568"]
val y: string list =
 ["-1000.1"; "-200.3"; "-40.0"; "0.0"; "0.0"; "1.2"; "22.5"; "345.6"]

Or is there a better way of doing it?
Am I thinking about the problem in the wrong way?

That’s a pretty philosophical question, so there’s no clear answer, and any answer kind of depends on what you’re looking for. In the end it’s going to come down to tradeoffs and you/your team’s values. The obvious advantage to using the mutable variable is you can implement it with very little code change. You set the mutable variable at the top of the program, and you use it in the deeply nested code, and nothing in between has to change.
The advantages to being more explicit and passing that value in through all the functions that need it (assuming they are otherwise “pure”) are:

  1. You can see by looking at the functions’ input arguments exactly what information goes into the output. The function signature is a form of documentation about what data is used in the production of the output.
  2. Calls are repeatable - if you see some weird behavior in production, you can just capture the inputs to the function and get the exact same behavior locally. There’s no state you have to also try to capture and reproduce.
  3. Similarly, you can construct unit tests that just call the function directly. Your tests don’t have to bother with setup.

It’s up to you to figure out for which style the benefits outweigh the costs.

If you have a whole collection of stateful values, and they get consumed in a lot of places, you might consider the Reader monad from FSharpx, but then everyone who works in that code has to understand monads. That’s kind of a whole separate topic. And it would be complete overkill for just the simple case you describe here.

Thanks for your advice – it all sounds good to me.
I would prefer not to use mutable values, for the reasons you gave.

I think I may have been looking at the problem the wrong way and I have the vague basis of an idea which might let me do what I want without mutables.

I will have a look at Fsharpx but would prefer not to go too deep into monads at the moment – I think I sort of understand the basics but as soon as ‘things get real’ that understanding seems to fly out of the window.

P.S. I’m not working with a team, it’s just me learning this by myself (hence the really basic questions), but I understand the need to use techniques which are understandable by others.