When to use Struct?

I read the “Programming Elixir” book, here is quote from the book:

At some point, the old object-orientation neurons still active in the nether regions of your brain might burst into life and you might think, “Hey, this is a bit like a class definition.” And you’d be right. You can write something akin to object-oriented code using structs (or maps) and modules.
This is a bad idea. Not because objects are intrinsically bad, but because you’ll be mixing paradigms and diluting the benefits a functional approach gives you.

Having this in the back of my mind, today I’m making a simple app which relies on data comes from a JSON API. The data structure is deeply nested and the API on different end-points uses different keys for same objects (imagine in one it returns name in another it returns title). So I thought I’d convert those JSON data to different structs and define a to_normalized_struct function in those to convert them to a struct that provides a same interface to be used through out the app.

But then… I thought maybe I’m coding OO here and may face wrath of functional programming gods later. So when is right to use structs? How would you implement the code for above problem?

1 Like

My 2 cents. The way out of the OO trap is to think data and transformations.

I can’t answer your specific question w/o knowing the larger context, it might make sense to simply create a transform function for each JSON type to a standardized data form. Or it might make sense
to avoid the central form and just make transforms that extract the data you need.

The way to answer that question is to think about any transforms you need for the resulting data. What data structure makes the most “sense” in your problem? Rather than modeling “things”[1], start thinking about transforms and the inputs and outputs they need.

The Struct construct handy in that it provides both an easy to read access syntax and it constrains your transforms to a specific set of keys in a Map. However, the problem with Struct is the dual of it strengths, you lose the ability to dynamically change the structure of the data as the problem evolves. Struct requires that you “model” the problem in advance. This is not a terrible thing; in fact the strong type people would claim this is a very good thing. It does take away some of the flexibility and power of a dynamic language.

So as a practical guide, I’d suggest that if you are either constantly adding fields to your Struct or creating many slightly different, but almost the same structs, then you probably don’t understand the problem well enough yet to use a struct. The other thing to be aware of is that Struct only constrains the keys, not the values. If you find yourself putting wildly different values under the same struct key, then you’re probably in the weeds.

[1]- Experience suggests this is also the source of most problems with OO design as well.

6 Likes

I am just going through the Elixir and Phoenix Bootcamp course and in my notes I have written:

Structs

Just like Maps but with 2 advantages:

  • Can be assigned default values
  • Additional compile-time checking of properties

If we know the properties we are going to be working with we will use a Struct instead of a Map.

3 Likes

elixir-lang: Structs

Structs are extensions built on top of maps that provide compile-time checks and default values.

Maps aren’t necessarily less OO than Structs - it’s all about how you use them.

OO on the other hand will often manage and encapsulate “state” within object instances - so if most of your “transformations” look like they are simply advancing the state of a thing that is represented by a single key-value collection then there may be cause for concern because the ultimate judgement is context sensitive. For example it is accepted practice that a single key-value collection can represent the state of an Elixir process.

I think sloppy terminology isn’t helping in this case. While it’s called “JavaScript Object Notation” JSON doesn’t contain objects in the OO sense because an OO object collocates data and the functions that operate on it - JSON isn’t allowed to contain any functions - so you are left with pure data. Also does “name” and “title” actually return data that is identical or do they merely return data that has the same shape - i.e. given that “name” and “title” fulfill a similar role do they simply use the same data type?

2 Likes

It returns identical data. Imagine an API endpoint for searching products, and one for getting info about an specific product. The former returns an array of products and latter returns one product. One of them has name attribute and the other title but with same value and purpose.

That’s a valid point, the way that I tend to divy it up is to think of the flows within the application. Each flow or path through the application will likely require it’s own unique data structure; if you’ve got one GIANT map that is passed through multiple flows, then that’s probably a warning flag in the opposite direction. You can write the GOD object in any language (i.e. fortran common )

[quote=“arashm, post:5, topic:2396”]
One of them has name attribute and the other title but with same value and purpose.[/quote]

:rolling_eyes:
I would lean towards parsing the data in both cases into a generic Map and then generating the normalized structure from the data in the Map. I’d basically end up with 3 modules, one for the normalized struct, one for the product collection conversion and one for the single product conversion - possibly a fourth for commonalities between the last two - depends on how (in)consistent they are.

Though not necessary, I would typically expect more than one data structure/shape per flow. As the data moves through the transformation pipeline the shape tends to pick up derived/looked up details that drive towards the desired end result.

A useful comparison of structs and maps is made in When to Use Structs, String-keyed Maps, and Atom-keyed Maps.

It lists six rules governing the best practice usage of these data structures.

  1. Always Use String-Keyed Maps for External Data.
  2. Convert External Data to Structs ASAP.
  3. Use Structs in All Other Code.
  4. Use Structs for Output Data.
  5. Avoid Using Atom-keyed Maps That Aren’t Structs.
  6. Use Keyword Lists Only for Function Arguments
8 Likes