Correct way to suppress "warning: variable "state" is unused"? Use "_state" or create a dummy utilzation of "state"?

This may sound offensive around here which is not intentional, but I must be blunt - I absolutely hate dynamically typed languages. I love the structure and security of a proper IDE supported language like C++ or C#.

Maybe I am doing something wrong?

I am constantly afraid if I change something like a variable name, it will start triggering endless weird errors that become hard to chase down. I have had this experience with Javascript where i mistype something and then spend 20 min trying to find out why it won’t run. It is always quite annoying. Would never happen in a proper IDE like Visual Studio with C++/C#.

I am programming in VS Code for Elixir (or Javascript).

Take for example where here I am trying to implement @behaviour WebSock:

    #termination reasons here: https://hexdocs.pm/websock/0.5.3/WebSock.html#c:terminate/2
    def terminate(reason, state) do

        case reason do

            # The local end shut down the connection normally, by returning a {:stop, :normal, state()} tuple from one of the WebSock.handle_* callbacks
            :normal ->
                IO.puts("TERMINATED: NORMAL");
            
            #The remote end shut down the connection
            :remote ->
                IO.puts("TERMINATED: REMOTE");

            # The local server is being shut down
            :shutdown ->
                IO.puts("TERMINATED: SHUT DOWN");

            #No data has been sent or received for more than the configured timeout duration
            :timeout ->
                IO.puts("TERMINATED: TIMEOUT");

            #An error occurred. This may be the result of error handling in the local server, or the result of a WebSock.handle_* callback returning a {:stop, reason, state} tuple where reason is any value other than :normal
            {:error, reason}->
                IO.puts("TERMINATED: ERROR" <> to_string(reason));

        end

    end

Is this a reasonable structure for this based on the documentation of the list of possible reasons here?

Currently this gives me on build:

warning: variable “state” is unused (if the variable is not meant to be used, prefix it with an underscore)
│
37 │ def terminate(reason, state) do

Which I understand. I am not using state in the above. Sure. But what do I want to do about this to silence the warning which is meaningless to me? I must match the pattern. I don’t know if I’ll use state in the future.

Option 1 - rename state to _state as they say. It works. Is this a good idea? Perhaps not. If I start using state later, I have to rename it again. Then if I stop using state (comment out that code) I’m back to the warning popping up again. And what? I rename it again to _state? Seems insane.

If this was a statically typed language with a proper IDE to immediately tell me if I do something wrong, I would more readily just rename variables on ongoing basis back and forth. Maybe I just need to suck it up and deal with it.

Option 2 - create some dummy usage of state in this function that costs nothing or near nothing to run. Anything come to mind? Something like if (state === true) do end or state = state. Kind of wasteful probably no matter what as every operation adds up. I don’t know.

One idea I just thought of would be to wrap it in a case that will never be met like

            :never_gonna_happen ->
                IO.puts(to_string(state));

This does seem to suppress it with negligible code bloat/expense on run. Might be best way to me.

Option 3 - ignore the warnings or disable the warnings altogether somehow - I am already accumulating these warnings in just minimal projects. Pollutes the screen when running. But I don’t want to disable them globally as I might actually get some important ones at some stage.

What do you do about this?

In general, does it not drive you crazy coding in general and especially naming and renaming variables when there is no IDE to help you? And you are just working in a glorified text editor? How do you cope with working like this? Is there something I’m missing in terms of how I’m approaching it?

Thanks for any thoughts or advice.

Blunt or not, I understand where you are coming from.

First you are not just renaming a variable when you add the _, you are telling the compiler to ignore that argument, you could replace the whole name with just _, although _state is more clear towards the future.

What I do not agree with is that this warning would not appear when using a strongly typed language. If the LSP integration is working in your editor you would get the warning there when you are writing the function and not only at compile time.

Option 1 is the clear winner and default, if later on you need that variable you can easily find and “reactivate” it, the compiler will error out on you if you don’t.

Option 2 & 3 are worse as they trick the compiler and you never want to do that.

My opinion, as always it is your codebase and yours to maintain.

6 Likes

I so often find myself in agreement with @Hermanverschooten that I should likely pay him a visit and grab a dinner with him.

Option 1 to me is the clear winner as well: it says “there is a variable here but we don’t need it at the moment, or ever”.

I am not sure why this particular thing to do ticks you off so much though, it’s just one underscore.

That I agree with you that dynamic typing gets on your nerves from one point and on is sadly also true, and I work with Elixir for 8.5 years now.

3 Likes

Thanks guys, yes I just need to mentally adapt to the structure I must work in. I need Elixir so I must deal with it.

This example is more a tangential frustration which is coming from my dislike for untyped or dynamically typed languages given that they don’t allow any good IDE protections. I can manage 80,000-100,000+ lines of code in a personal project in Visual Studio in C++ or C# with zero stress. Automatic warnings if I rename something wrong. Immediate feedback. Very easy to read code and see what it does given types in and out of everything at every stage.

But even going past a few hundred lines in Javascript or now Elixir is always a continual stress. Feels like walking over a tightrope with no net. But this is just the nature of the beast. So be it. I will have to accept it. Maybe I need therapy. :joy:

Be careful because the _state is a perfectly normal variable, the _ prefix just tells the compiler not to warn me when I don’t use it. If I want to the I can use it but then the compiler may warn that I am using it. No rest :wink:

2 Likes

You are worrying too much. Elixir may be dynamic but it’s still compiled. You can’t reference undefined functions or variables at runtime without throwing an error during compilation. Furthermore, Elixir’s variables are very tightly scoped—renaming a variable will have no consequences outside of its scope (unless you’re doing some particularly dark macro stuff). You’ll get an error that will tell you exactly where the problem is with a little ASCII arrow pointing right at the offending column. Language servers can inform you about this as soon as you hit save giving you that IDE experience.

This isn’t to say that there aren’t footguns, like referencing map keys that don’t exists via Access. This is more a matter of a mentality shift to program for dynamic languages but more specifically for Elixir/BEAM programming. There are safe ways to navigate a lot of these things.

We recently did some mass renames on two different projects, one was 65k lines and the other 220k lines. Both went smoothly. Was it as smooth as picking “rename” from a dropdown? Of course not, but neither took much time and we went on our merry way.

In any event, all I’m saying is: relax :wink:

2 Likes

I hear you but I wouldn’t ever go as far as to call C++ and C# code readable :003:

That much is true IMO as well. I cope with it with tests and Dialyzer… or by just being stressed and paranoid. :smiley:

1 Like

TIL thx

And you are just working in a IDE that you need to spend years to hone your skill with?

It is a skill issue one way or the other. I prefer to develop skills that are more transferable.

2 Likes

I think this is what you’re saying but I wanted to provide a concrete example of the compiler warning when you try to use a variable that starts with an underscore:

def fun2(num, _state) do
  num + _state
end

Compiling the above results in a warning:

warning: the underscored variable “_state” is used after being set. A leading underscore indicates that the value of the variable should be ignored. If this is intended please rename the variable to remove the underscore

│
│    num + _state
│          ~
│
└─ lib/compile_test.ex:7:11: CompileTest.fun2/2

And you can effectively turn the warning into an error by adding a mix compile --warnings-as-errors check in CI which is something I try to include in all my projects.

@axelson I think what he is saying is that those are real variables and that if you are trying to use it in code it can bite you:

Erlang/OTP 27 [erts-15.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1]

Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {_, _} = {1, 1} # good
{1, 1}
iex(2)> {_, _} = {1, 2} # also good
{1, 2}
iex(3)> {_hi, _hi} = {1, 1} # good
warning: the underscored variable "_hi" appears more than once in a match. This means the pattern will only match if all "_hi" bind to the same value. If this is the intended behaviour, please remove the leading underscore from the variable name, otherwise give the variables different names
└─ iex:3

{1, 1}
iex(4)> {_hi, _hi} = {1, 2} # match error
warning: the underscored variable "_hi" appears more than once in a match. This means the pattern will only match if all "_hi" bind to the same value. If this is the intended behaviour, please remove the leading underscore from the variable name, otherwise give the variables different names
└─ iex:4

** (MatchError) no match of right hand side value: {1, 2}
    (stdlib 6.1) erl_eval.erl:652: :erl_eval.expr/6
    iex:4: (file)
iex(4)>

Note the warning on iex(3) (which is new to me and am SUPER happy about it) which tries to help you from not getting bit. A few years back we were matching in function heads (we had it as a fallback, it should just ignore, with a var like _ignore or something, but it didn’t and led to some head scratching). So its kind of burned in my memory. That warning would have helped a ton back then :joy: Unsurprisingly at this point, Elixir is :heart:

2 Likes

I just want to say I have:

  • Added Elixir LS for Visual Studio Code
  • Started running VS Code from Ubuntu WSL (Windows) via code . in Ubuntu command prompt (necessary as some of my packages require Linux and will otherwise error with Elixir LS)

Much much nicer experience. Lots of checking and auto-correction/suggest. Feels normal now. No longer just typing and editing blindly. Anyone on Windows should try this.

Also changing default indentation to 4 is essential in my opinion.

1 Like

All well and good, though if you ever want to contribute to a library, the formatter is two spaces and you may not have such a good time if you don’t get used to it. But ya, that’s a whole big debate in software in general which I don’t want to touch :sweat_smile:


Speaking of style, in another one of your recent posts you had if written like so:

if (foo == bar) do

It happens to work here but you should be aware that () on their own (not touching an identifier) denote a block kinda like {} in curly brace languages. For example:

if true, do: (
  foo = bar()
  baz(foo)
)

For illustrative purposes only, you can even do:

foo = (
  bar = "baz"
  bar
)

When they are touching an identifier (really just a function call) only then do they become argument list delimiters. It happens to work fine with if since it is single arity, but there is a subtle difference. if(1) means “pass 1 as the first argument to if” while if (1) means “pass the block (1) as the first argument to if.” For functions with arities larger than one, putting a space between the function name and the first paren won’t work.

So to give you one more crazy example I would never suggest anyone do:

if (
  a = 1
  b = 2
  b - a == 1
), do: "hi"

Change the first line to if( and the whole thing barfs.

All that to say I would try and get used to not using parens with if but if you insist, it would be more consistent to write:

if(foo == bar) do
  foo
end

Anyway, that is a lot of words for a smallish thing, but it’s something I’ve experienced people getting tripped up over so I thought I’d share. You are of course free to code as you wish!

2 Likes