A few questions

236 views
Skip to first unread message

alco

unread,
Mar 23, 2012, 1:03:07 PM3/23/12
to elixir-lang-core
Hi!

I've just finished reading the guide. It is very nicely written and
provides a great start, but it raises a couple of questions I have not
been able to find answers to.

1. What's the difference between throw and raise? As far as I can
tell, they're orthogonal, meaning that I can only catch a thrown value
and rescue a raised error. What's the intention of having these two
separate things that are somewhat similar?

2. Why do we need to use a dot after a variable name when the variable
is a function? A related question is how can I get the value of a
built-in function?

For instance, in Python everything is an object and variables are
simply "tags" that point to objects. So, if I type the name of a built-
in function in Python's REPL, the function object's representation is
printed to the console. I could assign the same object to a different
variable and still have similar behavior since variable names don't
make any difference.

Likewise, in Scheme and in Clojure I can pass a function unquoted to a
higher-order function like map. This is not the case in Common Lisp,
which has separate namespaces for vars and functions.

What is the case with Elixir? When I type a name of a built-in
function in iex, it tries to call it with 0 args. However, when I type
a variable name to which I have assign a function via fn, I get the
value printed as expected. But calling the variable requires adding a
dot (the fact for which I have not found an explanation). Aren't
functions in Elixir first-class objects?

Thanks!

José Valim

unread,
Mar 23, 2012, 4:23:54 PM3/23/12
to elixir-l...@googlegroups.com
I've just finished reading the guide. It is very nicely written and
provides a great start, but it raises a couple of questions I have not
been able to find answers to.

Glad you enjoyed!

1. What's the difference between throw and raise? As far as I can
tell, they're orthogonal, meaning that I can only catch a thrown value
and rescue a raised error. What's the intention of having these two
separate things that are somewhat similar?

Mainly, you should use throw for control-flow and reserve raise for errors, which happens on developer mistakes or under exceptional circumstances.

In Elixir, this distinction is rather theoretical, but they matter in some languages like Ruby, where using errors/exceptions for control-flow is expensive because creating the exception object and backtrace is expensive.

2. Why do we need to use a dot after a variable name when the variable
is a function? A related question is how can I get the value of a
built-in function?

This question in Elixir is just a matter of syntax. If we allowed `var()` to call a function, `some_code()` could either mean that we are invoking a function in the variable some_code or invoking a named function some_code. In order to remove the ambiguity, Elixir reserves `some_code()` to call only named functions and requires `var.()` to invoke functions.

Also, regarding Higher Oder Functions, Elixir allows functions to be passed and returned from functions, but retrieving a named function from a module cannot be implemented trivially because functions in Erlang/Elixir are defined by their name and arity. Erlang for example has a special syntax like `fun map/2`, but I haven't added it to Elixir yet. Fortunately, we can achieve the same (and more) with partial application.

alco

unread,
Mar 24, 2012, 10:59:01 AM3/24/12
to elixir-lang-core
> Mainly, you should use throw for control-flow and reserve raise for errors,
> which happens on developer mistakes or under exceptional circumstances.
>
> In Elixir, this distinction is rather theoretical, but they matter in some
> languages like Ruby, where using errors/exceptions for control-flow is
> expensive because creating the exception object and backtrace is expensive.

This makes sense and I think it's a good choice to reserve exceptions
for truly
exceptional cases. In Objective-C land, for instance, exceptions are
likewise
only used for programmer's errors.

The point to make here is that at some point in the future we'll need
an
example in a working app or a short write-up on this convention
established by
Elixir, since it's not mentioned at all in the guide.


> This question in Elixir is just a matter of syntax. If we allowed `var()`
> to call a function, `some_code()` could either mean that we are invoking a
> function in the variable some_code or invoking a named function some_code.
> In order to remove the ambiguity, Elixir reserves `some_code()` to call
> only named functions and requires `var.()` to invoke functions.
> Also, regarding Higher Oder Functions, Elixir allows functions to be passed
> and returned from functions, but retrieving a named function from a module
> cannot be implemented trivially because functions in Erlang/Elixir are
> defined by their name and arity. Erlang for example has a special syntax
> like `fun map/2`, but I haven't added it to Elixir yet. Fortunately, we can
> achieve the same (and more) with partial application.

I think I've figured it out finally. My biggest misconception was that
I didn't
think of defining functions with different arities as defining
different
functions, even if the same name was used in both definitions. This
is, in fact,
the case in Erlang and Elixir: functions with different arities are
independent of each other, so my_fun/1 and my_fun/3 are different
functions
that just happen to have the same name. That's exactly what you have
been
telling me, it was just my thickness that got in the way :)

I'm ok with the default that functions are invoked regardless of
whether
parentheses are used. You've summed it up nicely in your gist --
https://gist.github.com/b0df374ef6483fafc380. Basically, as I
understand it, fn
returns a partially applied function, that is:

    my_size = fn(x) -> size x end

is conceptually exactly the same as

    my_size = size &1

This makes it possible to pass functions as arguments and return
values, I'm
content with this mechanism.

But I think using the postfix dot to invoke a partially applied
function (or,
in other words, a function assigned to a variable) is not the best
idea. It is
like something from another world, something that hasn't been used in
any other
language. This syntax is going to be used in all functions that accept
other
functions as arguments. The dot can be included by mistake, it can
also be
omitted by mistake. It has to be more prominent than that.

Every language I know of uses prefix notation for an operator when its
meaning is "dereferencing" or "extraction of value". For instance,

  &x     -- take an address of x in C
  *x     -- dereference a pointer in C
  ::x    -- use the global x, instead of the shadowing local x in C++
  *list  -- splice a list in Python
  **dict -- splice a dict in Python
  #'x    -- get a var object x rather than the value of x in Clojure

And so on. I'm sure you see my point. The dot is too strongly
associated with
property access, in my opinion.

We have a ^ syntax used in pattern matching: ^x means match against
the value
of x instead of binding x to the value. It can only be used inside an
assignment, so why not extend its usage outside of assignment to mean
the
function call of the function stored in x?

Alternatively, we could pick something that intuitively means "apply".
Because
what dot is doing is it applies the function to the list of arguments:

    apply my_size, ["123"]

This explanation can be included in the guide, and I believe it is
much more
intuitive. We just need to pick an appropriate character for the task.
It could
be @x, $x, or `x, whichever you like the most. I'm just proposing that
it is not
the dot and that it is used as a prefix.

José Valim

unread,
Mar 24, 2012, 2:31:41 PM3/24/12
to elixir-l...@googlegroups.com
This makes sense and I think it's a good choice to reserve exceptions
for truly exceptional cases. In Objective-C land, for instance, exceptions are
likewise only used for programmer's errors.

The point to make here is that at some point in the future we'll need
an example in a working app or a short write-up on this convention
established by Elixir, since it's not mentioned at all in the guide.

Agreed. Thanks for the concern. Once we have improved the IO part of the STDLIB, I will write a new chapter in the getting started guide and I think this will be a good point to describe such scenarios (mainly because the IO libs will avoid raising errors.
 
Every language I know of uses prefix notation for an operator when its
meaning is "dereferencing" or "extraction of value".

This is one way to interpret the situation. One other way we could interpret it is considering that functions are anonymous and therefore don't expect a name to be given on invocation. For example, imagine we have a module named List. If we want to invoke a function of that module, we need to pass a name, for example, flatten:

    List.flatten([1,2,3])

However, functions doesn't require a name to be invoked, and therefore we can omit the `flatten`:

    function.([1,2,3])

So we could fit everything under named and anonymous invocation. The expression flatten([1,2,3]) would be a named invocation as well, because it is simply a shortcut to __LOCAL__.flatten([1,2,3]).
    

Alexei Sholik

unread,
Mar 25, 2012, 7:02:07 AM3/25/12
to elixir-l...@googlegroups.com
This is one way to interpret the situation. One other way we could interpret it is considering that functions are anonymous and therefore don't expect a name to be given on invocation. For example, imagine we have a module named List. If we want to invoke a function of that module, we need to pass a name, for example, flatten:

    List.flatten([1,2,3])

However, functions doesn't require a name to be invoked, and therefore we can omit the `flatten`:

    function.([1,2,3])

So we could fit everything under named and anonymous invocation. The expression flatten([1,2,3]) would be a named invocation as well, because it is simply a shortcut to __LOCAL__.flatten([1,2,3]).

I don't understand the transition from "List.flatten" (which is MODULE_NAME.FUNCTION_NAME) to "function." (which is function.NOTHING ??).


--
Best regards
Alexei Sholik

José Valim

unread,
Mar 25, 2012, 7:11:32 AM3/25/12
to elixir-l...@googlegroups.com

Basically you always need a dot to invoke something. But sometimes there isn't anything between the dot and the parenthesis, because the function is anonymous.



José Valim
Founder and Lead Developer

Alexei Sholik

unread,
Mar 25, 2012, 7:25:30 AM3/25/12
to elixir-l...@googlegroups.com

Basically you always need a dot to invoke something. But sometimes there isn't anything between the dot and the parenthesis, because the function is anonymous.

I see. If LEFT.RIGHT means "Look for something named RIGHT in the thing denoted by LEFT" then "x.()" means "look for nothing (or something anonymous) in x and try to use it as a function". I find this approach unintuitive, because I can't use "x." to simply look for that anonymous thing or do a call without parens: "x. 5  # this doesn't work".

I think we've both demonstrated our points of view and this discussion will lead nowhere unless more people join and share their feedback. At least we'll be able to point newcomers to this discussion in case someone has similar problems with the dot as I do :)

José Valim

unread,
Mar 25, 2012, 7:46:12 AM3/25/12
to elixir-l...@googlegroups.com
 
I see. If LEFT.RIGHT means "Look for something named RIGHT in the thing denoted by LEFT" then "x.()" means "look for nothing (or something anonymous) in x and try to use it as a function".

For what is worth, this is also how it is represented in the syntax tree.
 
I find this approach unintuitive, because I can't use "x." to simply look for that anonymous thing or do a call without parens: "x. 5  # this doesn't work".

We could make it work, but "x. y" is "x.y()" or "x.(y)" ? :)
 
I think we've both demonstrated our points of view and this discussion will lead nowhere unless more people join and share their feedback. At least we'll be able to point newcomers to this discussion in case someone has similar problems with the dot as I do :)

Agreed. Thanks for the discussion anyway, it always forces to me re-think some decisions.

Reply all
Reply to author
Forward
0 new messages