Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Elixir, is there any way to make _real_ constants?

I find it a little misleading and confusing that while Elixir boasts immutability, that immutability is buried between layers of mutable abstractions.

For example,

if I have the code:

foo = {:cat, "Puff"}
bar = foo
foo = {:cat, "Pepper"}

I can expect bar to remain {:cat, "Puff"} But foo's changing value can make it difficult to reason about.

When I first read of module attributes, I thought they would provide some solution, but it was pointed out to me that this is perfectly valid code:

defmodule Cats do
   @cat "Puff"
   def foo, do: @cat

   @cat "Pepper"
   def bar, do: @cat
end

Would it be possible to rely on function definitions remaining constant? Eg.

defmodule Cats do
   def foo, do: "Puff"
   def bar, do: "Pepper"
end

Could such code be considered idiomatic? Are there any reasons I shouldn't do this?

Is there any other sort of entity I can declare as having some value without any possibility of some other code giving that entity a new value?

like image 266
Brian Kessler Avatar asked Aug 10 '19 18:08

Brian Kessler


1 Answers

Could such code be considered idiomatic?

No.

Are there any reasons I shouldn't do this?

A ton.

You do not need constants in BEAM in the first place. Just use "Puff" and "Piffles" everywhere. New memory won’t be allocated. This is also true for any term. The last bug with tuples was fixed in 1.6.

So there is no need to create constants at all.

If you want to shorten the name to call a value by, you can define a macro, that will be inlined by compiler everywhere. This is more efficient than calling a function (slightly, but still.)


After reading through all the comments, I got an impression that we might be trapped by the terminology here. There are many things people call constants in CS. E. g. in Java there are final int i = 42; and public enum Math { PI }. There are constants like in Ruby CONST Pi = 3.14159265. And there are constants like in Javascript const i = 42;.

They all differ. Java enum is once set, never changed. Ruby allows resetting. Java final and Javascript const are mostly the same, the subtle difference might be omitted without a loss of generality.

So far so good. You seem to talk about Javascript const. Elixir has a greatly shaped scoping. One cannot accidentally modify anything. But there is a rebinding. One cannot rebind the variable from inside another scope, though.

iex|1 ▶ foo = 42
iex|2 ▶ if true, do: foo = :bar
#⇒ warning: variable "foo" is unused
iex|3 ▶ foo
#⇒ 42

Assigning foo inside a block, macro/function call, closure, any different scope would not modify the original variable.

The only possible rebinding may occur within the same scope. If we are talking about local variables in functions, the chances one would accidentally rebind the variable not on purpose are nearly zero (and it is covered by this great article by José.) If we are talking about module attributes, they are private to the module scope, inlined by compiler, inaccessible from outside. One might use both accumulated and not-accumulated module attributes and there is a way to declare a real constant in javascript way by declaring the attribute being accumulated and refer to the head of the list, but nobody uses it that way, because module attributes are usually declared on top of the module and it’s very easy to grasp whether there are duplicated names.

Long story short, const in Javascript saves developers from shooting their legs in the cases that are simply impossible in Elixir because of immutability and scoping.

Show any example of the code where you think you need a const and I’ll show how it should be refactored to be an idiomatic Elixir without using anything const-like.

like image 110
Aleksei Matiushkin Avatar answered Sep 21 '22 19:09

Aleksei Matiushkin