How do bodyless functions work?

623 views
Skip to first unread message

Chris Keele

unread,
Aug 22, 2013, 7:56:02 PM8/22/13
to elixir-l...@googlegroups.com
Reading through the source, I've noticed a common pattern of bodyless functions with default parameters. I have a few questions about them:

1) How does this mechanic work behind the scenes? I assume the function just gets recalled with the defaults filled in?

2) If the source is any indication, it's good form/a convention to declare method signatures with default parameters separately from their non-default signatures. Is there any difference between doing this and in-lining the default parameters in a single signature?

3) If so, what?

4) If not, what motivates the convention? (Easier refactoring somehow, smaller signatures before blocks of code being easier to read, etc)

5) Can bodyless functions be defined without default parameters in some useful way? (I can define them in console, but they throw an exception on invocation.)

6) If so, how would one use them?

Function bodies are required on a parser level in the languages I come from, so it never occurred to me that such constructs could exist, let alone be useful. Even understanding that the Elixir parser doesn't require them (since they're just keyword arguments to a macro), it never would have occurred to me to even try playing with them if I hadn't been reading the source. I'd like to fully understand this mechanic.

The more I use Elixir, the more it changes the way I think about languages. This might just be a weakness in my background, but by merit of it being an approachable functional language alone, learning it has been the most enjoyable and intriguing experience of my career. At the very least, it's changing the way I think about mapping a problem domain to a suitable tool on the language level, and that ain't bad.

José Valim

unread,
Aug 23, 2013, 3:58:03 AM8/23/13
to elixir-l...@googlegroups.com
1) How does this mechanic work behind the scenes? I assume the function just gets recalled with the defaults filled in?

Yeah. 
 
2) If the source is any indication, it's good form/a convention to declare method signatures with default parameters separately from their non-default signatures. Is there any difference between doing this and in-lining the default parameters in a single signature?

Yes, it is a good convention. Elixir will even warn if you have different clauses and some of the contain a default value, like:

    def foo(bar, counter // 0) when is_integer(bar) do ...
    def foo(bar, counter) do ...
 
In those cases, extracting to a bodyless clause is required. Otherwise, there is no difference at all in between the bodyless and having it inlined.
 
5) Can bodyless functions be defined without default parameters in some useful way? (I can define them in console, but they throw an exception on invocation.)

It is also useful for documentation. Sometimes, all of your clauses, are pattern matching on something:

    def foo([h|t], 0) do ...
    def foo([h|t], 1) do ...
    def foo([], 0) do ...

In such cases, defining a "def foo(bits, counter)" at the top helps to document what the arguments really are.

There is another situation a bodyless clause is useful. In the cases where some of the functions were defined via a macro, you can use the bodyless clause to refer to that particular function and add docs to it:

    use SomeModuleThatDefinesFunctions

    @doc "my docs for this"
    def autogenerated_function(foo, bar, baz)

 
6) If so, how would one use them?

You can't use them. Elixir master will emit a warning or a failure if you do something with a bodyless clause like define docs. 
 
Even understanding that the Elixir parser doesn't require them (since they're just keyword arguments to a macro), it never would have occurred to me to even try playing with them if I hadn't been reading the source. I'd like to fully understand this mechanic.

:) 
 
The more I use Elixir, the more it changes the way I think about languages. This might just be a weakness in my background, but by merit of it being an approachable functional language alone, learning it has been the most enjoyable and intriguing experience of my career. At the very least, it's changing the way I think about mapping a problem domain to a suitable tool on the language level, and that ain't bad.

This is very nice to hear Chris. Elixir by design provides a small language core and then builds on top of it. That's why "def" and friends are just macros. The fact you realized how those same tools can be used to map to your own domain makes me really glad, since it is one of our goals.

Steve Downey

unread,
Aug 28, 2013, 11:32:11 AM8/28/13
to elixir-l...@googlegroups.com
I've read José's response but I'm still not getting the purpose of a bodyless function.

Can someone else take a stab at explaining what is going on?

Alexei Sholik

unread,
Aug 28, 2013, 11:55:15 AM8/28/13
to elixir-l...@googlegroups.com
One use case if to define a default argument that is common for multiple clauses. I.e. what used to be

   def foo(:clause1, arg // default) do
      ...
   end

   def foo(:clause2, arg // default) do
     ...
   end

can now be written as

   def foo(arg1name, arg2name // default) do

   def foo(:clause1, arg) do
      ...
   end

   def foo(:clause2, arg) do
     ...
   end

---

Another use case is attaching docs to generated functions. José has mentioned this. Here's a concrete example: https://gist.github.com/alco/6367640

---

Some builtin functions have no body because their body is written in Erlang as part of the compiler (see "defmacro case" in Kernel, for instance). This is not useful for user code, merely an implementation detail.


--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



--
Best regards
Alexei Sholik

Alexei Sholik

unread,
Aug 28, 2013, 11:56:02 AM8/28/13
to elixir-l...@googlegroups.com
Sorry, replace this code above

def foo(arg1name, arg2name // default) do

with this

def foo(arg1name, arg2name // default)

Chris Keele

unread,
Aug 28, 2013, 12:34:17 PM8/28/13
to elixir-l...@googlegroups.com
One analogy: it's like a C header for a function, but for programmers instead of compilers.

Elixir allows for a lot of variation in method signatures. It's a really cool feature that can clean up a lot of conditional branching inside functions. There are three dimensions: a function can be defined for different aritys (number of parameters), it can have default values for certain parameters, and it can have guard clauses against those parameters.

In situations where default parameters can make for ambiguity, say when you have multiple same-arity clause definitions, some with default values, you can define the default values in a bodyless function separately to remove ambiguity. By defining it first, function calls that match will get 'populated' with those default values and then continue looking for a function definition with a matching arity and guards.

More commonly, defining a bodyless function first gives you place to document it. Since the same function can have multiple definitions, and it only makes sense to use the @doc hook on one of them, people will create a well-documented bodyless function before a large group of multiple definitions, and detail all the different ways to use the function therein.
Reply all
Reply to author
Forward
0 new messages