Skip to content

Commit 6c44fea

Browse files
committed
Update CHANGELOG
1 parent 35eef87 commit 6c44fea

File tree

1 file changed

+91
-28
lines changed

1 file changed

+91
-28
lines changed

CHANGELOG.md

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,71 +26,134 @@ See the full CHANGELOG for more improvements.
2626

2727
## Improvements to sort-based APIs in Enum
2828

29-
`Enum.sort/1` in Elixir always sorts from lowest to highest. If you want to sort from highest to lowest, you need to call `Enum.sort/2` with a custom sorting function, such as `Enum.sort(collection, &>=/2)`, which is not immediately obvious to someone reading the code.
29+
`Enum.sort/1` in Elixir by default sorts from lowest to highest:
3030

31-
To make matters worse, comparison operators, such as `<=` and `>=`, perform structural sorting, instead of a semantic one. For example, using `>=` to sort dates descendingly won't yield the correct result. Therefore, to sort dates from more recent to oldest, one has to write `Enum.sort(dates, &(Date.compare(&1, &2) != :lt))`.
31+
```elixir
32+
iex> Enum.sort(["banana", "apple", "pineapple"])
33+
["apple", "banana", "pineapple"]
34+
```
35+
36+
If you want to sort from highest to lowest, you need to call `Enum.sort/2` with a custom sorting function, such as `Enum.sort(collection, &>=/2)`, which is not immediately obvious to someone reading the code:
37+
38+
```elixir
39+
iex> Enum.sort(["banana", "apple", "pineapple"], &>=/2)
40+
["pineapple", "banana", "apple"]
41+
```
42+
43+
Furthermore, comparison operators, such as `<=` and `>=`, perform structural sorting, instead of a semantic one. For example, using `>=` to sort dates descendingly won't yield the correct result:
44+
45+
```elixir
46+
iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]])
47+
[~D[2020-01-01], ~D[2019-12-31]]
48+
```
49+
50+
To perform proper semantic comparison for dates, one would also need to pass a custom sorting function:
51+
52+
```elixir
53+
iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]], &(Date.compare(&1, &2) != :lt))
54+
[~D[2019-12-31], ~D[2020-01-01]]
55+
```
3256

3357
Elixir v1.10 streamlines the sorting functions by introducing both `:asc` and `:desc` shortcuts:
3458

35-
Enum.sort(collection, :asc) # the default
36-
Enum.sort(collection, :desc) # in reverse
59+
```elixir
60+
iex> Enum.sort(["banana", "apple", "pineapple"], :asc)
61+
["apple", "banana", "pineapple"]
62+
iex> Enum.sort(["banana", "apple", "pineapple"], :desc)
63+
["pineapple", "banana", "apple"]
64+
```
3765

38-
Furthermore, if you want to perform semantic comparison, you can pass a module that provides the relevant comparison function. For example, to sort dates:
66+
As well as adding the possibility to pass a module to perform semantic comparisons. For example, to sort dates, one now only needs to pass the `Date` module or even `{:desc, Date}` for descending semantical sort:
3967

40-
Enum.sort(birth_dates, Date)
41-
Enum.sort(birth_dates, {:asc, Date})
42-
Enum.sort(birth_dates, {:desc, Date})
68+
```elixir
69+
iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]], Date)
70+
[~D[2019-12-31], ~D[2020-01-01]]
71+
iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]], {:desc, Date})
72+
[~D[2020-01-01], ~D[2019-12-31]]
73+
```
4374

44-
This new API has also been added to `Enum.sort_by`, `Enum.min_by`, `Enum.max_by`, and friends.
75+
These API improvements make the code more concise and readable and they have also been added to `Enum.sort_by`, `Enum.min_by`, `Enum.max_by`, and friends.
4576

4677
### Tracking of compile-time configuration
4778

48-
All applications in Elixir come with an application environment. This environment is a key-value store that allows us to configure said application. While reading the application environment at runtime is the preferred approach, in some rare occasions you may want to use the application environment to configure the compilation of a certain project. This is often done by calling `Application.get_env/3` outside of a function:
79+
In Elixir, we organize our code in applications. Libraries, your dependencies, and your own project are all separate applications. All applications in Elixir also come with an application environment.
4980

50-
defmodule MyApp.DBClient do
51-
@db_host Application.get_env(:my_app, :db_host, "db.local")
81+
The application environment is a key-value store that allows us to configure said application. While reading the application environment at runtime is the preferred approach, in some rare occasions you may want to use the application environment to configure the compilation of a certain project. This is often done by calling `Application.get_env/3` outside of a function:
5282

53-
def start_link() do
54-
SomeLib.DBClient.start_link(host: @db_host)
55-
end
56-
end
83+
```elixir
84+
defmodule MyApp.DBClient do
85+
@db_host Application.get_env(:my_app, :db_host, "db.local")
86+
def start_link() do
87+
SomeLib.DBClient.start_link(host: @db_host)
88+
end
89+
end
90+
```
5791

5892
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:
5993

6094
config :my_app, :db_host, "db.production"
6195

62-
The new value will have no effect as the code was compiled to connect to "db.local", which is mostly likely unavailable in the production environment.
96+
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".
97+
98+
Of course, the obvious solution to this mismatch is to not read the application environment at compilation time in the first place, and instead move the code to inside a function:
6399

64-
For those reasons, reading the application environment at runtime should be the first choice. However, if you really have to read the application environment during compilation, Elixir v1.10 introduces a `Application.compile_env/3` function:
100+
```elixir
101+
defmodule MyApp.DBClient do
102+
def start_link() do
103+
SomeLib.DBClient.start_link(host: db_host())
104+
end
105+
defp db_host() do
106+
Application.get_env(:my_app, :db_host, "db.local")
107+
end
108+
end
109+
```
65110

66-
@db_host Application.compile_env(:my_app, :db_host, "db.local")
111+
While this is the preferred approach, there are still two scenarios we need to address:
67112

68-
By using `compile_env/3`, Elixir will store the values used during compilation and compare the compilation values with the runtime values whenever your system starts, raising an error in case they differ. This helps developers ensure they are running their production systems with the configuration they intend to.
113+
1. Not everyone may be aware of this pitfall, so they will mistakenly read the application environemnt at compile-time, until they are bitten by this behaviour
114+
115+
2. In rare occasions, you trully need to read the application environment at compile-time, and you want to be warned when you try to configure at runtime something that is valid only at compilation time
116+
117+
Elixir v1.10 aims to solve these two scenarios by introducing a `Application.compile_env/3` function. For example, to read the value at compile time, you can now do:
118+
119+
```elixir
120+
@db_host Application.compile_env(:my_app, :db_host, "db.local")
121+
```
122+
123+
By using `compile_env/3`, Elixir will store the values used during compilation and compare them with the runtime values whenever your system starts, raising an error in case they differ. This helps developers ensure they are running their production systems with the configuration they intend to.
124+
125+
In future versions, we will deprecate the use `Application.get_env` at compile-time with a clear message pointing users to configuration best practices, effectively addressing the scenario where users read from the application environment at compile time unaware of its pitfalls.
69126

70127
### Compiler tracing
71128

72129
This release brings enhancements to the Elixir compiler and adds new capabilities for developers to listen to compilation events.
73130

74-
In previous Elixir releases, Elixir would compile a database of cross references between modules (such as function calls, references, structs, etc) for each project. Although developers could traverse this database, they often requested more events or more information to be made available.
131+
In previous Elixir versions, Elixir would compile a database of cross references between modules (such as function calls, references, structs, etc) for each project in order to perform all kinds of checks, such as deprecations and undefined functions.
75132

76-
In Elixir v1.10, we have replaced this database by compiler tracing. This means that developers can now directly listen to events emitted by the compiler to store and collect all the information they need (and only the information they need).
133+
Although this database was not public, developers would still use it to run their own checks against their projects. With time, developers would request more data to be included in the database, which was problematic as Elixir itself did not have a use for the additional data, and the database was not meant to be used externally in the first place.
77134

78-
Elixir itself is already using the new compiler tracing to provide new functionality. In particular, the compiler now checks for undefined function warnings more consistently. In previous versions, we would emit undefined function warnings only for files in `lib`, skipping test files and scripts.
135+
In Elixir v1.10, we have addressed these problems by introducing compiler tracing. The compiler tracing allows developers to listen to events as they are emitted by the compiler, so they can store all of the information they need - and only the information they need.
79136

80-
Furthermore, in Elixir v1.10 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:
137+
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:
81138

82139
@compile {:no_warn_undefined, OptionalDependency}
83140
defdelegate my_function_call(arg), to: OptionalDependency
84141

85-
Finally, as consequence of these improvements, some functionality related to `xref` (our previous database), has been deprecated in favor of the new compiler tracing.
142+
Previously, this information had to be added to the overall project configuration, which was far away from where the optional call effectively happened.
86143

87144
### Other enhancements
88145

89-
The calendar data types got many improvements, such as sigil support for third-party calendars, as well as the additions of `DateTime.now!/2`, `DateTime.shift_zone!/3`, and `NaiveDateTime.local_now/0`.
146+
Elixir's calendar data types got many improvements, such as sigil support for third-party calendars, as well as the additions of `DateTime.now!/2`, `DateTime.shift_zone!/3`, and `NaiveDateTime.local_now/0`.
147+
148+
There are many improvements related to Elixir's AST in this release too. First of all, `Code.string_to_quoted/2` has two new options, `:token_metadata` and `:literal_encoder`, that give more control over Elixir's parser. This information was already available to the Elixir code formatter and has now been made public. We have also extensively documented all of Elixir's AST metadata. These changes alongside compiler tracing means static analyzers and IDE integrations have a better foundation to analyze the source code.
149+
150+
ExUnit, our test framework, ships two small but important improvements: `ExUnit.CaptureIO` can now be used by tests that run concurrently and we have added "pattern-matching diffing". To understand the last feature, take this code:
90151

91-
There are many improvements related to the Elixir AST in this release too. First of all, `Code.string_to_quoted/2` has two new options, `:token_metadata` and `:literal_encoder`, that give more control over Elixir's parser. This information has already been available to the Elixir formatter for a couple versions and has now been made public. Furthermore, all public metadata entries in the AST nodes have been extensively documented. These changes alongside the compiler improvements from previous section means tools like Credo and Boundary now have a better foundation to analyze the source code.
152+
```elixir
153+
assert %{"status" => 200, "body" => %{"key" => "foo"}} = json_payload
154+
```
92155

93-
Finally, ExUnit comes with two small but important improvements: `ExUnit.CaptureIO` can now be used in tests that run asynchronously and we have added "data-structure diffing" when performing assertions with pattern matching. So now, whenever an assertion such `assert %{field: value} = expression()` fails, ExUnit will show both left-hand and right-hand sides, highlighting the parts that did not match in red.
156+
Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the `"body"` did not have value of `"foo"`. In previous Elixir versions, if the assertion failed, Elixir would print the right side and let you up to your own devices to figure out what went wrong. In Elixir v1.10, we diff the data structure against the pattern so you can see exactly which parts of the data matched the pattern and which ones did not. Note ExUnit already performed diffing when comparing data types, this new version adds diffing when matching data agaainst a pattern.
94157

95158
## v1.10.0-rc.0 (2020-01-07)
96159

0 commit comments

Comments
 (0)