Is it common to create accessor functions for struct fields?

Hi,

I’m wondering is it my thinking process or this is the norm among the Elixir developer for the use of Struct and accessor functions (get/set)?

For example:

defmodule Session do
    defstruct [:name]

    # this is just example as there might be 
    # more transformation before putting the value inside the struct
    def set_name(%Session{} = session, name) do
        %Session{session | name: String.upcase(name)}
    end
    
    # likewise there might be more transformation before returning the value
    def get_name(%Session{} = session) do
        String.downcase(session.name)
    end
end

The reason to create the accessor is somehow (probably OO habit die hard, or not?) along the argument of to isolate the user of the struct to access the key directly to allow future changes to the struct key without affecting the caller. For example later if the struct change the field to :customer_name, caller using the get_name() function would not aware the field has changes its name.

But this pretty much feels like OO thinking in me. Is that an alternative or is that a sensible way to design this structure?

Cons is now get/set is everywhere inside the struct module.

Thanks!

Regards

There are lots of packages dedicated for structs:
Search results for: struct access @ hex.pm

Many of them adds support for Access behaviour, so you can use it with *_in functions like get_in.

That’s rather not about FP or OO paradigms, but about struct documentation. Some parts may be private, so if they are not well documented then the developer assumes that specific keys are private and should be not changed by hand. Documenting all fields would require a support for them in future as long as you would not hard deprecate them.

User-defined types | Typespecs reference @ Elixir documentation

Not really objective if you do not update the object … No worries for that. If it’s not a “simple struct” then you can follow Phoenix generators i.e. use context-based functions to fetch data from database and use struct (in ecto it’s called schema) module to define other helper functions like get_full_name etc.

If you’re asking if people regularly create struct getters / setters in Elixir then I’d say no.

There are legitimate exceptions when you need calculated values or you need to massage the value but they always were the very small minority in my work.

I’m seeing no reason to generate them, too. Elixir is quite terse; whenever you need a few of them, typing them out still looks like the fastest way of doing it.

1 Like

Hi thanks for the answer.

But if we allowed directly accessing the key won’t that be a nightmare for code maintenance and refactoring later in the stage?

Why would it be a maintenance nightmare? Do you plan on changing those important structs every week?

Of course changes do happen, yes, but nobody has actually found a way to isolate the programmer from having to refactor and re-test, I believe.

If you find yourself in a situation where you have to modify important business code structs so often then that’s a symptom of other problems.

If you get out of your head the idea that tying data structures + behavior to modules is a good idea, you will understand that operating with data structures directly, without tying them to additional logic that is part of the same module, is easier to reason about in the code and more maintainable.

I honestly don’t like the fact that structs are tied to a module, there are technical reasons why it was done like this, however at the same time it sends a wrong message to people coming from OOP languages. The scope of the structs is to have something that resembles enforceable types, that can be checked easily by tools like dialyzer, tying behavior to those types is not the most optimal way to write code.

If you are just starting out, my advice is to just avoid using structs until you get a good hang on how to write elixir, use guards/pattern match to achieve the same guarantees structs offer. If you’ll use data structures that are not tied to any modules for some time, it will start to click in place the ideology where you design pipelines that transform data, instead of imperative logic tied to a module.

2 Likes

Thanks for the insight.

For example in use case like there are data that is belongs to a group and I don’t want to spin off a GenServer since there is no reason to spin up one. The logic can be a library that a group of functions working on a group of data that is similar in nature. For example I have a login, password, role, contact fields those belongs a particular user. If this is not structured inside a struct/map, how would this be stored under non GenServer approach?

I may have a function to check for login, another for password, yet another check on role. If a struct/map is not provided then we left for the caller to find a way to keep that related info at their side?

My usual use case for struct/map is to keep those related info in a single module so that I have a holistic view on what fields being passed around between the functions and to reduce the parameter need to pass into each functions. Is that a correct way to think about this?

GenServer is a runtime construct, completely different to how a class in languages like java works, each genserver is associated with a process. You use genservers when you have concurrency involved (think of them as analogy to green threads or coroutines), you shouldn’t use them for data encapsulation, as that is a clear misuse in most cases.

You don’t have to define all the contracts with structs, that is one way to do things, but not the only one. Since elixir is dynamic by nature, you can have dynamic data structures, for which you can enforce a shape when you need to. A very generic example:

credentials = %{username: "Daniel", password: "123123"}

def check_username(%{username: username}) do
...
end

def check_password(%{password: password}) do
  ...
end

The data structure is dynamic, however you will get an error instantly if you are missing one of those fields, this is partial validation and it’s used a lot in elixir. You could also enforce the structure completely:

def check_credentials(%{username: _username, password: _password} = creds) do
...
end

Elixir inherently doesn’t suffer from this problem, you can build your custom types on the fly and since you have pattern match, you don’t have to pay for structure deconstruction with imperative code like in many other languages:

tuple = {:this, "tuple"}
map = %{key1: "value", key2: "value2"}
list = [a, b, c]

# I want the first element of the list
def hello([head | _tail]) do

# I want all the list elements and I know the list has 3 elements
def hello([first, second, third]) do

# I have a well-defined tuple where I know the second element is a string
def hello({_first, second}) when is_binary(second) do

# I want to make sure the map has key1 present, don't care about other ones
def hello(%{key1: _value} = map) do

# Finally, you can pass all these values around without deconstruction
# if you don't care about their contents from context of that function
def hello(tuple, map, list) do

This might be fighting your static typing nature at first, however this is just as maintainable as having those structs specified in a module plus with the addition that is a lot more readable and makes for code that is easier to understand.

This now sounds like a bunch of Ecto.Schema modules to me. You can model them that way, especially if you have persistence coming down the road (databases).

But no don’t use GenServer for this, it’s something that “lives” – as in, runs continuously – and it’s not necessary for what you have in mind.

Structs are completely fine but you do indeed have to learn to do pipelines of transformations. Thinking in “methods” and “getters / setters” will not help. Think of what must happen.

Speaking of which – what’s your goal here? What code do you lack and what code do you want to have?

My doubt is how to structure the Elixir code so future maintenance is easier and is my current way to use struct is the way it should be done. Not really lacking anything just to verify the thinking / software design of how I approach Elixir. I’ve background in Java, DotNet, C & Ruby however, not sure I’m bringing the correct thinking/design methodology.

This reminds me of a Scope struct that a coworker wrote.

It is still just a data structure with keys for the current user struct, the user permissions/roles, and a few other things like the organization.

It used guards heavily for the “important parts” but after the guard matched in the function head or in the function body, digging into the keys wasn’t a big deal because the compiler would complain if you tried to access keys that shouldn’t exist.

Modifying that Scope struct when logging in, or authorizing them, was done with setters, but that was more because of the sensitive nature of the use case and consistency, not because of the an Elixir idiom.

1 Like

This is a valid use case for “setters”. Sometimes a struct contains complex data structures and in order to expose a simple API, it’s convenient to use a “setter”.

1 Like

Hopefully this doesn’t come off too negative, I’m just poking some fun.

Something I’ve noticed about functional programming communities is that you absolutely have to use the correct terms. Talking about getters and setters will just get people mad because you don’t need that stupid overengineered OOP nonsense. Call them lenses and suddenly you’re an intellectual who understands the value of hiding the exact shape of the data.

I have yet to find an acceptable term for dependency injection, which is by far the thing I miss the most in functional languages.

That being said, defining functions for accessing simple struct keys is far from being idiomatic in Elixir, so you’re almost certainly better off not doing that and instead treating the shape of the data as your “interface” and pattern matching on it.

You can of course pre-calculate more fields based on existing ones when creating the struct, sou you could keep backwards compatibility this way if the internal representation needs to change. Of course, this only works as long as the rest of the code isn’t creating your struct directly, but rather through your own function that calculates the other fields. But don’t dare call this a constructor, we don’t do that here (/s).

Can you show an actual example (even with code that doesn’t work) where dependency injection would be useful? I have used in kotlin and I still can’t shake the fact that it covers flaws related to classes, I can’t see an actual use-case where it would be useful to have them in elixir.

Okay, then use Elixir idioms – no getters or setters unless you need logic at places (classic example: get_full_name that combines first and last names). That makes it pretty maintainable as you don’t use superfluous layers of abstraction.

And then there’s me who never got mad but still cringes at both. (Though lenses are still the better descriptive term IMO.)

Every week or so, there is a new thread asking about testing some kind of an external side effect. Inevitably, people recommend five different mocking libraries, each with its own set of drawbacks, and everyone says that their particular use case doesn’t work with at least three of those.

This obviously seems way more complicated than it should be, but what’s worse, it ignores hard earned knowledge about good testing practices. Mocking something that happens ten layers deep in the code that is being tested, always results in tests that know too much, and therefore break too easily.

How would you solve this in Elixir if you were all-powerful and could do anything at the snap of a finger?

I don’t think it’s really possible, which is probably why there isn’t a great solution. I would either pass dependencies explicitly, or set them up in config and hope I will never need more than one setting per environment. That’s often good enough. Until it isn’t.

Well, if we had perfect solutions to every problem, this and every other programming forum would no longer exist as programming would be now a 100% solved problem. :smiley:

IMO the FP approach to all this is great because it makes you think really hard about dependencies, and uncoupling them, and/or making those dependencies configurable.

This still nets you bigger clarity of yours and others’ (that you depend on) code. Some say “needless information about how stuff works below” but to me it’s helpful to lift a mythical veil and have a better understanding.

Speaking of dependency injection, in FP it’s called an argument to a function. I really don’t understand why most people writing Elixir just don’t like passing dependencies via function arguments. If you don’t state what your logic depends on, it depends on everything and it’s almost impossible to isolate and test.

4 Likes