We're now going to better get to know the Kernel module. The Kernel module is automatically imported everywhere, so you never have to explicitly specify the Kernel module unless that's needed to resolve some ambiguity, which does happen from time to time, since many Kernel functions have the same names as Elixir operators.
The Kernel module is big, really big, so the coverage of the Kernel module will be divided more than one part. Fortunately, many of the functions require little explanation since many of them directly correspond to operators or keywords in Elixir.
In fact, it appears from what I've seen so far that most of the operators and many of the keywords in Elixir have an equivalent function in the Kernel module. The Elixir compiler almost certainly translates operators and their operands into calls to these Kernel functions.
Since I've already introduced the concepts behind many of these functions, I'm not going to go in-depth on all of them. That would be boring and pointless. I'm assuming that you've been following along from the beginning, so I don't see a need to re-explain things. I'll only go into depth for the functions where it makes sense to do so. Finally, there are some functions in the Kernel module that relate to more advanced concepts I haven't learned about yet. I'll be glossing over these for sure, since I do not yet understand them yet. If I think it's useful to do so, I may come back and look at them again after I learn about the relevant concept, but that's only if I feel that it will add something to what we've already learned.
The Arithmetic Operator Functions
These functions directly correspond to arithmetic operators in Elixir. Whenever you use one of these operators in Elixir, the compiler calls these functions.
Function | Description | Example | Comments |
---|---|---|---|
*/ | Multiplication | 4 * 4 |
|
+/ | Unary plus | +3 |
I'm not sure why this is useful, since it doesn't appear to do anything more than echo the original value. |
+/2 | Addition | 5 + 3 |
|
-/1 | Unary minus | -1 |
Negates a number, causing the sign to toggle |
-/2 | Subtraction | 3 - 1 |
|
//2 | Float Division | 3 / 1 |
Result is always a float |
All of these operators should be familiar to you except the unary plus. At first I thought that the unary plus function was an absolute value function, but there's already abs/1 that does that. I played around with it to get a feel for what it does, and here are the results.
iex> -1
-1
iex> -(1 + 1)
-2
iex> -(-1 + -1)
2
iex> +(1 + 1)
2
iex> +(-1 + -1)
-2
iex> +(-4)
-4
iex> +4
4
It doesn't force a number to become positive, which makes sense now that I think about it; the unary minus doesn't force a value to be negative: it just toggles the sign on the number between positive and negative. The unary plus seems to do the opposite of unary minus, which is to just return the number with the original sign. This doesn't seem very useful, but there's something I'm probably missing here. It may be useful as a predefined function to pass to a higher-order function when you don't want to modify a number, but that's the only thing I can think of.
Calling Operator Functions
Although many of the functions in the Kernel module correspond to Elixir operators, they can be called directly or passed to higher-order functions. You would normally not call these functions directly, since the operator equivalent is much nicer to use, but it is normal to pass them to higher-order functions.
If you are weird and you want to call functions that correspond to operators, be aware that they cannot be called normally, because they will be interpreted as operators and not functions in the Kernel module.
iex> +(2, 3)
** (SyntaxError) iex:8: syntax error before: ')'
iex> *(2, 3)
** (SyntaxError) iex:8: syntax error before: '*'
Instead, you must explicitly reference the Kernel module so that Elixir knows you are referring to the function directly.
iex> 2 + 3
5
iex> Kernel.+(2, 3)
5
iex> 2 * 3
6
iex> Kernel.*(2, 4)
6
iex> 2 / 3
0.6666666666666666
iex> Kernel./(2, 3)
0.6666666666666666
You don't always need to specify the Kernel module when passing the function into a higher-order function as a parameter. Since you use the capture operator (&), Elixir will know that this is a function and not an operator.
iex> Enum.reduce(1..10, &+/2)
55
iex> Enum.reduce(1..10, &//2)
4.063492063492063
The last result was a bit unexpected for me because I was expecting the result to be (((1 / 2) / 3) / 4) .... Instead, the division occurred in the opposite order, with reduce dividing the number by the accumulator and not the other way around. Reduce passes the accumulator as the second parameter, not the first. If I wanted to get what I expected, I would have to create my own function to reverse the division operands.
iex> Enum.reduce(1..10, fn (number, acc) -> acc / number end)
2.7557319223985894e-7
The result is a very small decimal. The parameter order in a reduce function can make a big difference.
Comparison Functions
These functions correspond to comparison functions that return a boolean value after comparing. If you don't remember how these comparison operators work, please refer back to Lwm 7 where I go over that.
Function | Description | Example | Comments |
---|---|---|---|
!=/2 | Not equals (loose) | 5 != 4 |
|
!==/2 | Not equals (strict) | 5 !== 5.0 |
|
</2 | Less than | 4 < 10 |
|
<=/2 | Less than or equal | 5 <= 5 |
|
==/2 | Equals (loose) | 5 == 5.0 |
|
===/2 | Equals (strict) | 5 === 5 |
|
>/2 | Greater than | 12 > 10 |
|
>=/2 | Greater than or equal | 12 >= 10 |
Here are a few examples of calling these functions directly.
iex> Kernel.===(5, 5.0)
false
iex> Kernel.==(5, 5.0)
true
iex> Kernel.==("Blue", :blue)
false
iex> Kernel.>(3, 8)
false
iex> Kernel.>(8, 3)
true
Logical Boolean Operator Functions
These functions correspond to logical boolean operators in Elixir. If you don't remember how these operators work, please refer back to Lwm 7 where I go over that.
Function | Description | Example | Comments |
---|---|---|---|
!/1 | boolean not (loose) | !nil |
|
&&/2 | boolean and (loose) | nil && 4 |
|
and/2 | boolean and (strict) | true and false |
|
not/1 | boolean not (strict) | not false |
|
or/2 | boolean or (strict) | true or false |
|
||/2 | boolean or (loose) | nil || true |
Here are a few examples of calling these functions directly.
iex> Kernel.not(true)
false
iex> Kernel.and(5 > 2, 1 >= -1)
true
iex> Kernel.!(nil)
true
iex> Kernel.||(nil, true)
true
Elixir Code Construct Functions
These are functions in the Kernel module that I have lumped together in this category. These functions create Elixir code constructs such as modules, attributes, macros, named functions, and so forth. I'm not sure exactly when they are called, but I believe these functions are run when code is being loaded into the Elixir/Erlang runtime environment dynamically through IEx or via script files. I think these functions are used to translate Elixir code constructs into constructs the Erlang VM understands. They're something an interpreter would use. It's possible that compiled bytecode may use these as well, but I think compiled bytecode will more likely use the equivalent Erlang VM operations. I could also see these functions being used in someone's code for creating modules and functions at runtime, but that would not be something that was typically done.
This category of functions and the way the functions are used is a little mysterious to me, and I do not wish to attempt to call them directly at this point in my learning of Elixir. I think these are a candidate for revisiting in more detail once I have gained an advanced understanding of Elixir.
I'm going to summarize them for now. Some of them are related to concepts I haven't learned yet, and I'll make a note of those when I see them.
Function | Description | Comments |
---|---|---|
@/1 | Defines a module attribute | |
alias!/1 | Creates an alias in a macro | I don't understand what this is used for, since I haven't learned about macros yet. |
apply/2 | Calls a function with arguments | I could see this possibly being useful on occasion when the function to be called isn't known ahead of time. It's also a candidate for passing to a higher-order function. |
apply/3 | Calls a function in a module with arguments | |
binding/1 | Returns the binding for a context | I have no idea what this means at this point. Perhaps it's something to do with scoping. |
def/2 | Defines a function | |
defdelegate/2 | Defines a function that delegates to a function in another module | I believe this corresponds to a defdelegate keyword, but I haven't learned anything about that yet. I find the concept of function delegation to be interesting |
defexception/1 | Defines an exception type | |
defguard/1 | Defines a macro for use in guard expressions | |
defguardp/1 | Defines a private macro for use in guard expressions | |
defimpl/3 | Defines an implementation of a protocol | I have not learned about protocols yet |
defmacro/2 | Defines a macro | I have not learned about macros yet |
defmacrop/2 | Defines a private macro | |
defmodule/2 | Defines a module | |
defoverridable/1 | Makes certain functions overridable by outside code | I had no idea this was possible, but it's an interesting idea. Hopefully, I'll find out more about this |
defp/2 | Defines a private function | |
defprotocol/2 | Defines a protocol | |
defstruct/1 | Defines a struct | |
if/2 | Defines an "if" expression | |
in/2 | Corresponds to the "in" operator | |
unless/2 | Defines an "unless" expression | |
use/2 | Imports the module into the current context, calling any magic initialization code in the module | We call this function every time we put a "use" statement at the top of our code |
var!/2 | The documentation says something about not hygenizing a variable that doesn't make sense to me. | I suspect that this somehow has a role in macros, which I know nothing about yet |
|>/2 | Corresponds to the pipe operator |
Type Functions
These are functions that check to see if something is of a particular type. I covered most of these in lwm 6 where I discussed data types. I believe that all of these can be used in guard clauses.
Function | Description | Comments |
---|---|---|
is_atom/1 | Checks to see if something is an atom | |
is_binary/1 | Checks to see if something is a binary | |
is_bitstring/1 | Checks to see if something is a bitstring | |
is_boolean/1 | Checks if something is a boolean | |
is_float/1 | Checks if something is a float | |
is_function/1 | Checks if something is a function | |
is_function/2 | Checks if something is a function with a particular arity | |
is_integer/1 | Checks if something is an integer | |
is_list/1 | Checks if something is a list | |
is_map/1 | Checks if something is a map | |
is_nil/1 | Checks if something is nil | |
is_number/1 | Checks if something is a number | A number can be an integer or float |
is_pid/1 | Checks to see if something is a pid | I haven't yet learned what a pid is, other than knowing it stands for "process identifier" |
is_port/1 | Checks to see if something is a port | I haven't yet learned what a port is in the context of Elixir |
is_reference/1 | Checks to see if something is a reference | I haven't yet learned what a reference is in the context of Elixir |
is_tuple/1 | Checks to see if something is a tuple |
I'm going to try out some of the functions I haven't had much opportunity to use before.
iex> is_list([])
true
iex> is_list(3)
false
iex> is_map([])
false
iex> is_map(%{})
true
iex> is_nil([])
false
iex> is_nil(0)
false
iex> is_nil(nil)
true
iex> negate = fn number -> -number end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> add = fn (num1, num2) -> num1 + num2 end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> is_function(negate)
true
iex> is_function(add)
true
#negate is a function with an arity of 1
iex> is_function(negate, 1)
true
iex> is_function(negate, 2)
false
#add is a function with an arity of 2
iex> is_function(add, 1)
false
iex> is_function(add, 2)
true
iex> is_tuple({})
true
iex> is_tuple([])
Sigil Functions
I've previously covered sigils in lwm 41, so I won't go into detail here. Each sigil has a corresponding function in the Kernel module
Function | Description | Comments |
---|---|---|
sigil_C/2 | corresponds to the ~C sigil | Creates a character list without interpolation |
sigil_D/2 | corresponds to the ~D sigil | Creates a date value |
sigil_N/2 | corresponds to the ~N sigil | Creates a naive datetime value |
sigil_R/2 | corresponds to the ~R sigil | Creates a regular expression without interpolation |
sigil_S/2 | corresponds to the ~S sigil | Creates a string without interpolation |
sigil_T/2 | corresponds to the ~T sigil | Creates a time value |
sigil_W/2 | corresponds to the ~W sigil | Creates a word list without interpolation |
sigil_c/2 | corresponds to the ~c sigil | Creates a character list with interpolation |
sigil_r/2 | corresponds to the ~r sigil | Creates a regular expression with interpolation |
sigil_s/2 | corresponds to the ~s sigil | Creates a string with interpolation |
sigil_w/2 | corresponds to the ~w sigil | Creates a word list with interpolation |
I'm uncertain as to how to call these functions directly, since it's not even clear what needs to be passed as parameters. Even the examples in the sigil functions' documentation just show examples of using the sigil, and not calling the sigil functions. I don't think it's important to know how to either: the sigils themselves are just fine.
Process Functions
I am aware of Elixir processes and have a basic idea of how they work, which I talked about in lwm 2. The Kernel module contains some functions that work with processes. I'm just going to give a summary of what they do. I assume I'll understand these functions better after I've gained a good understanding of Elixir processes.
Function | Description | Comments |
---|---|---|
self/0 | returns pid of current process | pid is short for process identifier |
send/2 | sends a message to a destination, which could be another process or other things that I'm unclear about | |
spawn/1 | spawns a function as new process and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
spawn/3 | spawns a function from a module as new process and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
spawn_link/1 | spawns a function as new process, linking it to the current process, and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
spawn_link/3 | spawns a function from a module as new process, linking it to the current process, and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
spawn_monitor/1 | spawns a function as new process, monitors it, and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
spawn_monitor/3 | spawns a function from a module as new process, monitors it, and returns the pid | Documentation recommends not using any of the spawn functions directly, as there are higher-level abstractions that make life a lot easier for the developer |
exit/1 | Stops the current process | This can be used to exit your application immediately, assuming that your application only consist of a single process |
Exception Functions
I previously covered exceptions in lwm 48, so I will just list the exception-related functions and write a brief summary of what they do. Other than the "reraise" function, these functions are all covered in the section on exceptions.
Function | Description | Comments |
---|---|---|
raise/1 | raises an exception | |
raise/2 | raises an exception | |
reraise/2 | re-throws an exception, preserving the original stack trace | This is equivalent to using the "throw" keyword in C# without any arguments |
reraise/3 | re-throws an exception, preserving the original stack trace | This is equivalent to using the "throw" keyword in C# without any arguments |
throw/1 | creates a non-local return from a function, used as part of exception handling | I have no idea what this means. See the exception handling post where I talk about this keyword |
All the reraise functions do is throw the original exception with the original call stack trace. It allows you to intercept an exception, do something, and then let it continue flying up the call stack as if it had never been caught.
That's all we're going to cover today. Despite the Kernal module being huge, I we managed to cover the majority of the functions. As usual, I'm going to switch to another topic next time to prevent monotony, but when I come back to this topic, I'm going to go in depth into the functions I think deserve more explanation and examples.