Core Elixir: List.delete/2 and List.delete_all/2

Last week, we looked at List.delete_at, which deletes a value at a specific position of a given list.

What if you know the value you want to delete, though, but not the position? Elixir has List.delete/2 for that!

There is one catch, however. If your list contains the value you’re looking to delete in more than one place, it will only delete the first one. The rest will remain.

How does this function work? Why doesn’t it delete more than one value? What kind of daring recursion knows to short circuit itself so strongly?

Beats me. The function is an Erlang wrapper:

  def delete(list, item) do
    :lists.delete(item, list)
  end

Digging into Erlang source code is beyond the scope for Core Elixir.

It does, however, perfectly follow one of the Elixir language’s goals mentioned at the top of the List library’s documentation:

A decision was taken to delegate most functions to Erlang’s standard library but follow Elixir’s convention of receiving the target (in this case, a list) as the first argument.

Delete Them. ALL of Them.

As a programming exercise, let’s create a List.delete_all/2 that won’t stop after the first example of the value is found.

We’ll try recursion with a dash of pattern matching:

  def delete_all(list, value) do
    delete_all(list, value, []) |> Enum.reverse
  end

  defp delete_all([h|[]], value, end_list) when h === value do
    end_list
  end

  defp delete_all([h|[]], _value, end_list) do
    [h|end_list]
  end

  defp delete_all([h|t], value, end_list) when h === value do
    delete_all(t, value, end_list)
  end

  defp delete_all([h|t], value, end_list) do
    delete_all(t, value, [h|end_list])
  end

You have List.delete_all/2 now that takes a list and the value you’re looking to eliminate from that list. In turn, that calls the private function List.delete_all/3, where all the recursive fun begins. It goes through the list left to right, checking each member of the list to see if it matches the value you’re trying to eliminate. If it’s a match, it skips over it and calls itself with the tail of the list you started with, and without adding any values to the new list you’re constructing.

When you get to the final value, it checks for a match in the guard clause, and then returns the appropriate list.

The end result, naturally, is backwards, so the main function handles the reversing of the list to make it look right again.

Like this:

iex> li = [0,1,0,0,1,0]
iex> List.delete_all(li, 0)
[1, 1]
iex> List.delete_all(li, 1)
[0, 0, 0, 0]

iex> li = [1,2,3,4,5]
iex> List.delete_all(li, 3)
[1, 2, 4, 5]
iex> List.delete_all(li, 5)
[1, 2, 3, 4]
iex> List.delete_all(li, 0)
[1, 2, 3, 4, 5]

Take Two

Then I stopped to think, and remembered that functional programmers love three things:

  • Map performs the same function to every item in a list. (New List: Same Length)
  • Filter eliminates values from a list that don’t match up to a function. (New List: Smaller or Same Length)
  • Reduce takes a list and returns a single value derived from the items in the list. (Accumulator Value)

This is a gross simplification, but it works for me.

When I look at what I coded above, it’s basically a map function. I’m going over a list and applying a function to each item, one by one, then returning a list. But that’s not the best abstraction for this problem.

Map, by its very nature, is meant to apply a function to every item in a list and return that entire list, transformed. My code basically mapped over a list and created a new list. Kinda close, but not really it.

The problem I’m solving for here is an obvious filter. We need to filter out the values from the list that we don’t want:

  def delete_all(list, value) do
    Enum.filter(list, fn(x) -> x !== value end)
  end

These three lines of code do the same thing as the other monstrosity I programmed earlier. It filters the list, returning only the values for which the function is true — that the item in the list isn’t the same as the value we’re looking to get rid of. So it only returns values from the original list that don’t equal the value you’re looking to, well, “filter out.” It’s right there in pretty plain English. And you can save 12 lines of code.

Map. Filter. Reduce.

Such helpful concepts.

Functional programmers love it so much, they even combine them. Elixir’s Enum library also contains map_reduce/3 and filter_map/3. There’s even something called flat_map_reduce/3.

Filter It, Just a Little Bit (More)

But filter isn’t as deep as we could go. Here’s the Enum.filter source code:

def filter(collection, fun) when is_list(collection) do
  for item <- collection, fun.(item), do: item
end

def filter(collection, fun) do
  Enumerable.reduce(collection, {:cont, []}, R.filter(fun))
  |> elem(1) |> :lists.reverse
end

We’re dealing with a list here, so we get to use the much more simple first instance you see there. That’s a list comprehension that acts as a filter. It goes through each item in the collection, runs it through a function to make sure it returns a truthful value, and then does something with it. In this case, it returns the value, un-altered, so long as the function turns out to be true when run against that item.

The second filter version works when your collection it not a list. We’re not going to worry about that one now. We’ll get back to reduce in a little bit, though.

What if we rewrote our delete_all function directly with the list comprehension code that filter uses?

def delete_all_filter(collection, value) when is_list(collection) do
  fun = fn(x) -> x !== value end
  for item <- collection, fun.(item), do: item
end

Double check that it works:

iex> li = [1,2,3,4,3,4,5]
iex> List.delete_all(li, 4)
[1, 2, 3, 3, 5]
iex> List.delete_all(li, 3)
[1, 2, 4, 4, 5]

Yup, that’ll do it.

I doubt this speeds anything up, but it does feel “closer to the metal.”

If you were really feeling adventurous, though, you’d rewrite it as a straight reduce.

Oh, we’ve gone this deep, already. Why not?

The Reduce Road

Joe Kain has a great reduce write-up that I can’t recommend enough. It’s the best explanation of reduce I’ve seen so far, and I used it as inspiration for writing my function here:

  def delete_all(collection, value) do
    Enum.reduce( collection, [], fn(x, acc) ->
      case x !== value do
        true  -> [x | acc]   # Not the value, add it to the list
        false -> acc         # Matches the value, so don't add it
      end
    end)  |> Enum.reverse
  end

Note it still has the same user interface. It’s still a list and a value for the two parameters and that’s it. The reduce function takes care of the rest. The only complication here is the case statement I had to throw in. If there’s a way around it without adding more functions, please drop me a line or an issue/pull request. I’d love to simplify this further, so long as it’s readable.

The way this works is by using basically the same function we used above, but instead of just returning one value at a time, the reduce statement grows its own list as the list is traversed. It does, however, return the list backwards, so we pipe the results to Enum.reverse to straighten it back out. Now the input and the output of this function are identical to the other options discussed above.

In the end, I think the filter solution is the cleanest and clearest to me. It certainly beats my original stab at the problem. Maybe that’s the lesson we learned today? The further down you dig, the more options you have and the better your final function could be. Also, you might learn some stuff.

Sure, I’ll go with that.

If you have any comments, questions, complaints, criticisms, or corrections, catch me on Twitter, @AugieDB. Or make a pull request on Github! That Twitter handle and Github ID is the same as my GMail account, if you want to deal with it more quietly. I want these articles to be factually correct and will update them as necessary.

(12)

Perl on the Rebound?

Is this the beginning of a trend — people from other languages recognizing Perl’s accomplishments? Or just a coincidence?

And, finally, just because I thought it was cool, here’s a Perl/Ruby Quote of the Year:

Core Elixir: List.delete_at/2

The List module includes a bunch of functions that can’t be done with an Enum function, which collections conform to. If you’re having a hard time deciding whether to handle something as a List or Collection, try the Collection first.

But if you have a list and want to delete a single item on it, you’ve come to the right place!

List.delete_at/2

The first thing you need to remember is that Elixir is immutable. The return value from this function will be a new list. You can rebind it to the original list’s name, but it will not be directly affecting the data you started with. In reality, you’re not so much deleting an item out of the list as you are generating a new list that happens to not contain this value.

I do not include this at the top because I made that mistake. Oh, no. I’m a blogger and, as with Wikipedia, an Unimpeachable Voice of Authority/Wisdom. I’m telling you this for a friend. Or something.

Anyway —

delete_at takes two arguments: the list and an index, which is the position of the value you want to delete. This position is zero-based. Again, I remind you of this for a friend.

iex> a = [1,2,3,4,5]
iex> List.delete_at(a, 3)
[1, 2, 3, 5]

Remember what I said before about immutability?

iex> a
[1, 2, 3, 4, 5]

The value of the a list didn’t change at all. You can rebind, though:

iex> a = List.delete_at(a, 3)
[1, 2, 3, 5]
iex> a
[1, 2, 3, 5]

This function will also accept a negative index, which will count from the end of the list:

iex> List.delete_at(a, -2)
[1, 2, 5]

When you’re counting backwards, you count 1-based and not zero-based. Not that I’d accuse you of ever trying something silly like using a -0 index. Oh, no. That’s for me to try for you– er, your friend:

iex> a = [1,2,3,4,5]
iex> List.delete_at(a, -0)
[2, 3, 4, 5]

Remember, the index is 0-based and 0 is the same as -0 and is a valid number. If you try to delete -0, you’re just deleting 0.

I do these things so you don’t have to.

If you give a number that makes no sense, er, is out of bounds, the original list is returned:

iex> a = [1,2,3,4,5]
iex> List.delete_at(a, 27)
[1, 2, 3, 4, 5]

There’s nothing in the 27th (er, 28th) position to delete, so Elixir doesn’t do anything.

When you offer the function a negative number to count backwards with, it’s all a trick. The source code won’t be counting back, it’ll do something else. Let’s look at the source now so we can get that point.

The Source

delete_at is a gatekeeper function, meant to make things a little easier for the programmer. It exists to convert the negative index into a positive value first before passing everything along to do_delete_at, which is a private function that takes two values: the list and the index.

Let’s start with a positive number example, and look more closely at do_delete_at.

It first takes care of the simplest solutions. If the list is empty, it returns an empty list:

  defp do_delete_at([], _index) do
    []
  end

Duh.

If you only want to delete the 0th element (which is the first value, remember, as well as the -0th), return just the tail:

  defp do_delete_at([_|t], 0) do
    t
  end

With those cases out of the way, now we can get to the fun recursive stuff. This one bent my mind a little, until I sketched it out. Here’s a good tip for figuring out recursive functions: Start at the base case and increment your way up. In this case, I started with an index of 0 already: It returns the tail.

Let’s look at the code all together, and then we’ll walk through it with an index of 1:

defp do_delete_at([_|t], 0) do
  t
end 

defp do_delete_at(list, index) when index < 0 do
  list
end

defp do_delete_at([h|t], index) do
  [h | do_delete_at(t, index-1)]
end

The third pattern would match (list, 1), returning the head value followed by the results of putting (tail, 0) through the same function. You pass in 0 (decrementing the index) the second time since the list is now one item shorter at the front.

We’ve already seen what (tail, 0) is going to return — the tail of the list you just passed in. Thus, the head is thrown out.

So, this function returns the head of the original list, tosses out the next value, then returns everything else: You just deleted the second item on a list, effectively.

Once you have that in your mind, the rest of them are easy. 2, 3, 4, etc. just get to the point where you cut off the head and return the tail plus all the previous heads.

As a nifty bonus, you don’t need to reverse the list at the end. The process maintains the order as it goes along.

Counting Backwards

A negative index does something interesting. There’s no code for explicitly counting backwards. do_delete instead calculates the forward-counting position in the list to delete. It adds the length of the list (hello, List.length/1) to the negative number you provided and deletes that item.

For example: If your list is the numbers 1 through 5 and you want to delete the -2nd item in the list (4), do_delete will add list length 5 to -2 and delete the element at position 3, which is the fourth item on the list.

Voila!

That was “simple.”

Wait, What About That Middle Function?

Update: 8/23/2015 Answering a Reddit question here, which I skipped over in the initial explanation.

What, this one in List.do_delete_at?

defp do_delete_at(list, index) when index < 0 do
  list
end

That’s there in case the negative value the user submits has an absolute value greater than the length of the list. if the user does something silly like that, Elixir will just pass back the original list, much the same way it does when the user provides a positive index greater than the length of the list.

iex> a = [1,2,3,4,5]
iex> List.delete_at(a, -100)
[1, 2, 3, 4, 5]

The gateway function (List.delete_at/2) doesn’t test for a negative value’s actual value. That happens inside the private functions’ (List.do_delete_at/2) pattern matching. This is what the gateway looks like:

  def delete_at(list, index) do
    if index < 0 do
      do_delete_at(list, length(list) + index)
    else
      do_delete_at(list, index)
    end
  end

You’ll see that while it checks to see if the index value being passed in is negative, it doesn’t check to make sure that it isn’t TOO negative. That happens in the next step.

Coming Up Next Week

We’ll take a look at a sister function of List.delete_at, how it differs, how we might improve it, create a new function, refactor it two ways, and then take a long nap.

If you have any comments, questions, complaints, criticisms, or corrections, catch me on Twitter, @AugieDB. Or make a pull request on Github! That Twitter handle and Github ID is the same as my GMail account, if you want to deal with it more quietly. I want these articles to be factually correct and will update them as necessary.

(11)

Core Elixir: Grab Bag 1

In the course of researching “Core Elixir” articles, I come across all sorts of little points of interest that don’t always fit into the article, no matter how far I stretch them. Those wind up in a little scrap file. That scrap file becomes the Grab Bag.

Self-Evident Git Commit

They say your Git commits should be descriptive.

This commit added backticks to surround true and false 64 times across 30 files. The git log on it reads, “add backticks to a massive amount of true, false and nil.”

“Massive” is a good word.

Alias Assumption

When you use an Alias in a module, you don’t have to specify what to Alias it as — if you just want to use the last section of the module’s name.

For example, if you have a module named Long.Module.Name, the default alias will be ‘Name’. These two commands do the same thing:

alias Long.Module.Name, as: Name
alias Long.Module.Name

I’m still wrapping my brain around writing modules with multiple levels like that, so this is a neat trick for me.

Renaming Maps

Elixir works hard to keep the arguments of its function calls consistent, for the sake of the pipeline operator. But it doesn’t stop there.

The Map library has a couple of functions that wrap an Erlang function directly, re-ordering the arguments for the pipeline operator’s sake, but also to rename the function for readability. For example:

def has_key?(map, key), do: :maps.is_key(key, map)

It’s not enough to simply re-order the arguments there. The function name, itself, is changed to make it more readable and consistent with the rest of Elixir’s syntax. Read it out loud and I think you’ll agree that, in that order, the “has_key” name works better. ” ‘Map’ has key ‘key’ ” reads better than however-you’d-read-the-Erlang equivalent. (“‘Maps’ ‘is a key’ named ‘key’ in ‘map'”?!?) It’s obvious what it does, but it doesn’t read as cleanly.

Since you’ll likely be including the module name with a call to this function, there’s even a certain rhythm there:

Map.has_key?(map, key)

It alternates between Map and Key.

And, because Elixir allows for it (as in Ruby), the question mark in the function name indicates that the return value will be a boolean. That’s not enforced by the language. It’s in the source code’s specs, but that’s only useful for documentation and tools like Dialyzer. It’s a stylistic thing that you’re encouraged to use.

If you do declare a function that ends in a question mark that returns an integer, prepare yourself for a pull request to fix that…

Erlang is Not For the Faint of Heart

The Supervisor shutdown variable is a bloodthirsty one. You can choose between three values when shutting down a Supervisor.

First, there is :infinity. This is the most forgiving option, leaving the child process that is another Supervisor all the time it needs to stop everything below it first before the Supervisor dies off.

Second, there is a number, which represents the time it’ll wait for the child process to shutdown. If it doesn’t get confirmation back from the child that it shut itself down in x number of milliseconds, the Supervisor takes out the child hard:

  Process.exit(child, :kill)

In the words of Elixir’s documentation, “the child process is unconditionally terminated”. Gruesome!

There aren’t too many languages out there that give you the chance to kill your children like this.

But, wait, there’s more! There’s a third option:

  Process.exit(child, :brutal_kill)

Elixir is starting to sound like either an 80s action movie or a 90s fighting video game. That option goes straight to killing the child without spending any time waiting. Just — boom! The child is out of its misery without warning.

Some might call that more humane.

Housekeeping: Updating Core Elixir

I made an update to the Collection To List installment that added a new section, cleaned up some confusing code, and just generally rambled on.

I also [cleaned a couple of minor things up on last week’s System.tmp_dir post.

Further back, I made an update to a January 2014 post about random number generation to reflect a recent deprecation in Erlang.

But the big news is, this blog is now on Github! I’ve taken the original Markdown files for every post and created a Github project for them. If you see any problems with past, present, or future posts here, make a pull request. (Spelling errors, coding errors, what have you.) If there’s a part of Elixir you’d like to see me tackle in this series, raise an issue.

If you have any comments, questions, complaints, criticisms, or corrections, catch me on Twitter, @AugieDB. Or make a pull request on Github! That Twitter handle and Github ID is the same as my GMail account, if you want to deal with it more quietly. I want these articles to be factually correct and will update them as necessary.

(10)

Proud Papa

I created the little purple guy based on the Elixir logo. I call him “Drop.”

And now he’s been remade.

I’ve never seen anything I’ve created changed into something else like this before. It’s humbling and awesome and ego boosting all at the same time.

I love it. Thanks, Misael!

Core Elixir: System.tmp_dir/0

In which a Hex module is suggested, a pull request is considered, Erlang is explored, and a big warning kicks things off.

Sometimes, in the course of handling files through automation in any programming language, you’ll have need of a temporary directory to stash things away in. Elixir has something to help you find that directory: System.tmp_dir/0

The command returns the name of the most suitable temp directory for you that is writable.

But the devil’s in the details and it’s very important to set expectations properly here.

A BIG GIANT HUGE WORD OF WARNING

Seriously. Heed me.

System.tmp_dir only finds a directory for you. It does not create one special to this program. It will not guarantee for you that the directory is empty to begin with.

It will merely give you a location on the file system where files can be written. If you’re lucky, there’s an environment variable set to guide you to a good place. If you’re not lucky, it’ll return nil and you won’t have anywhere to go. If you’re supremely not lucky and are having a very bad terribly no good day, you’ll get the current working directory returned to you.

You see, my precious Perl has a module like this called File::TempDir. (It’s based on File::Temp, naturally.) It creates a new and randomly-named temporary directory for you and then destroys it and everything in it once it goes out of scope. It’s a truly temporary place that’s also self-cleaning. Best of both worlds.

Elixir’s System.tmp_dir will only help you find a directory that’s writable. Period. It won’t guarantee that other files won’t be there. It won’t guarantee that what files you write to it will ever go away. It won’t destroy the temporary directory once it’s gone. That’s still all on you. So keep track of things.

You may consider this a warning, or you may consider this a golden opportunity to create a new application to put up on Hex for one and all. Take your pick.

With that out of the way…

Let’s Look at the Code

System.tmp_dir takes no arguments and returns one string value: the location of the temporary directory you can write to.

  def tmp_dir do
    write_env_tmp_dir('TMPDIR') ||
      write_env_tmp_dir('TEMP') ||
      write_env_tmp_dir('TMP')  ||
      write_tmp_dir('/tmp')     ||
      ((cwd = cwd()) && write_tmp_dir(cwd))
  end

This one is pretty simple. The entire thing is a short-circuiting OR (||) statement. The first chance Elixir gets to find something that will work, it’ll exit out with that directory’s location.

This function breaks up into three pieces:

  • First, it checks for environment variables. (lines 2-4)
  • Second, it’ll check on a /tmp directory. (line 5)
  • Third, if all else fails, it’ll go with the current directory. (line 6)

Let’s drill down now and get our hands dirty at even deeper code:

Looking Up the Directories

The three checks on the environment variables all use the function write_env_tmp_dir/1, taking as an argument the environment variable to check.

Let’s see how it works:

defp write_env_tmp_dir(env) do
  case :os.getenv(env) do
    false -> nil
    tmp   -> write_tmp_dir(tmp)
  end
end

This uses the case statement, which is an Elixir macro short for “We need an IF statement but we’re functional programmers and we don’t use if statements, so cover it with a macro.”

It farms the work out to Erlang, using its :os.getenv function to look for the system variable provided to it.

That’s an interesting Erlang function by itself. So let’s take a look at it. One more level down, we go:

:os.getenv/0 – :os.getenv/2

It comes in three flavors, with arities of 0, 1, and 2. The 0 arity function call returns all available environment variables. Here’s a truncated look at what that result is on my humble little Vagrant box:

iex> :os.getenv
['LC_PAPER="en_US.utf8"', 'SSH_CONNECTION=192.168.33.1 51655 192.168.33.10 22',
 'PWD=/home/vagrant/augiedb/elixir', 'LC_ALL=en_US.utf8',
 'LC_IDENTIFICATION="en_US.utf8"', 'LC_MEASUREMENT="en_US.utf8"',
 'LESSCLOSE=/usr/bin/lesspipe %s %s', 'LC_NAME="en_US.utf8"', 'SHELL=/bin/bash',
 'ROOTDIR=/usr/local/lib/erlang', 'LC_MESSAGES="en_US.utf8"',
 'PATH=/usr/local/lib/erlang/erts-7.0/bin:/usr/local/lib/erlang/bin:/home/vagrant/.rakudobrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/vagrant_ruby/bin',
 'LC_COLLATE="en_US.utf8"', 'TERM=xterm', '_=/usr/local/bin/iex',
 'LOGNAME=vagrant', 'BINDIR=/usr/local/lib/erlang/erts-7.0/bin',
 'MAIL=/var/mail/vagrant', 'LESSOPEN=| /usr/bin/lesspipe %s']

If you call it and pass along the environment variable (the /1 arity), it’ll return the value:

iex> :os.getenv('PWD')
'/home/vagrant/augiedb/elixir'

Finally, you can pass both a key AND a value into the function. If Erlang finds that environment variable, it returns its value. If that variable doesn’t exist, it returns the value you passed in. Do note, though, that the environment variable won’t be created or modified.

iex> :os.getenv('HELLO')  # Does not exist.
false
iex> :os.getenv('HELLO', 'WORLD') # Provide a default.
'WORLD'
iex> :os.getenv('HELLO')  # See?  It still does not exist.
false

Where Were We? Oh, Yes…

defp write_env_tmp_dir(env) do
    case :os.getenv(env) do
      false -> nil
      tmp   -> write_tmp_dir(tmp)
    end
  end

We’re past the case statement now. You’re getting ‘false’ in response to the :os.getenv/1 command if the environment variable doesn’t exist. As a result, this function will return a nil back, which will force the next statement after the || (or) in the tmp_dir function above.

If there is a value for that key, you get that value back, which the code pattern matches with ‘tmp’ and sends as the argument to the next function down, write_tmp_dir. We have a directory, but now we’ll check that we can use it!

write_tmp_dir/1

This is the end of the road for our exploration. It’s as deep as we’re going to get. Honest. This is where our final answer will be found and returned.

write_tmp_dir begins, amusingly, with our old friend File.stat! This time, it’s looking for the properties of that temporary directory we’re working on providing the coder, who’s back about six steps now and doesn’t realize it since things run so quickly on modern computer chips.

defp write_tmp_dir(dir) do
  case File.stat(dir) do
    {:ok, stat} ->
      case {stat.type, stat.access} do
        {:directory, access} when access in [:read_write, :write] ->
          IO.chardata_to_string(dir)
        _ ->
          nil
      end
    {:error, _} -> nil
  end
end

Do if statements make you feel dirty? How do nested case statements work for you? Maybe I need to make a pull request to help flatten this out:

defp write_tmp_dir(dir) do
  case File.stat(dir) do
    {:ok, stat} -> return_tmp_dir(stat, dir)
    {:error, _} -> nil
  end
end

defp return_tmp_dir(stat, dir) do
 case {stat.type, stat.access} do
  {:directory, access} when access in [:read_write, :write] ->
    IO.chardata_to_string(dir)
  _ ->
    nil
 end
end

That looks so much better to me.

return_tmp_dir is probably not the best name, but naming is hard and that isn’t the point of this write-up.

I probably won’t submit this as a pull request, though. Quoting the Elixir Contributions docs:

NOTE: Do not send code style changes as pull requests like changing the indentation of some particular code snippet or how a function is called. Those will not be accepted as they pollute the repository history with non functional changes and are often based on personal preferences.

I’m in a gray area here with this one. This isn’t a high holy war of camelCase versus snaked_names or parenthesis vs. no parenthesis, but it also doesn’t add any functionality. It looks prettier, I think, but I don’t want to be a polluter. 😉

As much as I loved Garrett Smith’s talk, that doesn’t mean I should apply his techniques everywhere I see Erlang/Elixir code. In my projects, sure…

I’ve also looked at other Elixir source code and found a mixed bag of nested case statements. A couple do, indeed, call out to another private function, but most just nest the code right there without that extra level of redirection. (File.do_cp_r actually goes three levels deep!)

In any case, in plain English, the function grabs the stats on the directory. If it can’t for some reason (the directory doesn’t exist, for example), it won’t match with {:ok, stat} and will instead return a nil. Game over. (Or, at least, on to the next check…)

If there are stats to be had, we go deeper. Let’s pull those lines out specifically:

  {:ok, stat} ->
    case {stat.type, stat.access} do
      {:directory, access} when access in [:read_write, :write] ->
        IO.chardata_to_string(dir)
      _ ->
        nil
    end

Here’s a sample File.stat result

iex(1)> File.stat('ia')                                                              
{:ok,                                                                                
  %File.Stat{access: :read_write, atime: { {2015, 7, 24}, {22, 35, 36} },
  ctime: { {2015, 7, 24}, {22, 35, 36} }, gid: 1000, inode: 112, links: 1,             
  major_device: 22, minor_device: 0, mode: 16895,                                     
  mtime: { {2015, 7, 24}, {22, 35, 36} }, size: 0, type: :directory, uid: 1000} }   

The code looks specifically for the type and access keys, pattern matching them in the next line to :directory and access. In other words, you better be a directory and not, say, a file. Secondly, there’s a guard clause on line 3 to govern what type of access you need to have to that directory to use it. It needs to be either read/write or write only. That makes sense to have for a directory you want to write files into temporarily, don’t you think?

If that all holds up, we get to our end position, which is that the function returns the writeable temporary directory name, properly stringified for peak Elixir usage.

Is That All?

No.

Don’t be silly.

We haven’t discussed what happens when the case statement back at the top leads to the /tmp directory. You didn’t forget about that while I was dragging your attention all around the world, did you?

Can’t say as I blame you…

In Case of /tmp

It’s been awhile, so let’s go back to the very beginning:

  def tmp_dir do
    write_env_tmp_dir('TMPDIR') ||
      write_env_tmp_dir('TEMP') ||
      write_env_tmp_dir('TMP')  ||
      write_tmp_dir('/tmp')     ||    ## You are here
      ((cwd = cwd()) && write_tmp_dir(cwd))
  end

We’re up to line 5, where no environment variables have been set and so we look for the /tmp directory as a possible answer. With this option, we can skip over the checks for environment variable status that we had with write_env_tmp_dir and go straight to checking on the /tmp directory at write_tmp_dir/1, a function we’ve already discussed.

It’ll tell us if the directory exists, is a directory, and is writable. Or not.

If not, we arrive at the final option.

Think Globally, Write Locally

I call it the “nuclear option” for reasons outlined at the very top. This is the default answer when all else fails: The current working directory.

Instead of calling out to another function, there’s a little block of code with two commands joined by an AND (&&) to run the second half assuming the first half returns a value. (Erlang’s documentation does bring up a circumstance under which this would fail — if the directory’s permissions prevent it.) First, it sets the cwd variable up with the current working directory and, once that has a value, it goes back to the write_tmp_dir well to prove that it is a valid and writeable directory.

I’m not so sure why they didn’t just combine it into one command like so:

 (write_tmp_dir( cwd() ) )

I tested it. It works. Maybe it’s less readable that way? Is it a violation of Elixir style guides in that you’re reading it inside out instead of left to right? For one level of depth, I don’t think it’s a bad call. Plus, eliminated a temporary variables is always a pleasant thing.

What about the more Elixir-ish pipeline option:

  cwd() |> &write_tmp_dir/1

That looks like a jumble of punctuation, though. I’m still not convinced the current line is the prettiest version of the code, but I’m really nit-picking at this point. I definitely won’t do a pull request here.

One Last Note

There, is of course, a version of the function with the added bang to return a more verbose error in case of failure. I present to you System.tmp_dir!/0:

  def tmp_dir! do
    tmp_dir ||
      raise RuntimeError, message: "could not get a writable temporary directory, " <>
                                   "please set the TMPDIR environment variable"
  end

As you can see, it just calls on the bang-less tmp_dir function we’ve already exhaustively covered and, should it fail and receive a nil, it executes the second half of the OR (||) statement, raising an error complete with a suggestion for how to fix the problem. After all, the problem isn’t the code; it’s the file system. Set your ENV and be a better UNIX person.

At Long Last, It’s Summary Time!

There you have it — the fancy pants Elixir way to find a temporary directory. Use with care. Track the files you’re playing with. Be a nice citizen and clean up after yourself.

And always eat your vegetables.

If you have any comments, questions, complaints, criticisms, or corrections, catch me on Twitter, @AugieDB. Or make a pull request on Github! That Twitter handle and Github ID is the same as my GMail account, if you want to deal with it more quietly. I want these articles to be factually correct and will update them as necessary.

(9)

Updated August 11, 2015: Whops, got the arity slash in the wrong direction in one spot. Also added clarification to the “AND (&&) to guarantee both halves will run” bit. Thanks to Henrik for the spots!

Near Miss

In my third year of college, I took a Programming Languages course that pretty much defined my career.

The class was broken up into groups of four or five people, and each group had to pick a different programming language to study. (Presumably, we reported on that language back to the rest of the class. I don’t remember, but it’s probably what happened…)

I joined a group that would study Perl. This was 1996/1997. Perl would have been fairly popular at that point, being the tool the web ran on after basic HTML.

I fell in love with Perl. Of course, having previously studied languages like Scheme and C and Pascal, of course Perl would look and sound beautiful. It was powerful and easy to read, with lots of creative opportunities for a programmer.

For Christmas that year, I got the Perl Black Book and a CD with a Linux distribution on it. (I think it was Slackware, the world’s worst version of Linux to attempt to put on a computer for a newbie.) Even after the class was over, I could play with Perl on my own.

Years later, I found an opportunity to insert Perl into some client work I was doing and it quickly became my thing at the company.

Looking back, I don’t remember much else about that programming course, aside from the professor being a temporary prof who put all her notes on one long HTML slide that she’d share with the class… eventually. But I do remember that one of the other languages I could have chosen to study that semester had a ridiculous sounding name.

SmallTalk.

Part of me looks back now and wonders if I had chosen a different group in that class back in 1996/1997, would I have glommed onto Ruby earlier and been a Rails programmer, instead?

Missed opportunity?