When to define types?

I’m not sure this is the right section to ask this question, because it’s mostly stylistic, but I’d like it if more experienced elixirists (alchemists? which one is it) could give their personal opinion.

I have a store (just a map, actually) that stores user credentials. Credentials are just strings (String.t).
I have some functions that take credentials as arguments or return credentials.

Should I define something like

@type credential :: String.t

?

This makes type signatures clearer, but obscures the fact that credentials are strings.
If this were Haskell, I’d define a newtype or even a datatype for credentials, and it would protect me from dumb errors, but I worry that in elixir defining a new type might obscure things without any practical benefits for someone who calls the function.

4 Likes

The use case you suggest is entirely valid and a good use of typespecs. It’s very convenient to define the credential type once and then re-use in multiple type signatures.

obscures the fact that credentials are strings

Keep in mind that ExDoc has very good support for typespecs. They are included in the module documentation, and each type is linked to its definition.

For example: Plug.Conn — Plug v1.15.2

While this might be true, I would argue that it’s good for documentation. A function that operates on credential should be declared as such and not for arbitrary strings (even when you have no ability to actually restrict it via type only, compared to, for example, Haskell’s type constructors).

The obscurity of it is valid concern, but can be solved if the editor can somehow expand type definitions (Atom + atom-elixir at least can be told to Show Elixir Docs, which includes type defs).

1 Like

When you decide later on, that credentials are so much more than just a plain String.t, you can easily change a single @type credential to whatever you came up with now, no need to walk your complete codebase and decide per case if you need to change this String.t to the new enhenced type or if it is just a string.

A very good example might be String.t, String.codepoint, and String.grapheme itself:

1 Like

I’m a newb as well but my inclination is to put off declaring types until the thrashing has settled. During the early stages of a project or feature or whatever, it just makes for more accounting. Once the dust has settled, and other humans might have to start consuming documentation is when I’d add them. At least that’s my approach right now.

In something like Haskell the types are part of the program. In Elixir typespecs are linter help and documentation. (At least that’s my impression)

That may actually mean that you have already decided not to declare types at all - I understand that your are trying to cut down on the cognitive overload right now - but the earlier you start “thinking about/in types” the more consistent your function signatures will be.

For example, when I was working through “React Up And Running”, Flow wasn’t introduced until Chapter 7 where most of the sample application was already firmly established - introducing type checking at that point can only be described as “infuriatingly disruptive” - the experience would have been a lot smoother if type checking was introduced from the start.

1 Like

I disagree with that. I mean, I understand the logic behind the statement but I’ve yet to see that born out in my experience. Often the admonition to contemplate type signatures before writing functions ends up being an exercise in premature design. Much like writing documentation for a function call before writing the function call itself.

Everybody is different, for sure. I often find myself doing a lot of exploratory coding before I settle into something sane. It’s not so much the type checking that puts a spanner in the works there, but the accounting involved in updating the declarations. That’s just my experience with this language so far in the last several months, so I may find myself coming around to another way of looking at things.

However my intuition from working with, e.g. Javascript type annotations fits fairly well with this. Likely, it’s just different dev styles or mental models though. If it works for you I certainly don’t intend to yuck your yum :slight_smile:

I personally use @type when I want to use @typedoc. If I have a String.t that isn’t immediately obvious what the purpose is or needs a special description, then it gets a @type.

Meaning that if the credential type was something like username@password then I would have a @type and an @typedoc for that type. In this case I don’t find the name credential descriptive enough to figure out what it is, so it gets a @type and @typedoc.

However, if I have something like username type. I would probably just do:

@spec func(username :: String.t, password :: String.t)

That way I am being more descriptive than just String.t but I don’t feel like it’s special enough to need its own @type declaration.

2 Likes

Oh, another place I use @type is when there a ridiculous number of possible other types. For example:

@spec cast(pid | name | {:global, name} | {:via, module, name}, msg :: term) :: :ok when name: atom
@spec call(pid | name | {:global, name} | {:via, module, name}, msg :: term) :: term when name: atom

vs

@type destination :: pid
                   | name :: atom
                   | {:global, name :: atom}
                   | {:via, module, name :: atom}

...

@spec cast(destination, msg :: term) :: :ok
@spec call(destination, msg :: term) :: term

It’s all about explaining purpose and maintaining scannable understanding to me.

4 Likes

Wait, you can do that? I thought you could only do @spec func(String.t, String.t) and not naming them…

1 Like

Haha, I don’t know how to answer except to say yes you can.

It’s pretty useful too, it adds just enough information in some cases.

1 Like

True, I have somehow skipped the last few bits of the Typespecs documentation. I think I got thrown off by the fact that it’s not mentioned in the getting started guides. Either way, thanks for the info! #TIL

1 Like

I marked this as solved by @Azolo quite arbitrarily, but all responses were helpful in their own way. There should be a way to mark more than on post as a solution.

1 Like

I think most people just use the “like this post” control for that.

1 Like