Hello. Newbie here:
My question is: Is it possible to pass a struct field as an argument of a function?
I have seen that if you have a struct, then you can use %Struct_name{ old_struct | field_name: new_value }.
What I want is to have a generic function, say, change_struct_field(data, field_name, new_value). The problem is that I don’t know how to pass the (field_name) to become (field_name:).
Structs are just maps with atoms for keys. So you can do:
def change_struct_field(data, field_name, new_value) do
Map.put(data, field_name, new_value)
end
# and call it like
change_struct_data(%StructName{some_field: "some value"}, :some_field, "some other value")
I have to apologize because what I want is actually to rebind the “Strctuct object” I am passing to the function to its new value after the field value is change.
Sure, if I use: Map.put(data, field_name, new_value), it will give me the new object, and I can do:
data = Map.put(data, field_name, new_value). If I do this in my function definition, I get the warning that data is not being used.
So, I thought that what I want is:
def change_struct_field(data, field_name, new_value) do
data = %StructName{ data | field_name Map.put(data, field_name, new_value).field_name}
end
and this will give an error, of course, because the struct constructor is expecting key value pairs.
Actually. when thinking about this, my question could be more general, and becomes: how can I pass certain value and make it an atom? AM I right in this thinking?
There are no objects. Only immutable data and functions.
If you have a struct where you want to change a single key, you have to create a new based on the old, only changing that key. There is no changing in place as you would do it in most OOP or imperative languages.
You can make a String into an atom with the function String.to_existing_atom. There is not much difference between using the %Struct{data |} syntax and using Map.put - both simply create a new term with the modified data. The %Struct syntax gives you some compile time guarantees that you are not updating an undefined field, which makes it good for general cases but useless for dynamic updates.
Be sure to understand the warnings that come with String.to_atom/1!
Warning: this function creates atoms dynamically and atoms are not garbage-collected. Therefore, string should not be an untrusted value, such as input received from a socket or during a web request. Consider using to_existing_atom/1 instead.
Mostly. The data before the = is a ‘new’ binding, unrelated to the original data binding from the function head. It basically gets compiled to data__1 where the original data from the function head was data__0. The BEAM VM only allows binding a name once so Elixir fakes it by just incrementing an invisible counter on the name on each usage, like OCaml does.
(Well, technically internally a lot of pointers exist, but they only point to immutable data as well, this makes it so you can make a crazy efficient GC the likes of which things like the JVM could never even hope to achieve, but they are not exposed to the user-side nor would they have any use if they were).
This might not be the proper place to ask this, but how can one create a variable that references another variable, and if you change the first, the second is changed automatically?
This is absolutely the case of the XY Problem. But in ‘general’ you want to invert the data access. If you give a complete example in, say, python or something simple, I can show you how you’d traditionally rewrite it in an immutable language like elixir (or ocaml or haskell or whatever).
If you do respond with an example you want me to convert, please post that in a new thread, it will be a great learning opportunity for more people too!