Specs

If you’ve been reading my blog for a while, you should know by now that I’m a huge fan of dialyzer. So, my first step, if I have the task to write the function described above, would be to write a spec for it.

This may seem tedious, but doing just that will lead us to the first important realization. Our function answer should produce the answer to one thing, namely the Ultimate Question of…

That means, our function should likely have just one parameter…

-spec answer(questions:t()) -> answer().

Actually, since it will only provide an answer to the Ultimate Question, we might as well have a proper type for it…

-spec answer(questions:ultimate()) -> answer().

That way we don’t need the catch-all clause from the image above, since dialyzer will be able to let us know if someone is calling this function with something that’s not the Ultimate Question and, if that’s the case, we can simply let it crash!

That’s great but we just transferred the problem. Now we need a function to build a question, something like…

-module questions. -export [new/??]. -opaque t() :: …

-opaque ultimate() :: …

-export_type [t/0, ultimate/0]. -spec new(??) -> t().

I’m assuming here that questions:ultimate() ⊆ questions:t() (i.e. all ultimate questions are questions, too) and that new/?? can generate both the Ultimate Question and also regular ones. And those question marks over there are exactly what we are struggling with: what is the Ultimate Question about? The requirements are not clear here. Of course, it’s science fiction! Such a thing is unheard of in real life, right? 🙄

For simplicity, let’s assume that our module questions can generate questions based solely on their topics/subjects (represented as atoms) and that the Ultimate Question is the only question that the module can generate given the right topics, as the code in the blog’s header seems to imply. In other words, there can be many questions about different topics, but if and only if you provide the right ones, the function will generate the Ultimate Question.

Even with those super-strong assumptions, we still have many many options. Let’s ponder some of them, shall we?

Three Combined Entities

One way to look at this problem (which seems to be the one that ErlangExplained uses) is to think that Life, The Universe and Everything are 3 different topics and the ultimate question is about them all together. If that’s the case, one way of modeling this would be the one seen in their header:

new(life, universe, everything) -> ultimate_question();

new(Topic1, Topic2, Topic3) -> …

But what would be the spec of such a function?

-spec new(topic(), topic(), topic()) -> t().

That means we can only build questions about 3 topics. Of course, we could also have versions of new with 1, 2, 4, 5, etc… arguments, but we will still be modeling a world in which questions can have just a limited set of subjects.

Also, what happens if someone alters the order of the arguments (e.g. questions:new(universe, life, everything) ), shouldn’t that generate the Ultimate Question, too?

I would personally recommend a different approach in this scenario:

-spec new([topic()]) -> t().

new(Topic) ->

case lists:usort(Topic) of

[everything, life, universe] -> ultimate_question();

SortedSubjects -> …

end.

This model represents a world in which you can generate questions about as many topics as you want, but if you choose the three relevant ones, you’ll be building the Ultimate Question.

Three Different Entities

Another way to interpret the sentence would be that 42 is the answer to The Ultimate Question of Life, The Ultimate Question of The Universe and also The Ultimate Question of Everything.

With this interpretation, we only really need to know one main topic to build a question, and we can build the right one depending on it…

-spec new(topic()) -> t().

new(life) -> ultimate_question(life);

new(universe) -> ultimate_question(universe);

new(everything) -> ultimate_question(everything);

new(Topic) -> …

Now our functions let us build 3 different ultimate questions (assuming that ultimate_question/1 only accepts those 3 atoms) but answer/1 will still evaluate to 42 for each of them.

42 as the Answer to Everything

OK, but what if Everything is not an individual entity but a way to represent literally anything. In other words, what if the Ultimate Question about Everything is not a question about everything at once and it’s a generic question that can be asked about any particular thing (for instance, Life or The Universe). It’s a stretch, I know, but (maybe because English is not my native language or maybe because I’m just complicated like that) that was the way I understood the book when I first read it.

In my mind, the Ultimate Question can have any topic. Which means that being ultimate is not a property that we can derive from the question topic. We need something like this, instead:

-spec new_ultimate([topic()]) -> ultimate().

new_ultimate(Topics) -> ultimate_question(Topics).

We are now exposing an interface to build any ultimate question that we want, in particular, we can build exactly the one in the blog header: questions:ultimate([life, universe, everything]).

But I want answers!

That’s all good and nice, but how will we actually implement answer/1 ?

Well, since we already had the spec and we don’t really need to know anything about the question that we’re receiving except for its type…

-spec answer(questions:ultimate()) -> answer().

answer(UltimateQuestion) -> 42.

🤔 If you try to compile that, erlc will promptly tell you that UltimateQuestion is unused. So, the code should actually be…

-spec answer(questions:ultimate()) -> answer().

answer(_) -> 42.

But then, if you have a function where one argument is ignored in all its clauses, why do you have that argument at all? (Except when implementing behavior callbacks, of course) We can safely remove it and just update the system where it is calling this function not to provide that parameter (maybe renaming the function to get the same level of documentation from it)…

-spec answer_to_ultimate_question() -> answer().

answer_to_ultimate_question() -> 42.

Well, then… if the whole goal of our system was to evaluate that function, and it does not require us to produce any question anymore, we can get rid of the questions module altogether, right?

In that case, we would’ve generated a system that provides the answer to the Ultimate Question about Life, the Universe, and Everything regardless of what that question actually is. In fact, the system provides no way for us to generate the question… For that, we will need a different system…

“Look, alright, alright,” said Loonquawl, “can you just please tell us the Question?”

“The Ultimate Question?”

“Yes!”

“Of Life, the Universe, and Everything?”

“Yes!”

Deep Thought pondered this for a moment.

“Tricky,” he said.

“But can you do it?” cried Loonquawl.

Deep Thought pondered this for another long moment. Finally: “No,” he said firmly.

I think we have modeled Deep Thought perfectly :)