DEV Community

Discussion on: Should function arguments be reassignable or mutable?

Collapse
 
lambdude profile image
Ian Johnson • Edited

Personally, I prefer the functional style. I like it because it sets you up for parallelism without a lot of code changes. I also like that it's declarative, rather than imperative. I'm also wary of mutability in general.

I'm starting to think that non-reassignable and read-only should be the default. If I want a mutable argument, I can mark it.

In Rust, they have the mut keyword. Things are immutable by default and you have to explicitly mark something mutable.

Equivalent signatures from Rust are as follows:

fn calc(values: Vec<f64>) -> f64;
fn calc(mut values: Vec<f64>) -> f64;

And taking them by reference:

fn calc(&values: &Vec<f64>) -> f64;
fn calc(&mut values: &Vec<f64>) -> f64;

Another situation that gives me pause is argument sanitization.

For sanitization, you could use variable shadowing inside blocks to explicitly define the scope of the shadowed variable:

float calc( float a, float b ) {
    // a = 1.0
    // b = -2.0
    if (b < 0) {
        a = -a; // a = -1.0
        b = -b; // b = 2.0
    }
    // a = 1.0
    // b = -2.0
    ...
}

calc(1.0, -2.0);

Or, even better, you could use pattern matching:

fn calc(a: f64, b: f64) -> f64 {
  match (a, b) {
    (a, b) if b < 0 => {
       do_something(-a, -b)
    },
    (a, b) => {
       do_something(a, b)
    }
  }
}

With pattern matching and guard clauses, you could make all of your validation clean and have it cover all of the variants without having lots of nested constructs (like loops and conditions).

Pattern matching is super-powerful. Here's a fizzbuzz example using match:

fn main() {
  for i in 1..101 {
    match (i % 3, i % 5) {
      (0, 0) => println!("FizzBuzz"),
      (0, _) => println!("Fizz"),
      (_, 0) => println!("Buzz"),
      (_, _) => println!("{}", i),
    }
  }
}
Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

Thanks for pointing out Rust's immutable by deault. It makes me more comfortable taking the same approach. I handle references and shared values a bit differently, but it appears to be an orthogoanl concept.

Yes, variable shadowing is definitely an option. The one danger it opens is last-shadowed variables, where something early uses the original name, and something later the new name, but both in the same scope.

Pattern matching looks like a clean approach. I don't always like creating separate functions, but I could always use a local function definition, or combine it with lambdas in the simple cases.