Elixir Function Surprises for a Rubyist
Tags: Ruby, Learning To Code, Programming, Elixir, Coding

#TheSurprisedRubyist is back with more from the I’m learning elixir in 2021 series🎉

Coming from Ruby, the way elixir methods work feels excitedly different at a fundamental level.

Before we dive into that can I just briefly touch on macros and say that wow: def and defmodule are macros, not keywords…! Is this turtles all the way down? Am I a macro? 🤔 Thanks, back to functions.

Standing on the shoulders of higher arity functions

Method overloading is completely foreign to me:

def add(a) do

add(a, 0)

end def add(a, b) do

a + b

end

It’s not just the same function names being repeated, it’s that lower arity functions lean on higher arity functions (arity is the number of arguments a method accepts).

Which seems like elixir encourages you to make smaller methods that are reusable from the outset, sounds like a win to me!

I’ve hardly written any elixir yet, so I haven’t delved into complex scenarios where I can compare them to Ruby, but these building blocks seem exciting, even just to have the chance to think about programming in a different way.

Let me know if programming in Elixir changed the way you program in other languages?

Default Values Do What Now?

For starters, the default argument syntax looks quite unusual and stand-out-ish, which is good I suppose:

defmodule Functions

def add(a, b \\ 0) do

a + b

end

end

Can you ask etymology (history of a word) for programming symbols? I’m curious if there is some historical reason why it’s made up of two backslashes.

The real surprise is finding out that this is actually just shorthand for generating a lower arity function 😮

This uses the ‘rule’ of lower arity functions utilizing higher arity functions and is expanded into:

defmodule Functions

def add(a) do

add(a, 0)

end def add(a, b) do

a + b

end

end

I’m not really going to think of them as default arguments, but as function generators :D

That may sound silly but I think it will help remind me.

Methods go *Splat in Ruby

So, what about splatting? In ruby a function can accept a collection of unknown arguments using the splat ( * ) operator:

# # # RUBY # # #

def add(*numbers)

numbers.sum

end > add(1, 2, 3, 4) # => 10

There is also the double splat (`**`) which denotes a hash of arguments, defaulting to an empty hash.

In elixir you can’t pass in an unknown collection of arguments because the arity is part of the methods name!

So, what is the elixir way of handling this? Correct me if I’m wrong, but by looking at the official docs it’s gunna be using a couple of methods with different arities and pulling out the recursion card:

defmodule Math do

def sum_list([head | tail], accumulator) do

sum_list(tail, head + accumulator)

end



def sum_list([], accumulator) do

accumulator

end

end



> IO.puts Math.sum_list([1, 2, 3], 0) #=> 6

Passing in the starting value of the accumulator doesn’t quite get us aligned with the Ruby version of our add , so we can just add another small method that uses the head of the list as the starting number:

def sum_list([head | tail]) do

sum_list(tail, head)

end > IO.puts Math.sum_list([1, 2, 3]) #=> 6

All these methods end up just taking a list and then just add the head (the first element) up one at a time storing it in the accumulator until there is not more elements left to add up — pretty much performing a reduce but without calling a reduce method… 🤔

Reductionism

Looking up docs there IS a reduce function on the Enum module in elixir:

> Enum.reduce([1,2,3], fn x, acc -> acc + x end)

# => 6

I LOVE the explicitness of knowing exactly where and what it is that is performing the work, but this is quite unnatural to me.

I’m soooo used to calling methods on the data objects…. like in ruby land:

# # # Ruby # # #

def add(*numbers)

numbers.reduce(0) { |number, accumulator| accumulator + number }

end > add(1,3,4) # => 8

Whereas having the module right there for you to look up, make everything much clearer!

Not for reduce but in ruby I would often use reflection to double check how something is implemented with an_object.methods(:my_method).source.display and check where it’s defined an_object.methods(:my_method).source_location just to sanity check methods, which now seems like a ton of effort for this magic!!!

Ruby’s strength is that it can make reading software close to reading in English, however, it does this by hiding away logic in other places where you’ll need to peek at to understand HOW something is implemented, with the top level English only explaining WHY it’s been implemented (hopefully).

My extremely newbish view on Elixir is that it keeps things obvious and explicit.

So, when would you lean on Enum.reduce and when would you use recursion with method overloading? Guess I’ll need to read more elixir code to find out, but let me know if you have thoughts on this!

I’m slightly worried my Ruby ways are going to make some horrible Elixir code to start off with!!! Can this be avoided or is it just the natural course to be ridden out? 🙈

Is there documentation for functions generated from default arguments?

My new found favourite toy in iex : the h method 😂

With the functions generated by default values I wonder what would happen with documentation for those, would it say there isn’t any or pass that on up to the parent arity method?

To test this out I went on a little adventure. Originally I didn’t have a mix project and just a file called functions.ex that looked like this:





Adds two integers together

"""

def something(a, b \\ 0) do

a + b

end

end defmodule Mymodule do @doc """Adds two integers together"""def something(a, b \\ 0) doa + bendend

So in an iex session it wouldn’t show me the money(!) — I mean docs:

$ iex functions.ex

> h(Mymodule.something/2

No documentation for Mymodule.something/2 was found

Apparently this file needs to be compiled before the h method can read it’s documentation, which there is a c method for storing that in memory:

$ iex functions.ex

> c("functions.ex", ".")

> h(Mymodule.something/2 def something(a, b \\ 0) Adds two integers together

BUT really I should have followed the official guide and created a mix project to play around in, then the h works straight out the gates 🎉

$ mix new functions

$ cd functions

$ iex -S mix

> h(Functions.something/2 def something(a, b \\ 0) Adds two integers together

Remember, curiosity killed the programmer trying to run ahead or something like that. Always mix your elixir!

To answer the original question:

What documentation will be shown for a generated function (from a default argument)?

> h(Mymodule.something/1 def something(a, b \\ 0) Adds two integers together

It shows the same docs 🎉 Which does make sense now I think more on it, because the documentation itself is on the method definition with the default argument, so it should be associated, and default examples can be included in the doctests.

This has been FUN!

Not having splat arguments and working with overloading functions has got me pretty hyped to write some elixir code! This is SO different from Ruby or JS that I’m used to writing!

Thanks for following along and helping out #TheSurprisedRubyist (me!) with your feedback and answers, I’m starting to see how amazing this community is :)

If you fancy continuing to be part of this adventure with me you can:

Follow on medium 💪

Talk to me on twitter 📝

Have a great day!

TTFN