Core Elixir: List Module Misc.

Core Elixir looks into the List module and finds Erlang wrappers, test-driven development, and the joy of tuples.

We’ll be dipping a toe into the List module waters today…1

Elixir Drop dips his toe into the water

In researching modules and functions for Core Elixir, I often come across dead ends. These are the functions that don’t go very deep and that aren’t very interesting for a Core Elixir article.

The #1 cause of this is Erlang functions that are called directly by Elixir just to switch the parameters around so the list or collection comes first. Thanks, Pipeline Operator.

For example:

List.duplicate/2 takes a list and a non negative integer that describes how many times the caller wants to repeat that list. So, to steal the example straight out of the documentation:


iex> List.duplicate([1,2], 2)
[[1,2], [1,2]]

You know how that works? List.duplicate(list, multiplier) calls Erlang’s :lists.duplicate(multiplier, list).

For Core Elixir purposes, that’s a dead end.

That seems simple, but Elixir goes one step simpler:

List.flatten/1 directly calls :lists.flatten/1. It doesn’t even need to swap the order of the parameters around, since there’s only one.

But, wait, you say, there’s another version of List.flatten that has an arity of 2! The second parameter is a value that gets slapped on at the end of the newly flattened list! Does that extra parameter make any difference?

No. List.flatten(list, tail) calls :lists.flatten(list, tail).

The list still comes first. Erlang is inconsistent compared to Elixir in this way.

Purely Functional Looks Test Driven

I like this bit of code, to define List.last, the biggest tongue twister in Elixir’s code base:


  def last([]),    do: nil
  def last([h]),   do: h
  def last([_|t]), do: last(t)

It feels like the way you’d write something when you’re test driving it, doesn’t it? Let’s work through it:

Make it work for the simplest possible case — an empty list. In that case, there’s no making heads or tails of it (ha!), so you return a nil.

OK, so what happens when there’s only one item in the list? The answer isn’t nil anymore. You need to write that case next. The single value is the head AND the last item in the list.

And when you have two or more items? You look for the last value in the list by going through the whole list until you get to the end, recursively. Keep stripping off the head from the list and look for the last of the rest.

As a bonus, the second case now makes more sense. Why is the single value the last value? Because if you started with two or more values, this is the base case. A list with one value would effectively be the most tail item in the list. Pretty nifty.

It’s logic like that that makes me like functional programming. It just feels smart, somehow.

Of Tuples and Keyfinds

List.keyfind/3 strikes me as funny, mostly because it isn’t what I thought it was at first. And then I felt stupid for missing it. Let me assume for the moment that we’re all being honest with ourselves and that all of our minds blank at something ridiculously basic every once in a while. Thanks.

`List.keyfind/3′ calls on the similarly named :lists.keyfind/3, like so:


  def keyfind(list, key, position, default \\ nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

The function takes a list of tuples ( [a: 1, b: 2, c: 3] ), a value that it’s looking for, and which position in each tuple it’s looking for that value.

Here’s the trick to remember: A tuple can have not just two values, but also more or less than 2 values. A tuple is just an ordered list of things. Curly braces surround the list, and the values inside are separated by commas.

Valid tuples:
* {1}
* {1,2}
* {1,2,3}
* {1,2,3,4}

Et cetera. Well, you probably want to use a map after that, instead, but it’s your program. Do what you want.

When you have a two element tuple, it’s not a key and a value. It’s just two values.

I had forgotten that in a list with keywords, the curly braces can be dropped.


iex> t1 = [a: 1, b: 2]
[a: 1, b: 2]

iex> List.first(t1)
{:a, 1}

See? That’s a list with two tuples. Each is kinda sorta key and value, but not really. It’s position 0 and position 1 in a two element tuple. [a: 1, b: 2] is the same thing as [{:a, 1}, {:b, 1}]. It just looks a lot cleaner, so long as you remember that little trick. I’m a little out of practice using it, I guess.

In List.keyfind, the position parameter refers to which value you’re looking at in each tuple, the zeroth or the first, in these cases. For what I think of as the key, I’d always use 0. For what is traditionally the value, it’s a 1.

If I wanted to make a ridiculous pull request to the Elixir core language, I’d offer up positions of :key and :value that are equal to 0 and 1 in two element tuples. (Pro tip: Don’t do it. It’s a silly idea.)

Finally, the last parameter sent to List.keyfind is the default value to return if nothing is found. “Nil” is the default default.

There’s one other gotcha to point out here in this code. Erlang likes to be a 1-based language, while Elixir likes the 0-based values. List.keyfind calls :lists.keyfind, moving the list to the front and adding 1 to the position value to make up for that off-by-one position.

You may all quibble amongst yourself over whether everything should start with 0 or 1. I’ll be over here indenting my code with the tab key just to annoy you.

I’m just kidding. I would never do that. I program in a non fixed width font, so indentations never looks right no matter which keys I use.

Sorry, I’m kidding again. I don’t use a non fixed width font. That’s madness! But this switch to a Dvorak keyboards means d;jdsfo dsmowm ojf of s;ljfy. !

(17)

  1. This might just be the best Elixir Drop drawing I’ve done yet. Just sayin’