Elixir Function name compatibility design issue?

So I’m trying to full-fill the interface of an erlang library, however it seems I’ve hit a design fault in Elixir in regards to intercompatability…

In Erlang a function name can be any atom, so in other words anything, however in Elixir function names are arbitrarily constrained and it seems that an atom cannot be used. I’m needing to define a function name that contains $ in it, and I am needing to call a function name that contains a $ in it, however I’ve tried all of the following variants to no avail:

iex> defmodule Testering do def $blah$, do: 42 end
** (SyntaxError) iex:4: unexpected token: "$" (column 28, codepoint U+0024)

iex> defmodule Testering do def :$blah$, do: 42 end
** (SyntaxError) iex:4: unexpected token: ":" (column 28, codepoint U+003A)

iex> defmodule Testering do def :"$blah$", do: 42 end
warning: redefining module Testering (current version defined in memory)
  iex:4

** (CompileError) iex:4: invalid syntax in def :"$blah$"
    iex:4: (module)
iex> defmodule Testering do def :'$blah$', do: 42 end
warning: redefining module Testering (current version defined in memory)
  iex:4

** (CompileError) iex:4: invalid syntax in def :"$blah$"
    iex:4: (module)
iex> Hrmm.$someFun$()
** (SyntaxError) iex:4: unexpected token: "$" (column 6, codepoint U+0024)

iex> Hrmm.:$someFun$()
** (SyntaxError) iex:4: unexpected token: ":" (column 6, codepoint U+003A)

iex> Hrmm.:"$someFun$"()
** (SyntaxError) iex:4: syntax error before: "$someFun$"

This is the first major compatibility issue I’ve run across, and it seems pretty major, how do you work around it other than dropping in to Erlang directly (I.E., how do you stay ‘inside’ of Elixir)?

For note, in erlang you can just do (also wtf syntax coloring in discourse for erlang…):

% Define a function of any name:
'$blah$'(A) -> A + 42.

% Call a function of any name:
'$blah$'(42).
1 Like

Well figured out a simple way to work around ‘calling’ such a function, just use apply/3, however that incurs a speed hit so how would you direct-call anyway? And this does not fix being able to make a function of such a name either…

EDIT: Ah hah, figured out how to direct call on a module, apparently . magically makes an atom, so doing Blah."$bloop$"() works. However this does not work for local calls (nor does :"$blah$"(), so doing that still does a speed hit when you could local call instead. This will work well enough for my use-case at least. Now let’s see if that works for defining functions, and nope, still no clue how to define such a function for a behaviour…

1 Like

Is there really a drop in performance? As far as I can remember the erlang performance myths there should not…

1 Like

Does an apply causes a global environment lookup that does not exist when doing a direct module name call since that gets looked up at load-time. A local call is often inlined or has a direct pointer call compared to even a module call to the same module, which has to look in the module environment to grab the function.

The speed does not matter much unless doing performance sensitive stuff, which this is, hence I would not be caring much, I’m wanting to intersperse elixir macro’s around as making erlang parse transforms is quite painful in comparison. ^.^

Regardless, this is still a weird lapse in intercompatibility…

1 Like

Whoo hoo, fixed working around making a def with a macro. Still unsure how to do a local call, but this will at least get me going, if not a bit painfully. ^.^

iex> defmodule Testering do
...>   import BorkedElixirFixes
...>   def :"$tester$", do: 42
...>   def blah, do: :bloop
...> end
{:module, Testering,
 <<70, 79, 82, 49, 0, 0, 4, 152, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 131,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:"$tester$", 0}}
iex> Testering."$tester$"()
42

A side effect because of some odd elixir parsing is that you cannot do def :"$"(a,b) but instead have to do def :"$".(a,b), but I’ll live with it, elixir’s syntax is very nonsensical at times (ruby heritage I guess) but the macro’s are awesome. ^.^

1 Like

Defining functions with arbitrary names can be performed with unquote(name):

defmodule Foo do
  def unquote(:"$foo")(x, y, z), do: x + y + z
end
Foo."$foo"(1, 2, 3) #=> 6

It indeed seems, there’s no way to directly call a private function with a “wired” name. This can be hacked-around using macros, though.

defmodule Foo do
  defp unquote(:"$foo")(x, y, z), do: x + y + z
  def test, do: unquote({:"$foo", [], [1, 2, 3]})
end
Foo.test #=> 6

I guess a syntax for calling functions with arbitrary names (even through a macro) should be available.

1 Like

Yep, already did with def, and I added a static_apply too that allows for inlining. :slight_smile:

It is weird the Elixir AST allows it without dying, but the language does not…

I just realized I already knew this about the elixir ast, I use such things in my elixir_ml stuff, that was probably why it was so surprising that the language did not support it. ^.^

1 Like

The AST always allows more constructs than the textual format, as it is not about by syntax rules.

And there is no performance cost in calling apply(Foo, :bar, [1, 2, 3]). If the arguments are known at compile time, then it is equivalent to Foo.bar(1, 2, 3).

1 Like

You can’t apply call a private function, though.

2 Likes

But there is a cost of calling Foo.bar(1, 2, 3) over calling bar(1, 2, 3), including it will reload the module if code has been swapped even if you do not want to swap to the new code yet.

1 Like

Yes, I missed the fact it was meant to be a local call. :slight_smile:

2 Likes