kip

kip

ex_cldr Core Team

Inspect/2 output for structs: executable code or structure?

TLDR;

What should I emit for inspect/2 output of library structs? Executable code, string representation or default (outputs the struct fields)?

Summary

In April, I changed the output of inspect/2 for Cldr.LanguageTag.t to be executable code rather than the default structural output. I did this to align with what I understood is the emerging pattern for Elixir structs but not everyone agrees.

I decided to review the inspect/2 output for all the Elixir core structs (summary below)

It appears, as I had understood, that most structs emit executable code these days. Using a sigil if one is part of kernel and a function call if not.

The ones that do not emit executable code appear to do so because there is no mapping from the struct to code - perhaps because the struct holds some state like File.Stream. And it makes sense for a PID to be represented a a string because the struct is opaque. I think URI might be an outlier - it could be URI.parse!(uri_string) I think, without losing any information.

I didn’t seen anything in the Elixir Antipatterns guide.

What is core teams guidance and what are community expectations for inspect/2 output for a struct?

Output executable code

  • Date
  • Time
  • DateTime
  • Date.Range
  • NaiveDateTime
  • Regex
  • MapSet
  • Range
  • Exceptions (emit their name only, which I count as code)

Output structure

  • URI
  • File.Stream
  • Version

Output string representation

  • PID

Most Liked

josevalim

josevalim

Creator of Elixir

The standard library is not a great example because Date, Time, Regex, Range, etc, all have built-in sigils and operators, imported by default, which are compact and justify using the same notation for printing them. If we remove those from the equation, we end-up with:

  • URI, File.Stream, Version - output the struct
  • MapSet - uses MapSet.new
  • PID, Ref, Port - output the string representation

Given providing built-in sigils/operators is not practical outside of stdlib, here is the main chart:

  • Can I expose the struct representation without leaking implementation details?
    • If yes, output the struct (such as URI, File.Stream, etc)
    • If no, is there an executable version?
      • If yes, use it (such as MapSet, Decimal, etc)
      • If no, print a string version (#PID<...>)
13
Post #4
LostKobrakai

LostKobrakai

MapSet is an opaque type. It means the internal representation might change at any time – even has in the past, so not just a theoretical thing. Therefore it cannot show you the internals, because then you start depending on it (like pasting from one elixir version to the next might break), which is something to be avoided.

Previously it did inspect as #MapSet<[…]>, which made the mapset a comment in elixir syntax, meaning you couldn’t paste the inspected value into a shell or file and have elixir turn it back into an actual MapSet.

To help with that downside many of such structs were switched to an “executable code” representation, which is still valid elixir code, which happens to evaluate to the inspected value without needing to know the internals. That’s MapSet.new([…]).

Where it gets more into the space of “tradeoffs” is when it’s not clearly a opaque type, like with Decimal. It is a public and documented struct, but who want so see %Decimal{exp: -4, sign: 1, coef: 152345} vs. Decimal.new("15.2345") in the common case – for the uncommon case there’s always inspect(…, structs: false).

  • I think this works well because this is lossless. The information and value behind their evaluated data between both options is the same. There is a 1:1 mapping between all possible value in both forms and the conversion between could be implemented as a pure function.
  • Even with decimal not being an opaque type you’re generally not expected to dig in the individual fields of that struct. The expectation is that you use the Decimal api for any manipulation on a decimal.
  • That kinda boils down to “decimal” being considered a self-sufficient type, a value that’s (usually) not to be subdivided – similar to e.g. MapSet

From the issue @kip linked it seems those bullet points might not apply to how one would interact with a language tag in cldr.

LostKobrakai

LostKobrakai

To me “exectuable code” inspection format is a (better) alternative to the previous convention of having #Struct.Name<…> format. But I don’t expect it in places where no custom inspect implementation is needed and/or makes sense, so I don’t consider that new pattern a general one to be applied everywhere.

acalejos

acalejos

@josevalim This is a very useful breakdown and I’m glad the original question was asked.

Would this make sense in the documentation somewhere? Maybe under Library Guidelines?

josevalim

josevalim

Creator of Elixir

Perhaps we could document it under Inspect itself?

Where Next?

Popular in Questions Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

We're in Beta

About us Mission Statement