Elixir In Action Book Club!

This reminds me of an article by Joe Armstrong that I read long time ago. He mentioned about how Elixir gets closures right and a few other syntax observations he did (on trying out Elixir for the first time).

This is the link. Since we are reading chapter 2 I think this could be a nice supplement since he talked about syntax and his opinions of it (some historic artifact too like the ← operator)

4 Likes

This reminds me of an article by Joe Armstrong that I read long time ago. He mentioned about how Elixir gets closures right and a few other syntax observations he did (on trying out Elixir for the first time).

This is the link . Since we are reading chapter 2 I think this could be a nice supplement since he talked about syntax and his opinions of it (some historic artifact too like the ← operator)

Very cool. I’ll check it out, thanks!

1 Like

Fun fact: in my original plan, chapter 2 was supposed to cover the material from the current chapters 2, 3, and 4 :sweat_smile:

I agree it is a very long chapter. It’s also fairly unexciting, mostly a bunch of declarative facts. I spent considerable amount of time thinking how to restructure it, and, but didn’t come up with anything good. In the end, I decided to leave it as is. I could have done a chapter split, but it would still be the same amount of material. So I think it works better as a single chapter, because it communicates the general strategy of establishing the basic vocabulary in a single iteration, and then moving on to more interesting things.

To some extent I feel the same about chapter 3. For the next edition I’m considering splitting that one into two parts: conditionals and loops, with the loops part getting some more detailed treatment.

All great points & catches! Note to self: invite Björn to be the reviewer of the next edtition :sweat_smile:

Original plan was to have the full chapter on macros, but I couldn’t find a good place to fit them in. So I decided to completely ditch them, which was a somewhat unusal choice. However, since later in the book we end up using a bit of macros (from the plug library), I decided to add a super short intro to chapter 2, so people aren’t completely confused by it.

And luckilly there is one! Metaprogramming Elixir: Write Less Code, Get More Done (and Have Fun!) by Chris McCord

Thanks for noticing! The book isn’t meant to be a complete reference, but I also don’t want to leave readers stranded, so I opted for the generous linking to the official docs (which are a great resource).

Yeah, the example is contrived. Some frequent practical examples of this syntax are referencing nodes (:"foo@bar.baz") and special process dict keys (:"$ancestors", :"$callers").

8 Likes

Haha. Well, from a completely selfish perspective, I appreciate the split. I usually try to finish a whole chapter per “session”. So the longer they are, the more my brain melts. :rofl:

100%. Even while I was mentioning that Chapter 2 felt a little long to me, I also completely agree with you that the content itself works together as one chapter. The chapter is long, and the chapter is also done well.


EDIT: I almost forgot. There was one line in 3.1.7 Matching bitstrings and binaries which read a little odd to me.

“rest::binary states that you expect an arbitrarily sized.”

An arbitrarily sized what?

I think it’s either missing a word or I’m potentially reading it completely wrong. Just a heads up.

2 Likes

Just finished Chapter 2. I found it a good, fast paced chapter that covers a lot of ground, and strikes a good balance between getting through the concepts while still giving enough detail.

There is so much to like in Elixir/Erlang; this chapter touches on a couple of things that are perhaps not recognised very often but quietly hold things together behind the scenes…

  • Immutability is such an important concept that make large programs easier to reason about. In the past while working on OOP systems I’ve spent many hours trying to figure out where a particular field of an object was changed, but now using Elixir with small functions and immutable data that class of problem simply disappears. Not to mention concurrency etc.

  • IO Lists is like this magic way of concatenating strings efficiently and it’s what helps to make Phoenix templates so fast :rocket:

Section 2.4.4 mentions Kernel.length/1, the Elixir docs mention the naming convention that length runs in O(n) whereas size runs in O(1). Here is the reference …
https://hexdocs.pm/elixir/1.16.2/naming-conventions.html#length-and-size
I only recently came across this and the trick to remember it is that length has the letter n :smile:

2 Likes

I also finished chapter 2 and everything just confirmed the impressions I had before: the book is very well structured and the reading flow is good. I have not much else to say here.

2 Likes

Chapter 3

The two biggest things I’m taking from this chapter:

  • A new understanding of pattern matching in general, and seeing plenty of examples here was really helpful. It’s clear how this can be powerful/helpful. I really enjoy the ease of this syntax:

    defmodule TestList do
      def empty?([]), do: true
      def empty?([_|_]), do: false
    end
    
  • Stream. Loved learning more about this module. I had seen Stream.something in passing while looking at Elixir code which was processing a CSV, but knew nothing about it and didn’t have a need to find out more at the time. Now that I’m diving into Elixir, it’s neat learning about this module and some use cases early on. I like to play around with file processing/manipulation when learning a new language, so this will be fun to explore further.

Another really informative chapter. On to the next!

Have a good week, everyone.

4 Likes

Just got your book - starting to read it while pairing it with my Exercism Elixir track.

3 Likes

Chapter 2

This is the long due chapter 2 review.

Unsurprisingly, this is a long chapter, and the naming of the chapter couldn’t be better.

The chapter nicely separates structure (modules, blocks), action (function, closure), and data (map, list) in a very nice way, as a person who is re-reading this book, versed with Elixir, and coming back to it professionally after some hiatus (apart from AoC), I certainly found it not boring (I was suspecting I would). The order those three blocks were introduced and the way knowledge transitions were made, makes up the core strength of this chapter, which I wish to see followed in other languages.

When I introduced folks to Elixir, my order ot familiarizing folks used to be data, function, structure. Fire up the REPL - create some lists, some maps, show how to transform through pipes, and making the introduction to function (keep macros hidden and let them believe it’s function for a bit), then write functions within modules (without reasoning what modules are, like “public static void main” is just assumed to be there when seeing Java), and then make the transition to module. It’s a ride with some leaps of faith and a few hiccups, but those three building blocks can be introduced to a new comer in any order.

This chapter has a much seamless way of presenting. Starting with blocks, then function, and then data. There is no (We will come to it later), and all this information that might as well by three chapters, feels like rightfully and naturally, one chapter. This I feel like, and felt like when I read previous edition, one of the best ones of this book, one I would adopt if I ever write one.

In gist, this is an introduction to Elixir language, and all you expect from a language is present here. No surprises. As usual, each paragraph crafted with care, flow well thought up.

Now for a funny bit, I was reading this book on my tab and didn’t realize it was 2nd edition (well the colour isn’t as different as 1st vs 2nd was). So I ended up reading the wrong “Chapter 2”. And Elixir being a more or less stable as a language didn’t help either, I mean, if it were JavaScript, I would have caught it on the second sentence.

So, I read the correct edition, and out of curiosity opened the chapter side by side and did a quick scanning for diffs. Here are a few that caught my eye:

  • Addition of #iex:break – I have seen so many folks having “Aha” moments when I introduced them to this when I was working in Elixir teams. This is a valuable tip and an addition to this chapter.
  • A nice sidebar on multiline structure
  • System.stop is the winner (as it should be) of the who’s more polite contest!
  • Has (thankfully) so, outputs of Code.fetch_docs printed out, this gives readers an initial idea of the structure.

Well that’s about it, not many other big changes that caught my eye, apart from a few formatting goodies.

This will be it, my experience with chapter 2-s :slight_smile:

4 Likes

I’ve finished reading chapter 3. I started a bit late this week and didn’t have time for all of the practice exercises. Here are a few comments collected as I went along.

3.1.7 Matching bitstrings and binaries

One possible use mentioned for the first part of this section was for handling network data. It reminded me of cross-network messaging using XDR (XML Data Reduced - no tags) where each field is a different size based on the data type and had to be translated in the specific order of the message definitions. It made me wonder how I might define similar messages using these patterns. I’m curious now how that thinking may get refined as I continue working through the book.

The binary string example had me thinking of if/how regular expressions might be used in similar cases, for example, where there is more than one space between ‘ping’ and the url. ~r/ping\s+/ <> url = command
Of course, this isn’t quite the same as using String.match. But, it would be interesting if this type of extended Regex matching + assignment would be possible.

3.3.2 Classical branching expressions

The cond expression description reminded me of how I sometimes handled state machines, though usually with quite a bit more code in other languages.

3.4.4 Comprehensions

The nested iteration example reminded me a bit of compacted perl code where I needed to study it some to figure out what it was doing.

3.4.5 Streams

The Stream.filter example code was described as being dense, which is what I had thought of earlier with my comprehensions comment above. In this case, the piping made it easily readable and a bit elegant. When I used to write perl code, I always tried to keep it readable even if at the expense of not always being as compact as it could have been. When you return to something a year or more later, it is not always obvious what you may have been thinking at the time you wrote it. So, I prefer the ability to have code that is both concise and readable as in this example.

Overall, chapter 3 was an enjoyable read about a powerful set of constructs. It was really interesting thinking of these in light of the more imperative languages I usually work with. I’m starting to get jealous of those who get to work with this on a regular basis. :stuck_out_tongue:

2 Likes

Chapter 3, done!

I think it was a great idea from Saša to highlight the elephant in the room for most newcomers: no loops, no while, and multiclause functions replacing conditional constructs! Further still, starting from pattern matching was definitely the right call here. So much of how one writes Elixir is underpinned by pattern matching that diving into it first feels very necessary.

Reading about guards is fun to me because of how they might have a special role in utilizing the set-theoretic type system in the future! Of course that’s still a very unknown and uncertain future, and it wouldn’t make sense for the book to speculate on their potential effect, but I find it intriguing to think how the 4th edition might indeed say a word or two about the type system here.

I found it interesting that the book chooses not to showcase the else branch of a with in this introduction. I wonder if this is done to avoid accidentally nudging readers down the path of complex else clauses in with -anti-pattern? I bet that would’ve undoubtedly required more space and more words to explain sufficiently, and the section clearly isn’t meant to throw everything at the reader at once :slight_smile:

I really enjoyed reading this chapter, as it reminded me of just how joyful I felt when learning Elixir for the first time. The language just clicked with my brain in a way no other language had before, and much of that is thanks to excellent authors like Saša.

How did our other book club members find the chapter? :eyes:

4 Likes

Chapter 3

This was a rather pleasant read. If I hadn’t known about pattern matching, this could have been full of AHA moments and WOWs.

I feel like, if anyone experienced with at least one programming language reads chapter 3 before chapter 2, they wouldn’t have as deteriorated experience as one would think. With a little tweak and assumption, these chapters can be read in any order.

Recursion was well explained. I like to think tail recursion became my second nature if I need to use them (Thanks to Project Euler and 99 Problems back in the days), but I agree with the sidebar on Tail vs Body recursion. I might add an article on this: Erlang's Tail Recursion is Not a Silver Bullet as a nice supplementary read.

I wish JavaScript wasn’t used as the initial example for reduce though. It’s not a fault of the book, but a fault with my intolerance of that language.

With my distaste for poorly designed language aside (sorry not sorry), let me add that Enum is one of my favourite modules, in any language. What it (along with its cousins Map, MapSet etc - House of Enumerable) provides never fail to impress me whenever I think about solving problems.

The explanations were fantastic, and the shift to Stream felt natural. My only complain there is that I wanted a bit more piped up examples, but I can understand how the focus is on “Higher Order Functions” and pipes, while it sure feels like it, but really involves macros and overusage of it would shift the focus a little.

The section on Stream deserves a re-read, especially in November when Advent of Code approaches.

I wonder if a few utilities of Enumerable should have been explored in this chapter (i.e. reduce_while, group_by etc), I see especially in Clojure books where they often dedicate a nice summary of useful and thought provoking sequence functions.

All in all, a great chapter that is at par with chapter 2 in terms of foundation building, but a much smaller one.

4 Likes

Just finished Chapter 3 - I found it to be concise and very well written!

Perhaps the most important line for me was…

If an error is raised from inside the guard, it won’t be propagated, and the guard expression will return false.

Also, good to read again about Streams - must see if I could/ should work them into my code a bit more.

One more Chapter to go and then onto Part 2 - where the real fun starts :rocket:

4 Likes

Hi AstonJ,

I am interested in this book-club.

Do you meet in person or via video-conference?
Where are you located?

Chrichton

1 Like

Part 2 (and Part 3)!!!

I am going to do extra time and finish up Chapter 4 so I don’t miss out all the great discussions we will be having on those chapters.

I believe true power of this book club will be experienced when that comes!

2 Likes

This thread is the meeting place @Chrichton - this means anyone from any timezone (or skill-level) can take part :smiley:

If you’d like to join, just jump right it :023:

I have now read chapter 3. Overall it is another excellent chapter. I only have a few comments.

Actually, I think it could be cleanly split into three chapters:

  • conditionals and pattern matching
  • recursion
  • high-order functions and comprehensions

The sidebar named “Tail vs. non-tail recursion” contains good advice, but in my opinion it needs some qualifications.

It is true that tail-recursion is over-used, and in many cases body-recursion would work just as well or better than tail-recursion. One reason for the popularity of tail-recursion is that it was usually much more efficient than body-recursion in the original JAM virtual machine. To a lesser degree, that also applies to ancient versions of BEAM.

The advice in the sidebar applies to tasks for which tail-recursion and body-recursion use the same amount of memory and have roughly the same performance. In general, that applies to most functions that return a list.

The advice in the sidebar does not apply to tasks where the tail-recursive implementation runs in constant space, while the body-recursive implementation uses stack space in proportion to the size of the input. There is never a good reason to use body-recursion for such tasks. Generally, such tasks don’t return lists but simpler terms such as integers. Some examples of such tasks are:

  • Calculating the sum of a list of integers (as the sum/1 example)
  • Calculating factorials
  • Printing a sequence of numbers

I think that the distinction between tasks that should always use tail-recursion and for tasks for which there is a choice should be pointed out.

While writing this comment, I started out writing more elaborate rules for when one had a choice of tail-recursion vs body-recursion. I then realized that I could boil it down to this simple rule-of-thumb that should work in most cases:

If a function returns a single list, it probably doesn’t matter efficiency-wise whether it is implemented using tail-recursion or body-recursion. However, if it returns something else, a tail-recursive implementation is almost always more efficient.


In 3.4.3, High-order functions, there is a lambda for determining whether an integer is odd:

fn x -> rem(x, 2) == 1 end

That will not work for negative integers because rem produces a negative result:

iex(1)> rem(-1, 2)
-1

Elixir has inherited that behaviour from Erlang, which in turn has inherited it from the % operator in C.

I suggest rewriting the lambda to:

fn x -> rem(x, 2) != 0 end
4 Likes

Binary :slight_smile: Somehow the word disappeared from the text :frowning:

Yeah, that’s basically the reason. There are occasional good cases for else in with (e.g. convert each type of error into :error, e.g. while authenticating), but most of what I’ve seen are abuses of complex else clauses as mentioned in the anti-pattern docs.

I used it because it would arguably be understandable to most of the readers.

Enum has some great functions, but what I like even more is how it works with multiple data structures (maps, lists, ranges, …), and how you can make it work with other types (including the ones you don’t own). A bit more on that in chapter 4 :wink:

The advice there aims to be deliberately vague, partially because I didn’t want to go into elaborate discussions, and partially because in practice n is often too small to matter :slight_smile: Another reason is that in my impression many people consider tail to be universally superior to body, so I tried to counter that sentiment. In my experience, the perf diff is often insignificant, and body is often clearer than tail.

That said, your observation about the constant space is great, and I’ll see about rewording and expanding this sidebar in the next edition, or perhaps even turn it into a full paragraph or two.

Great catch!

3 Likes

I’ve now read chapter 4. (I’ll stop repeating myself by using word “excellent” in the beginning of every post; take it as read.) Here follows my thoughts.

For Fraction, there could be an exercise to make Fraction.add/2 construct the smallest possible fraction, and also to implement the other arithmetic operations.

In 4.2, Working with hierarchical data, it is assumed that CRUD is already known to the reader. It wasn’t known to me. The text sort of explains what it means, but it would be easier to read if it were explained up front.

The part about Protocols raises some questions that don’t seem to be answered. It is said that:

It’s important to notice that the protocol implementation doesn’t need to be part of any module.

  • Is there any practical difference between putting an implementation inside or outside a module?

  • What is best praxis for putting a protocol implementation for your own data structure? Inside the module for the data structure? Outside in the same source file? In a separate file?

Perhaps there should be some exercises for Protocols. For example:

  • Implement the Enumerable protocol for TodoList.
  • Implement the Inspect and String.Chars protocol for Fraction.