Struct fields as arguments

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:).

Is there an idiomatic way to do this?

Many thanks

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")
1 Like

Many thanks for your reply.

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.

2 Likes

Many thanks. Yes, I guess, I was thinking the OOP way.
I need to think differently :slight_smile:

Hassan

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.

Many thanks. I researched, and found String.to_atom, and String.to_existing_atom.

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.

That’s because data isn’t used. Your code of:

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

Is setting data = ... but this data binding isn’t used afterwards, it’s just immediately returned from the function.

Thanks, but doesn’t data = … make a rebinding of the … to data?

I see. Thanks again

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. :slight_smile:

Only locally inside of change_struct_field - the name that passed in the value is still bound to the original value.

So changed values (i.e. copies that are different from the original immutable) values have to be returned.

Which is what happens in this case because the value of the last expression is returned.

Aha… So this = is seriously not an assignment operator :slight_smile:

That is why it’s called the match operator.

1 Like

Does this mean that you don’t have pointers in Elixir?

It is not, it is a matching operator. It takes the right side and tries to match it to the left side, ‘binding’ any names it comes across. :slight_smile:

Thus a fully unbound name like data ‘binds’ to the entirety of the result on the right. :slight_smile:

Remember, there is no mutability, nothing ‘changes’.

Correct.

(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).

Thanks again.

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?

1 Like

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). :slight_smile:

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! :slight_smile: