Why is it bad to check for values you don't want (eg nil values)?

:face_with_spiral_eyes:

2 Likes

In their partial defense, this was before we got the is_struct guard (which landed in Elixir 1.10).

Though for structs I still prefer direct pattern-matching and always will.

2 Likes

I just don’t see the advantage, other than that it lets you refer to the module as a variable in the body, which loops me back to “I don’t see the advantage.”

2 Likes

One more follow up question regarding function declarations. Do you always pass parameters as structured data versus just passing the values as independent parameters?

For example (and assume these are all just unrelated params):

def do_stuff( user_id, user_lastname, file_id, filename, is_manager) do
...
end

VS

def do_stuff( %{user_id: user_id, user_lastname: user_lastname, file_id: file_id, filename: filename, is_manager: is_manager} ) do
...
end

Pattern matching and guards aside … is it considered bad form to pass params as independent data values (first example) vs as a structured map (second example)?

I do as well. I am only wary of the comparison to type enforcement because it seems like there’s a negative reaction from people coming from statically typed languages…That POV being you can’t really call it “type enforcement” if your system still crashes at runtime, now just with function match error (or even just the same error but different line). I dunno I don’t have much experience (basically none) with typed languages in general so I’m not really sure how guards compare really.

2 Likes

Having a lot of separated parameters is a problem that all programming languages face.

Some of them like python, kotlin solved it partially by using named arguments:

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }

reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)

In elixir, since we can define maps on the fly, the best analog to named arguments is just to create maps on the fly. Then you can either enforce the structure by pattern match (as in your example) or allow some of them to be nullable, by accessing them in the function with param[:user_id].

1 Like

It seems that you already received a lot of great answers on this topic. A general note on the null(or other similar keyword that has the same meaning) from other languages and why this kind of confusion appears in the first place.

A lot of languages embraced the idea that all/most types should have union with null, that is completely a different type. Since this was implemented by language designers, there is no way to somehow enforce a value to not be null by the compiler, so the only option left is to deal with nullability in your own code.

A lot of modern languages acknowledged this issue and fixed it, in kotlin, c#, rust, etc. you no longer have to deal with null issue, the absence of a value is marked explicitly and only then you are forced to deal with it.

In elixir we inherently don’t have this problem, as the types are homogeneous (even if determined at runtime). The only way for nil to appear in your code is to explicitly allow it in your flow. A simple example would be:

  • map.user_id - this will raise if the key is missing, will never allow any nil values into the flow;
  • map[:user_id] - allows to have the key missing, you explicitly allow nil into the flow. In this case another pattern commonly used to avoid explicitly dealing with nil is map[:user_id] || default_value, but of course this is not applicable to all use-cases.
2 Likes

Here’s an infuriating answer: ItDepends™.

There is the “extract parameter object” refactoring technique that can generalize to maps in Elixir like you are suggesting, or to keyword lists, or even to dedicated structs (libraries do this when they want to add validation i.e. when you are constructing and modifying configuration given to them, and I applaud this practice).

Often, if you have too many parameters to a function and some of them are optional, in Elixir people do deal with that by using a keyword list like so:

def update_user(
  user_id,       # mandatory, cannot be skipped
  fields,        # optional fields
) when is_list(fields)  do # half-enforce a keyword list at runtime. 
  # ...
end

And then you call it like so:

update_user(1, username: "who_am_i", date_of_birth: ~D[1998-11-16])
update_user(2, email: "no@way.inhell")
update_user(3, first_name: "Nanya", last_name: "Biznis")

etc. This is convenient and feels nice to write but have in mind that you’ll have to do some work inside your function to (1) extract out the allowed fields and ignore everything else and (2) validate their values e.g. you definitely want username / first name / last name / email to be strings and date of birth to be, you know, a date and not a floating-point number or an atom.

So while it’s pretty nice to use an API like this, some suffering has to be expended by the implementing dev. I haven’t found it a big deal or even difficult at all but definitely did find it annoying at times, and some boilerplate cannot be avoided.

But I am known to get annoyed by any boilerplate so take my opinion with the requisite amount of salt. I am not 100% objective.

1 Like

That is super useful! I have a couple of functions that would work better with that approach. I don’t know why I never thought of using “optional fields.” That said, I see your point about the work you have to do inside the function so it’s not for everything.

And I am definitely going to use “no@way.inhell” the next time I’m trying to log into an airport WiFi and it demands an email address! Ha ha ha.

Also note the word-play of the full name “Nanya Biznis” which is a butchered variant of “None Of Your Business”. I got the idea from the first PS4 “God of War” game. Kratos’ son asked something one of the dwarves that repair and craft stuff for them and the dwarf replied: “Nanya” and then Atreus was like “What?” and the dwarf finished “Nanya business”. Gave me a good laugh back then.

1 Like

Oh I definitely laughed over that one too. I’m planning to use that one too the next time a website demands my name or I sign up for a store points card!

Thank you to everyone who replied to this post. I have a much better understanding of guards and “nil” after this discussion. And I definitely have some code to clean up!

1 Like