Files
n8n-openai-adapter/lib/elixir/special_forms.ex
T
2012-05-13 15:33:57 +02:00

496 lines
13 KiB
Elixir

defmodule Elixir.SpecialForms do
@moduledoc """
In this module we define Elixir special forms. Those are called
special forms because they cannot be overridden by the developer
and sometimes have lexical scope (like refer, require, import, etc).
"""
@doc """
Defines a new tuple.
## Examples
:{}.(1,2,3)
{ 1, 2, 3 }
"""
defmacro :{}.(args)
@doc """
Defines a new list.
## Examples
:[].(1,2,3)
[ 1, 2, 3 ]
"""
defmacro :[].(args)
@doc """
Defines a new bitstring.
## Examples
:<<>>.(1,2,3)
<< 1, 2, 3 >>
"""
defmacro :<<>>.(args)
@doc """
`refer` is used to setup aliases between modules.
## Examples
`refer` can be used to setup an alias for any module:
defmodule Math do
refer MyKeyword, as: Keyword
end
In the example above, we have set up `MyOrdict` to be referenced
as `Keyword`. So now, any reference to `Keyword` will be
automatically replaced by `MyKeyword`.
In case one wants to access the original `Keyword`, it can be done
by accessing __MAIN__:
Keyword.values #=> uses MyKeyword.values
__MAIN__.Keyword.values #=> uses Keyword.values
Notice that calling `refer` without the `as:` option automatically
sets an alias based on the last part of the module. For example:
refer Foo.Bar.Baz
Is the same as:
refer Foo.Bar.Baz, as: Baz
## Lexical scope
`import`, `require` and `refer` are called directives and all
have lexical scope. This means you can set up aliases inside
specific functions and it won't affect the overall scope.
"""
defmacro refer(module, opts)
@doc """
`require` is used to require the presence of external
modules so macros can be invoked.
## Examples
Notice that usually modules should not be required before usage,
the only exception is if you want to use the macros from a module.
In such cases, you need to explicitly require them.
Let's suppose you created your own `if` implementation in the module
`MyMacros`. If you want to invoke it, you need to first explicitly
require the `MyMacros`:
defmodule Math do
require MyMacros
MyMacros.if do_something, it_works
end
An attempt to call a macro that was not loaded will raise an error.
## Refer shortcut
`require` also accepts `as:` as an option so it automatically sets
up an alias. Please check `refer` for more information.
"""
defmacro require(module, opts)
@doc """
`import` allows one to easily access functions or macros from
others modules without using the qualified name.
## Examples
If you want to use the `values` function from `Keyword` several times
in your module and you don't want to always type `Keyword.values`,
you can simply import it:
defmodule Math do
import Keyword, only: [values: 1]
def some_function do
# call values(orddict)
end
end
In this case, we are importing only the function `values` (with arity 1)
from `Keyword`. Although `only` is optional, its usage is recommended.
`except` could also be given as an option. If no option is given, all
functions and macros are imported.
In case you want to import only functions or macros, you can pass a
first argument selecting the scope:
import :macros, MyMacros
And you can then use `only` or `except` to filter the macros being
included.
## Lexical scope
It is important to notice that `import` is lexical. This means you
can import specific macros inside specific functions:
defmodule Math do
def some_function do
# 1) Disable `if/2` from Elixir.Builtin
import Elixir.Builtin, except: [if: 2]
# 2) Require the new `if` macro from MyMacros
import MyMacros
# 3) Use the new macro
if do_something, it_works
end
end
In the example above, we imported macros from `MyMacros`, replacing
the original `if/2` implementation by our own during that
specific function. All other functions in that module will still
be able to use the original one.
## Refer/Require shortcut
All imported modules are also required by default. `import`
also accepts `as:` as an option so it automatically sets up
an alias. Please check `refer` for more information.
"""
defmacro import(module, opts)
@doc """
Returns the current module name as an atom or nil otherwise.
"""
defmacro __MODULE__
@doc """
Returns the current file name as a binary.
"""
defmacro __FILE__
@doc """
Returns the current line number as an integer.
"""
defmacro __LINE__
@doc """
Returns the current function as a tuple,
where the first element is the name as an atom
and the second is the arity as an integer.
"""
defmacro __FUNCTION__
@doc """
Allows you to get the representation of any expression.
## Examples
quote do: sum(1, 2, 3)
#=> { :sum, 0, [1, 2, 3] }
## Homoiconicity
Elixir is an homoiconic language. Any Elixir program can be
represented using its own data structures. The building block
of Elixir homoiconicity is a tuple with three elements, for example:
{ :sum, 1, [1, 2, 3] }
The tuple above represents a function call to sum passing 1, 2 and
3 as arguments. The tuple elements are:
* The first element of the tuple is always an atom or
another tuple in the same representation;
* The second element of the tuple is always an integer
representing the line number;
* The third element of the tuple are the arguments for the
function call. The third argument may be an atom, meaning
that it may be a variable.
## Macro literals
Besides the tuple described above, Elixir has a few literals that
when quoted return themselves. They are:
:sum #=> Atoms
1 #=> Integers
2.0 #=> Floats
[1,2] #=> Lists
"binaries" #=> Binaries
{key, value} #=> Tuple with two elements
## Hygiene
Elixir macros are hygienic regarding to variables. This means
a variable defined in a macro cannot affect the scope where
the macro is included. Consider the following example:
defmodule Hygiene do
defmacro no_interference do
quote do: a = 1
end
end
require Hygiene
a = 10
Hygiene.no_interference
a #=> 10
In the example above, `a` returns 10 even if the macro
is apparently setting it to 1 because the variables defined
in the macro does not affect the context the macro is
executed. If you want to set or get a variable, you can do
it with the help of the `var!` macro:
defmodule NoHygiene do
defmacro interference do
quote do: var!(a) = 1
end
end
require NoHygiene
a = 10
NoHygiene.interference
a #=> 11
Notice that references are not hygienic in Elixir unless
you explicitly access it via __MAIN__ to the reference name.
quote do
__MAIN__.Foo #=> Access the root Foo
Foo #=> Access the Foo reference in the current
module (if any is set), then fallback to root
end
## Options
`quote` also accepts some options as arguments. For example,
hygiene can be turned off via `hygiene: false` which is useful
when one is generating a code that should be inserted into
some function.
"""
defmacro quote(opts, do: contents)
@doc """
Unquotes the given expression from inside a macro.
## Examples
Imagine the situation you have a variable `name` and
you want to inject it inside some quote. The first attempt
would be:
value = 13
quote do: sum(1, value, 3)
Which would then return:
{ :sum, 0, [1, { :value, 0, quoted }, 3] }
Which is not the expected result. For this, we use unquote:
value = 13
quote do: sum(1, unquote(value), 3)
#=> { :sum, 0, [1, 13, 3] }
"""
defmacro unquote(expr)
@doc """
Unquotes the given list expanding its arguments. Similar
to unquote.
## Examples
values = [2,3,4]
quote do: sum(1, unquote_splicing(values), 5)
#=> { :sum, 0, [1, 2, 3, 4, 5] }
"""
defmacro unquote_splicing(expr)
@doc """
Returns an anonymous function based on the given arguments.
## Examples
sum = fn(x, y, do: x + y)
sum.(1, 2) #=> 3
Notice that a function needs to be invoked using the dot between
the function and the arguments.
A function could also be defined using the `end` syntax, although
it is recommend to use it only with the stab operator in order to
avoid ambiguity. For example, consider this case:
Enum.map [1,2,3], fn x ->
x * 2
end
The example works fine because `->` binds to the closest function call,
which is `fn`, but if we replace it by `do/end`, it will fail:
Enum.map [1,2,3], fn(x) do
x * 2
end
The reason it fails is because do/end always bind to the farthest
function call.
## Function with multiple clauses
One may define a function which expects different clauses as long
as all clauses expects the same number of arguments:
fun = fn do:
[x, y] when y < 0 ->
x - y
[x, y] ->
x + y
end
fun.(10, -10) #=> 20
fun.(10, 10) #=> 20
"""
defmacro fn(args)
@doc """
Handle annonymous recursive loops.
## Examples
list = [1,2,3]
loop list, [] do
[[h|t], acc] ->
recur t, [h*2|acc]
[[], acc] ->
acc
end
#=> [6,4,2]
Notice that all match clauses expects the same ammount
of arguments. Guards can also be given.
Recursion happens by calling recur with the same number
of arguments of each match clause. `recur` does not guarantee
that it will be tail recursive.
"""
defmacro loop(args)
@doc """
A function that forces the current loop to recur. See `loop/1`
for more information.
"""
defmacro recur(args)
@doc """
List comprehensions allow you to quickly build a list from another list:
lc n in [1,2,3,4], do: n * 2
#=> [2,4,6,8]
A comprehension accepts many generators and also filters. Filters must be given after the when clause:
# A comprehension with a generator and a filter
lc n in [1,2,3,4,5,6] when rem(n, 2) == 0, do: n
#=> [2,4,6]
# A comprehension with two generators
lc x in [1,2], y in [2,3], do: x*y
#=> [2,3,4,6]
Elixir provides generators for both lists and bitstrings:
# A list generator:
lc n in [1,2,3,4], do: n * 2
#=> [2,4,6,8]
# A bit string generator:
lc <<n>> in <<1,2,3,4>>, do: n * 2
#=> [2,4,6,8]
Bit string generators are quite useful when you need to organize bit string streams:
iex> pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>
iex> lc <<r:8,g:8,b:8>> in pixels, do: {r,g,b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
Elixir does its best to hide the differences between list and bit string generators.
However, there is a special case due to Erlang limitation where we need to explicitly
tell Erlang that a list is being given as argument:
# This will fail because when Elixir sees that the left side
# of the in expression is a bit string, it expects the right side
# to be a bit string as well:
lc <<n>> in [<<1>>,<<2>>,<<3>>], do: n*2
#=> ** (ErlangError) erlang error {:bad_generator,[<<1>>,<<2>>,<<3>>]}
# You need to be explicit and use inlist:
lc inlist(<<n>>, [<<1>>,<<2>>,<<3>>]), do: n*2
#=> [2,4,6]
# For consistency, inbin is also available:
lc inbin(<<n>>, <<1,2,3>>), do: n*2
#=> [2,4,6]
Notice that although comprehensions uses `when` to specify filters, filters are not
guards and therefore accept any expression (they are not limited as guards).
"""
defmacro lc(args)
@doc """
Defines a bit comprehension. It follows the same syntax as
a list comprehension but expects each element returned to
be a bitstring. For example, here is how to remove all
spaces from a string:
bc <<c>> in " hello world " when c != ?\s, do: <<c>>
"helloworld"
"""
defmacro bc(args)
@doc """
Keeps one of the given expressions depending in the context
of evaluation is a guard or not. This is useful when creating
macro that should work both inside and outside guards but
still hold some characteristics.
## Example
A good example is the `is_exception/1` macro defined in Elixir:
defmacro is_exception(thing) do
quote do
quote do
is_tuple(unquote(thing)) and elem(unquote(thing), 2) == :__exception__
else
result = unquote(thing)
is_tuple(result) and elem(result, 2) == :__exception__
end
end
end
Notice that if inside a guard, we unquote the same element twice.
This will cause the same element to be evaluted twice, but this is
fine for guards since we cannot assign variables in guards and
we cannot call expressions inside guards. However, when outside
of a guard, evaluating the arguments twice can be harmful and
unexpected, for this reason, we save the result in a variable.
In the example above, `in_guard` is allowing us to customize
the same macro to work inside and outside guards.
"""
defmacro in_guard(do: do_block, else: else_block)
end