You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -15,23 +15,25 @@ The idea of the parallel compiler is very simple: for each file we want to compi
15
15
16
16
In Elixir, we could write this code as follows:
17
17
18
-
def spawn_compilers([current | files], output) do
19
-
parent = Process.self()
20
-
child = spawn_link(fn ->
21
-
:elixir_compiler.file_to_path(current, output)
22
-
send parent, { :compiled, Process.self() }
23
-
end)
24
-
receive do
25
-
{ :compiled, ^child } ->
26
-
spawn_compilers(files, output)
27
-
{ :EXIT, ^child, { reason, where } } ->
28
-
:erlang.raise(:error, reason, where)
29
-
end
30
-
end
31
-
32
-
def spawn_compilers([], _output) do
33
-
:done
34
-
end
18
+
```elixir
19
+
defspawn_compilers([current | files], output) do
20
+
parent =Process.self()
21
+
child =spawn_link(fn->
22
+
:elixir_compiler.file_to_path(current, output)
23
+
send parent, { :compiled, Process.self() }
24
+
end)
25
+
receivedo
26
+
{ :compiled, ^child } ->
27
+
spawn_compilers(files, output)
28
+
{ :EXIT, ^child, { reason, where } } ->
29
+
:erlang.raise(:error, reason, where)
30
+
end
31
+
end
32
+
33
+
defspawn_compilers([], _output) do
34
+
:done
35
+
end
36
+
```
35
37
36
38
In the first line, we define a function named `spawn_compilers` that receives two arguments, the first is a list of files to compile and the second is a string telling us where to write the compiled file. The first argument is represented as a list with head and tail (`[current | files]`) where the top of the list is assigned to `current` and the remaining items to `files`. If the list is empty, the first clause of `spawn_compilers` is not going to match, the clause `spawn_compilers([], _output)` defined at the end will instead.
37
39
@@ -53,19 +55,21 @@ With this code, we were able to compile each file inside a different process. Ho
53
55
54
56
Imagine that we have two files, `a.ex` and `b.ex`, with the following contents:
55
57
56
-
# a.ex
57
-
defmodule A do
58
-
B.define
59
-
end
60
-
61
-
# b.ex
62
-
defmodule B do
63
-
defmacro define do
64
-
quote do
65
-
def one, do: 1
66
-
end
67
-
end
58
+
```elixir
59
+
# a.ex
60
+
defmoduleAdo
61
+
B.define
62
+
end
63
+
64
+
# b.ex
65
+
defmoduleBdo
66
+
defmacrodefinedo
67
+
quotedo
68
+
defone, do:1
68
69
end
70
+
end
71
+
end
72
+
```
69
73
70
74
In order to compile `A`, we need to ensure that `B` is already compiled and loaded so we can invoke the `define` macro. This means the file `a.ex` depends on the file `b.ex`. When compiling files in parallel, we want to be able to detect such cases and automatically handle them.
71
75
@@ -79,87 +83,95 @@ By default, Elixir (and Erlang) code is autoloaded. This means that, if we invok
79
83
80
84
As discussed in the previous section, we want to extend the error handler to actually stop the currently running process whenever a module is not found and resume the process only after we ensure the module is compiled. To do that, we can simply define our own error handler and ask Erlang to use it. Our custom error handler is defined as follows:
Our error handler defines two public functions. Both those functions are callbacks required to be implemented by the error handler. They simply call `ensure_loaded(module)` and then delegate the remaining logic to Erlang's original `error_handler`.
108
114
109
115
The private `ensure_loaded` function calls `Code.ensure_loaded(module)` which checks if the given module is loaded and, if not, tries to load it. In case it succeeds, it returns `{ :module, _ }`, which means the module is available and we don't need to stop the current process. However, if it returns `{ :error, _ }`, it means the module cannot be found and we need to wait until it is compiled. For that, we invoke `Process.get(:elixir_parent_compiler)` to get the PID of the main process so we can notify it that we are waiting on a given module. Then we invoke the macro `receive` as a way to stop the current process until we receive a message from the parent saying new modules are available, starting the flow again.
110
116
111
117
With our error handler code in place, the first thing we need to do is to change the function given to `spawn_link` to use the new error handler:
112
118
113
-
spawn_link(fn ->
114
-
Process.put(:elixir_parent_compiler, parent)
115
-
Process.flag(:error_handler, Elixir.ErrorHandler)
119
+
```elixir
120
+
spawn_link(fn->
121
+
Process.put(:elixir_parent_compiler, parent)
122
+
Process.flag(:error_handler, Elixir.ErrorHandler)
116
123
117
-
:elixir_compiler.file_to_path(current, output)
118
-
send parent, { :compiled, Process.self() }
119
-
end)
124
+
:elixir_compiler.file_to_path(current, output)
125
+
send parent, { :compiled, Process.self() }
126
+
end)
127
+
```
120
128
121
129
Notice that we have two small additions. First we store the `:elixir_parent_compiler` PID in the process dictionary so we are able to read it from the error handler and then we proceed to configure a flag in our process so our new error handler is invoked whenever a module or function cannot be found.
122
130
123
131
Second, our main process can now receive a new `{ :waiting, child, module }` message, so we need to extend it to account for those messages. Not only that, we need to control which PIDs we have spawned so we can notify them whenever a new module is compiled, forcing us to add a new argument to the `spawn_compilers` function. `spawn_compilers` would then be rewritten as follows:
124
132
125
-
def spawn_compilers([current | files], output, stack) do
126
-
parent = Process.self()
127
-
child = spawn_link(fn ->
128
-
:elixir_compiler.file_to_path(current, output)
129
-
send parent, { :compiled, Process.self() }
130
-
end)
131
-
wait_for_messages(files, output, [child | stack])
132
-
end
133
-
134
-
# No more files and stack is empty, we are done
135
-
def spawn_compilers([], _output, []) do
136
-
:done
137
-
end
138
-
139
-
# No more files and stack is not empty, wait for all messages
140
-
def spawn_compilers([], output, stack) do
141
-
wait_for_messages([], output, stack)
142
-
end
133
+
```elixir
134
+
defspawn_compilers([current | files], output, stack) do
135
+
parent =Process.self()
136
+
child =spawn_link(fn->
137
+
:elixir_compiler.file_to_path(current, output)
138
+
send parent, { :compiled, Process.self() }
139
+
end)
140
+
wait_for_messages(files, output, [child | stack])
141
+
end
142
+
143
+
# No more files and stack is empty, we are done
144
+
defspawn_compilers([], _output, []) do
145
+
:done
146
+
end
147
+
148
+
# No more files and stack is not empty, wait for all messages
149
+
defspawn_compilers([], output, stack) do
150
+
wait_for_messages([], output, stack)
151
+
end
152
+
```
143
153
144
154
Notice we added an extra clause to `spawn_compilers` so we can properly handle the case where we don't have more files to spawn but we are still waiting for processes in the stack. We have also moved our `receive` logic to a new private function called `wait_for_messages`, implemented as follows:
145
155
146
-
defp wait_for_messages(files, output, stack) do
147
-
receive do
148
-
{ :compiled, child } ->
149
-
new_stack = List.delete(stack, child)
150
-
Enum.each new_stack, fn(pid) ->
151
-
send pid, { :release, Process.self }
152
-
end
153
-
spawn_compilers(files, output, new_stack)
154
-
{ :waiting, _child, _module } ->
155
-
spawn_compilers(files, output, stack)
156
-
{ :EXIT, _child, { reason, where } } ->
157
-
:erlang.raise(:error, reason, where)
158
-
after
159
-
10_000 ->
160
-
raise "dependency on nonexistent module or possible deadlock"
156
+
```elixir
157
+
defpwait_for_messages(files, output, stack) do
158
+
receivedo
159
+
{ :compiled, child } ->
160
+
new_stack =List.delete(stack, child)
161
+
Enum.each new_stack, fn(pid) ->
162
+
send pid, { :release, Process.self }
161
163
end
162
-
end
164
+
spawn_compilers(files, output, new_stack)
165
+
{ :waiting, _child, _module } ->
166
+
spawn_compilers(files, output, stack)
167
+
{ :EXIT, _child, { reason, where } } ->
168
+
:erlang.raise(:error, reason, where)
169
+
after
170
+
10_000->
171
+
raise"dependency on nonexistent module or possible deadlock"
172
+
end
173
+
end
174
+
```
163
175
164
176
The implementation for `wait_for_messages` is now broken into 4 clauses:
Copy file name to clipboardExpand all lines: src/content/blog/elixir-v0-8-0-released.md
+6-2Lines changed: 6 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,11 +13,15 @@ On the last 9th January, we celebrated [two years since Elixir's first commit](h
13
13
14
14
One of the goals for the v0.8 release was better integration with OTP applications. By passing the `--sup` option to Mix, you can start a new OTP Application containing application callbacks and a supervisor:
15
15
16
-
mix new my_app --sup
16
+
```bash
17
+
mix new my_app --sup
18
+
```
17
19
18
20
And applications can be started directly from the command line as well:
19
21
20
-
elixir --app my_app
22
+
```bash
23
+
elixir --app my_app
24
+
```
21
25
22
26
We have written a whole [guide chapter about creating OTP applications, supervisors and servers](https://hexdocs.pm/elixir/supervisor-and-application.html). Give it a try!
Copy file name to clipboardExpand all lines: src/content/blog/elixir-v0-9-0-released.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,9 +43,11 @@ $ mix new my_project --umbrella
43
43
44
44
The generated project will have the following structure:
45
45
46
-
apps/
47
-
mix.exs
48
-
README.md
46
+
```
47
+
apps/
48
+
mix.exs
49
+
README.md
50
+
```
49
51
50
52
Now, inside the `apps` directory, you can create as many applications as you want and running `mix compile` inside the umbrella project will automatically compile all applications. The [original discussion for this feature](https://github.com/elixir-lang/elixir/issues/667) contains more details about how it all works.
Copy file name to clipboardExpand all lines: src/content/blog/elixir-v1-10-0-released.md
+7-3Lines changed: 7 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -97,7 +97,9 @@ end
97
97
98
98
This approach has one big limitation: if you change the value of the application environment after the code is compiled, the value used at runtime is not going to change! For example, if you are using `mix release` and your `config/releases.exs` has:
99
99
100
-
config :my_app, :db_host, "db.production"
100
+
```elixir
101
+
config :my_app, :db_host, "db.production"
102
+
```
101
103
102
104
Because `config/releases.exs` is read after the code is compiled, the new value will have no effect as the code was compiled to connect to "db.local".
103
105
@@ -143,8 +145,10 @@ In Elixir v1.10, we have addressed these problems by [introducing compiler traci
143
145
144
146
Elixir itself is using the new compiler tracing to provide new functionality. One advantage of this approach is that developers can now disable undefined function warnings directly on the callsite. For example, imagine you have an optional dependency which may not be available in some cases. You can tell the compiler to skip warning on calls to optional modules with:
145
147
146
-
@compile {:no_warn_undefined, OptionalDependency}
147
-
defdelegate my_function_call(arg), to: OptionalDependency
Previously, this information had to be added to the overall project configuration, which was far away from where the optional call effectively happened.
Copy file name to clipboardExpand all lines: src/content/blog/elixir-v1-13-0-released.md
+21-15Lines changed: 21 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,19 +82,23 @@ Those improvements came from direct feedback from the community. A special shout
82
82
83
83
Thanks to its sigils, Elixir provides the ability of embedding snippets in other languages inside its source code. One could use it to embed XML:
84
84
85
-
~X"""
86
-
<?xml version="1.0" encoding="UTF-8"?>
87
-
<text><![CDATA[Hello World]]></text>
88
-
"""
85
+
```elixir
86
+
~X"""
87
+
<?xml version="1.0" encoding="UTF-8"?>
88
+
<text><![CDATA[Hello World]]></text>
89
+
"""
90
+
```
89
91
90
92
Or even [Zig](https://ziglang.org/), [via the Zigler project](https://github.com/ityonemo/zigler):
91
93
92
-
~Z"""
93
-
/// nif: example_fun/2
94
-
fn example_fun(value1: f64, value2: f64) bool {
95
-
return value1 > value2;
96
-
}
97
-
"""
94
+
```elixir
95
+
~Z"""
96
+
/// nif: example_fun/2
97
+
fn example_fun(value1: f64, value2: f64) bool {
98
+
return value1 > value2;
99
+
}
100
+
"""
101
+
```
98
102
99
103
However, while you can format Elixir source code with [`mix format`](https://hexdocs.pm/mix/Mix.Tasks.Format.html), you could not format the code inside snippets.
100
104
@@ -134,11 +138,13 @@ We are looking forward to see how this new functionality will be used by communi
134
138
135
139
`SyntaxError` and `TokenMissingError` were improved to show a code snippet whenever possible:
The `Code` module has also been augmented with two functions: [`Code.string_to_quoted_with_comments/2`](https://hexdocs.pm/elixir/Code.html#string_to_quoted_with_comments/2) and [`Code.quoted_to_algebra/2`](https://hexdocs.pm/elixir/Code.html#quoted_to_algebra/2). Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
Copy file name to clipboardExpand all lines: src/content/blog/elixir-v1-3-0-released.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -167,7 +167,7 @@ Elixir v1.3 includes improvements to the option parser, including `OptionParser.
167
167
168
168
For example, invoking `mix test --unknown` in earlier Elixir versions would silently discard the `--unknown` option. Now `mix test` correctly reports such errors:
169
169
170
-
```
170
+
```bash
171
171
$ mix test --unknown
172
172
** (Mix) Could not invoke task "test": 1 error found!
173
173
--unknown : Unknown option
@@ -251,7 +251,7 @@ end
251
251
252
252
Every test inside a describe block will be tagged with the describe block name. This allows developers to run tests that belong to particular blocks, be them in the same file or across many files:
0 commit comments