Skip to content

Commit bf79066

Browse files
author
José Valim
committed
Update CHANGELOG
1 parent 40b7464 commit bf79066

File tree

5 files changed

+290
-26
lines changed

5 files changed

+290
-26
lines changed

CHANGELOG.md

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,270 @@
11
# Changelog for Elixir v1.3
22

3+
Elixir v1.3 brings many improvements to the language, the compiler and its tooling, specially Mix (Elixir's build tool) and ExUnit (Elixir's test framework).
4+
5+
## Language improvements
6+
7+
The language has been improved semantically and includes new types and APIs. Let's see the three major features.
8+
9+
### Deprecation of imperative assignment
10+
11+
Elixir will now warn if constructs like `if`, `case` and friends assign to a variable that is accessed in an outer scope. As an example, imagine a function called `format` that receives a message and some options and it must return a path alongside the message:
12+
13+
```elixir
14+
def format(message, opts) do
15+
path =
16+
if (file = opts[:file]) && (line = opts[:line]) do
17+
relative = Path.relative_to_cwd(file)
18+
message = Exception.format_file_line(relative, line) <> " " <> message
19+
relative
20+
end
21+
22+
{path, message}
23+
end
24+
```
25+
26+
The `if` block above is implicitly changing the value in `message`. Now imagine we want to move the `if` block to its own function to clean up the implementation:
27+
28+
```elixir
29+
def format(message, opts) do
30+
path = with_file_and_line(message, opts)
31+
{path, message}
32+
end
33+
34+
defp with_file_and_line(message, opts) do
35+
if (file = opts[:file]) && (line = opts[:line]) do
36+
relative = Path.relative_to_cwd(file)
37+
message = Exception.format_file_line(relative, line) <> " " <> message
38+
relative
39+
end
40+
end
41+
```
42+
43+
The refactored version is broken because the `if` block was actually returning two values, the relative path *and* the new message. Elixir v1.3 will warn on such cases, forcing both variables to be explicitly returned from `if`, `case` and other constructs. Furthermore, this change gives us the opportunity to unify the language scoping rules in future releases.
44+
45+
### Calendar types and sigils
46+
47+
Elixir v1.3 introduces the `Calendar` module as well as 4 new calendar types:
48+
49+
* `Date` - used to store dates (year, month, day) in a given calendar
50+
* `Time` - used to store time (hour, minute, second, microseconds)
51+
* `NaiveDateTime` - used to store datetimes without a timezone (year, month, day, hour, minute, second, microseconds) in a given calendar. It is called naïve because without a timezone, the datetime may not actually exist. For example, when there are daylight savings changes, a whole hour may not exist (when the clock moves forward) or a particular instant may happen twice (when the clock moves backwards)
52+
* `DateTime` - used to store datetimes with timezone (year, month, day, hour, minute, second, microsecond and time zone, with abbreviation, UTC and standard offset)
53+
54+
The current Calendar modules and its types is to provide a base for interoperatibility in the ecosystem instead of full-featured datetime API. This release includes basic functionality for building new types and converting them from and back strings.
55+
56+
Elixir v1.3 also introduces 3 new sigils related to the types above:
57+
58+
* `~D[2016-29-05]` - builds a new date
59+
* `~T[08:00:00]` and `~T[08:00:00.285]` - builds a new time (with different precisions)
60+
* `~N[2016-29-05 08:00:00]` - builds a naive date time
61+
62+
### Access selectors
63+
64+
This release introduces new accessors to make it simpler for developers to traverse nested data structures, traversing and updating data in different ways. For instance, given a user with a list of languages, here is how to deeply traverse the map and convert all language names to uppercase:
65+
66+
```iex
67+
iex> user = %{name: "john",
68+
...> languages: [%{name: "elixir", type: :functional},
69+
...> %{name: "c", type: :procedural}]}
70+
iex> update_in user, [:languages, Access.all(), :name], &String.upcase/1
71+
%{name: "john",
72+
languages: [%{name: "ELIXIR", type: :functional},
73+
%{name: "C", type: :procedural}]}
74+
```
75+
76+
You can see the new accessors in the `Access` module.
77+
78+
## Mix
79+
80+
Mix includes new tasks to improve your everyday workflow. Some of those tasks relies on many compiler improvements to know more about your code, providing static analysis to find possible bugs in your code and faster compilation cycles.
81+
82+
### Compiling n files
83+
84+
Mix no longer announces every file it compiles. Instead it outputs how many files there is to compile per compilers. Here is the output for a project like [`gettext`](https://github.com/elixir-lang/gettext):
85+
86+
```
87+
Compiling 1 file (.yrl)
88+
Compiling 1 file (.erl)
89+
Compiling 19 files (.ex)
90+
Generated gettext app
91+
```
92+
93+
In case a file is taking too long to compile, Mix will announce such, for example:
94+
95+
```
96+
Compiling lib/gettext.ex (it's taking more than 5s)
97+
```
98+
99+
The goal of these changes is to put an increased focus on the "warnings" emitted by the compiler.
100+
101+
In any case, the previous behaviour can be brought back with the `--verbose` flag and the compilation threshold for files that are taking long can be set via the `--long-compilation-threshold` option.
102+
103+
### mix xref
104+
105+
Speaking about warnings, Mix v1.3 includes a new task called `xref` that performs cross reference checks in your code. One of such checks is the ability to find calls to modules and functions that do not exist. For example, if in your library code you call `ThisModuleDoesNotExist.foo(1, 2, 3)`, `mix xref --unreachable` will be able to find such code and let you know about it.
106+
107+
Since such checks can discover possible bugs in your codebase, a new compiler called `xref` has been added to `Mix.compilers/0`, so they run by default every time you compile your code.
108+
109+
We have included other modes in `xref`, such as `mix xref --callers Foo`, to find all places in your code that a function from the module `Foo` is called. We hope other tools and text editors can leverage such features to provide useful functionality for their users.
110+
111+
### Better dependency tracking
112+
113+
Besides `xref`, Elixir v1.3 provides better module tracking generally. For example, in previous versions, if you changed a `:path` dependency, Elixir would always fully recompile the current project. In this release, we have improved the tracking algorithms such that, if you change a `:path` dependency, only the files that depend on such dependency are recompiled.
114+
115+
Such improvements do not only make compilation faster but they also make working with umbrella applications much more productive. Previously, changing a sibling application triggered a full project recompilation, now Elixir can track between sibling applications and recompile only what is needed.
116+
117+
### mix app.tree and deps.tree
118+
119+
Mix also includes both `mix app.tree` and `mix deps.tree`. The first will list all applications your current project needs to start in order to boot (i.e. the ones listed in `application/0` in your `mix.exs`) while the second will lists all of your dependencies and so on recursively.
120+
121+
Here is a quick example from [Plug](https://github.com/elixir-lang/plug):
122+
123+
```elixir
124+
$ mix app.tree
125+
plug
126+
├── elixir
127+
├── crypto
128+
├── logger
129+
│ └── elixir
130+
└── mime
131+
└── elixir
132+
```
133+
134+
### mix escript.install
135+
136+
Mix also includes `mix escript.install` and `mix escript.uninstall` tasks for managing escripts. The tasks was designed in a way to mimic the existing `mix archive` functionality except that:
137+
138+
* Archives must be used sparingly because every new archive installed affects Mix performance, as every new archive is loaded when Mix boots. Escripts solve this by being managed apart from your Elixir/Mix installed
139+
* Archives depends on the current Elixir version. Therefore, updating your Elixir version may break an archive. Fortunately, escripts bundle Elixir inside themselves, and therefore do not depend on your Elixir system version
140+
141+
Escripts will be installed at `~/.mix/escripts` which must be added to your [`PATH` environment variable](https://en.wikipedia.org/wiki/PATH_(variable)).
142+
143+
### Option parser integration
144+
145+
Elixir v1.3 includes improvements to the option parser, including `OptionParser.parse!/2` and `OptionParser.parse_head!/2` functions that will raise in case of invalid or unknown switches. Mix builds on top of this functionality to provide automatic error reporting solving a common complaint where invalid options were not reported by Mix tasks.
146+
147+
For example, invoking `mix test --unknown` in earlier Elixir versions would silently discard the `--unknown` option. Now `mix test` correctly reports such errors:
148+
149+
```
150+
$ mix test --unknown
151+
** (Mix) Could not invoke task "test": 1 error found!
152+
--unknown : Unknown option
153+
```
154+
155+
## ExUnit
156+
157+
ExUnit packs many improvements on the tooling side, better integration with external tools, as well as mechanisms to improve the readability of your tests.
158+
159+
### mix test --stale
160+
161+
ExUnit builds on top of `mix xref` to provide the `mix test --stale` functionality. When the `--stale` flag is given, `mix` will only run the tests that may have changed since the last time you ran `mix test --stale`. For example:
162+
163+
* If you saved a test file on disk, Mix will run that file and ignore the ones that have not changed
164+
* If you changed a library file, for example, `lib/foo.ex` that defines `Foo`, any test that invokes a function in `Foo` directly or indirectly will also run
165+
* If you modify your `mix.exs` or your `test/test_helper.exs`, Mix will run the whole test suite
166+
167+
Such feature provide a great workflow for developers, allowing them to effortlessly focus on parts of the codebase when developing new features.
168+
169+
### Diffing
170+
171+
ExUnit will now include diff-ing output every time a developer asserts `assert left == right` in their tests. For example, the assertion:
172+
173+
```elixir
174+
assert "fox jumps over the lazy dog" ==
175+
"brown fox jumps over the dog"
176+
```
177+
178+
will fail with
179+
180+
```elixir
181+
1) test compare (Difference)
182+
lib/ex_unit/examples/difference.exs:10
183+
Assertion with == failed
184+
lhs: "fox jumps over the lazy dog"
185+
rhs: "brown fox jumps over the dog"
186+
stacktrace:
187+
lib/ex_unit/examples/difference.exs:11: (test)
188+
```
189+
190+
in a way that "lazy" in "lhs" will be shown in red to denote it has been removed from "rhs" while "brown" in "rhs" will be shown in green to denote it has been added to the "rhs".
191+
192+
When working with large or nested data structures, the diffing algorithm makes it fast and convenient to spot the actual differences in the asserted values.
193+
194+
### Test types
195+
196+
ExUnit v1.3 includes the ability to register different test types. This means libraries like QuickCheck can now provide functionality such as:
197+
198+
```elixir
199+
defmodule StringTest do
200+
use ExUnit.Case, async: true
201+
202+
property "starts_with?" do
203+
forall({s1, s2} <- {utf8, utf8}) do
204+
String.starts_with?(s1 <> s2, s1)
205+
end
206+
end
207+
end
208+
```
209+
210+
At the end of the run, ExUnit will also report it as a property, including both the amount of tests and properties:
211+
212+
```
213+
1 property, 10 tests, 0 failures
214+
```
215+
216+
### Named setups and bundles
217+
218+
Finally, ExUnit v1.3 includes the ability to organize tests together in bundles:
219+
220+
```elixir
221+
defmodule StringTest do
222+
use ExUnit.Case, async: true
223+
224+
bundle "String.capitalize/2" do
225+
test "uppercases the first grapheme" do
226+
assert "T" <> _ = String.capitalize("test")
227+
end
228+
229+
test "lowercases the remaining graphemes" do
230+
assert "Test" = String.capitalize("TEST")
231+
end
232+
end
233+
end
234+
```
235+
236+
Every test inside a bundle will be tagged with the bundle name. This allows developers to run tests that belong to particular bundles, be them in the same file or across many files:
237+
238+
```
239+
$ mix test --only bundle:"String.capitalize/2"
240+
```
241+
242+
Note bundles cannot be nested. Instead of relying on hierarchy for composition, we want developers to build on top of named setups. For example:
243+
244+
```elixir
245+
defmodule UserManagementTest do
246+
use ExUnit.Case, async: true
247+
248+
bundle "when user is logged in and is an admin" do
249+
setup [:log_user_in, :set_type_to_admin]
250+
251+
test ...
252+
end
253+
254+
bundle "when user is logged in and is a manager" do
255+
setup [:log_user_in, :set_type_to_manager]
256+
257+
test ...
258+
end
259+
260+
defp log_user_in(context) do
261+
# ...
262+
end
263+
end
264+
```
265+
266+
By forbidding hierarchies in favor of named setups, it is straight-forward for the developer to glance at each bundle and know exactly the setup steps involved.
267+
3268
## v1.3.0-dev
4269

5270
### 1. Enhancements

lib/eex/lib/eex/tokenizer.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ defmodule EEx.Tokenizer do
143143
Enum.find_index tokens, fn
144144
{:fn_paren, _} -> true
145145
{:fn, _} -> true
146-
_ -> false
146+
_ -> false
147147
end
148148
end
149149

lib/elixir/lib/calendar.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ defmodule Calendar do
33
This module defines the responsibilities for working with
44
calendars, dates, times and datetimes in Elixir.
55
6-
Currently it defines types but may define the calendar
7-
behaviour in future versions. For the actual date, time
8-
and datetime structures, see `Date`, `Time`, `NaiveDateTime`
9-
and `DateTime`.
6+
Currently it defines types and the minimal implementation
7+
for a calendar behaviour in Elixir. The goal of the Calendar
8+
features in Elixir is to provide a base for interoperatibility
9+
instead of full-featured datetime API.
10+
11+
For the actual date, time and datetime structures, see `Date`,
12+
`Time`, `NaiveDateTime` and `DateTime`.
1013
1114
Note the year, month, day, etc designations are over specified
1215
(i.e. an integer instead of 1..12 for months) because different

lib/elixir/lib/exception.ex

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,6 @@ defmodule Exception do
3131
@callback exception(term) :: t
3232
@callback message(t) :: String.t
3333

34-
@doc false
35-
# TODO: Remove this function and use Kernel.struct! directly
36-
def __struct__(struct, args) do
37-
{valid, invalid} = Enum.partition(args, fn {k, _} -> Map.has_key?(struct, k) end)
38-
39-
case invalid do
40-
[] ->
41-
:ok
42-
_ ->
43-
IO.warn "the following fields are unknown when raising " <>
44-
"#{inspect struct.__struct__}: #{inspect invalid}. " <>
45-
"Please make sure to only give known fields when raising " <>
46-
"or redefine #{inspect struct.__struct__}.exception/1 to " <>
47-
"discard unknown fields. Future Elixir versions will raise on " <>
48-
"unknown fields given to raise/2"
49-
end
50-
51-
Kernel.struct!(struct, valid)
52-
end
53-
5434
@doc """
5535
Returns `true` if the given `term` is an exception.
5636
"""

lib/elixir/lib/kernel.ex

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3543,8 +3543,24 @@ defmodule Kernel do
35433543
end
35443544

35453545
@spec exception(Keyword.t) :: Exception.t
3546+
# TODO: Only call Kernel.struct! by Elixir v1.4
35463547
def exception(args) when is_list(args) do
3547-
Exception.__struct__(__struct__(), args)
3548+
struct = __struct__()
3549+
{valid, invalid} = Enum.partition(args, fn {k, _} -> Map.has_key?(struct, k) end)
3550+
3551+
case invalid do
3552+
[] ->
3553+
:ok
3554+
_ ->
3555+
IO.warn "the following fields are unknown when raising " <>
3556+
"#{inspect __MODULE__}: #{inspect invalid}. " <>
3557+
"Please make sure to only give known fields when raising " <>
3558+
"or redefine #{inspect __MODULE__}.exception/1 to " <>
3559+
"discard unknown fields. Future Elixir versions will raise on " <>
3560+
"unknown fields given to raise/2"
3561+
end
3562+
3563+
Kernel.struct!(struct, valid)
35483564
end
35493565

35503566
defoverridable exception: 1

0 commit comments

Comments
 (0)