Elixir v1.6.0-rc.0 released

Hi everyone,

We are glad to announce that the first release candidate for Elixir v1.6.0 is out.

Check out the CHANGELOG and give the release candidate a try. Since this is a pre-release, you may not find it in package manager, so you will have to use the precompiled packages, a version manager, or compile from source.

Happy coding!

38 Likes

@josevalim: Formatter, Dynamic Supervisor, helper for guards, new attributes and more - it’s a really big list of changes that will be really useful for lots of projects!
I definitely need to test this RC soon.
Keep going on! :heart:

3 Likes

@josevalim I ran mix test on our project with around 400 unit tests and only two failed, both with easy fixes. Great stuff.

The formatter is priceless as well and resulted is much easier to read code. Definitely excited for this release.

Do you know if 1.6 will come with any performance enhancements not mentioned in the changelog?

1 Like

Can you please report those? Unless you were relying on a bug, everything should just work™.

We always do small changes that improve performance but I don’t believe we have anything that would be easily noticeable.

1 Like

Do you prefer I open an issue on the Elixir github repo or just paste them here?

1 Like

@josevalim: I’m trying defguard now

My current version

Here is my version of guard for rgb(a) check:

defmodule Example do
  defguard is_rgb(rgb)
           when is_tuple(rgb) and elem(rgb, 0) in 0..255 and elem(rgb, 1) in 0..255 and
                  elem(rgb, 2) in 0..255 and
                  (tuple_size(rgb) == 3 or
                     (tuple_size(rgb) == 4 and elem(rgb, 3) >= 0 and elem(rgb, 3) <= 1))

  def sample(rgb) when is_rgb(rgb), do: :ok
  # Let's do not remove comment line for now
  # def sample(_rgb), do: :error
end

Extra

With call like:

Example.sample(5)

as expected returns error:

** (FunctionClauseError) no function clause matching in Example.sample/1    
     
    The following arguments were given to Example.sample/1:
    
        # 1
        5
    
    iex:8: Example.sample/1

but … is it possible to describe which guard part fails like in normal function guards?

My question

Is it possible to write it somehow nicer? I would like to separate:
a) rgb check (in 0..255) of all elements
b) rgba check

I would like to have it looks like:

# Note: this example is only to show what I want to achieve in much more cleaner - not working way
defmodule Example do
  # strict 3-element tuple check
  defguard is_rgb_strict({red, green, blue}) when red in 0..255 and green in 0..255 and blue in 0..255
  # alpha check
  defguard is_css_alpha(alpha) when alpha >= 0 and alpha <= 1

  # rgba
  defguard is_rgba({red, green, blue, alpha}) when is_rgb_strict({red, green, blue}) and is_css_alpha(alpha)
  # 3-element tuple guard for is_rgb
  defguard is_rgb(rgb = {red, green, blue}) when is_rgb_strict(rgb)
  # 4-element tuple guard for is_rgb
  defguard is_rgb(rgba) when is_rgba(rgba)
end

Any ideas?
Note: Of course I know that defguard does not work like I invented it and I don’t ask to change it. I just want to ask for better (if any) version than my original.

1 Like

Unfortunately can’t reproduce this in a fresh clone, but I’m getting

** (FunctionClauseError) no function clause matching in IO.chardata_to_string/1    
    
    The following arguments were given to IO.chardata_to_string/1:
    
        # 1
        {"src/absinthe_parser.erl", []}
    
    (elixir) lib/io.ex:461: IO.chardata_to_string/1
    (elixir) lib/file.ex:968: File.rm/1
    (elixir) lib/enum.ex:737: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:737: Enum.each/2
    (mix) lib/mix/compilers/erlang.ex:165: Mix.Compilers.Erlang.clean/1
    (mix) lib/mix/tasks/clean.ex:30: anonymous fn/2 in Mix.Tasks.Clean.run/1
    (elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
    (mix) lib/mix/tasks/clean.ex:27: Mix.Tasks.Clean.run/1

after the upgrade. Did the usual rm -rf _build deps thing.

Here is great!

I have tried it locally and it did show which guards failed, albeit the expanded version of is_rgb.

I could reproduce it on gettext. Fixed on master and v1.6 branches, thanks!

2 Likes

Congrats on the RC! I’ve played with the new formatter and have a couple of questions about what it’s doing.

-  @spec setup_customer(TwitterApi.oauth_creds, map) :: %Account.Customer{}
+  @spec setup_customer(TwitterApi.oauth_creds(), map) :: %Account.Customer{}

Why does oauth_creds get () added when map doesn’t?

Likely related to above:

   def sync_finished(customer) do
-    Logger.info "[SYNC] #{customer.id} (#{customer.twitter_id}) finished"
-    GenServer.cast({:via, Registry, {Account.ManagerRegistry, customer.twitter_id}}, :sync_finished)
+    Logger.info("[SYNC] #{customer.id} (#{customer.twitter_id}) finished")
+
+    GenServer.cast(
+      {:via, Registry, {Account.ManagerRegistry, customer.twitter_id}},
+      :sync_finished
+    )
   end

The .cast call change is fine by me, but I’m definitely not used to/don’t like the Logger call having to have brackets. Same with Ecto macros e.g.:

   def oauth_creds(twitter_id) do
     query =
-      from c in Customer,
-      where: c.twitter_id == ^twitter_id,
-      select: {c.oauth_token, c.oauth_token_secret}
+      from(
+        c in Customer,
+        where: c.twitter_id == ^twitter_id,
+        select: {c.oauth_token, c.oauth_token_secret}
+      )
+
     Repo.one!(query)
   end

and:

   schema "customers" do
-    has_many :relationships, Twitter.Relationship
-    has_many :users, through: [:relationships, :user]
-    has_many :lists, Twitter.List
+    has_many(:relationships, Twitter.Relationship)
+    has_many(:users, through: [:relationships, :user])
+    has_many(:lists, Twitter.List)

I realise that I could just type the non bracket version and have an editor plugin format it but I find the non bracket form easier to parse as a human (this is just my personal opinion of course). In general I’ve found the formatter:

  • Does a lot of nice things with e.g. too long lines, case statements
  • Adds brackets in a lot of places I don’t consider them useful/necessary

Thanks again to everyone involved in the new RC for all the hard work.

@josevalim: I tried it with asdf:

$ asdf current
elixir ref-v1.6.0-rc.0 (set by /home/eiji/.tool-versions)
erlang 20.2 (set by /home/eiji/.tool-versions)
rust stable (set by /home/eiji/.tool-versions)

So I compiled it from source from git tag v1.6.0-rc.0.
I tried it both in iex shell and after creating new project mix new example (of course after update).

What could I miss?

The default in the formatter is to always add parens except for:

  1. variables, such as map in your specs. If we changed it to map(), we would change the AST, which means the code before and after are no longer equivalent - and that’s a no-no for a code formatter

  2. local calls listed in your .formatter.exs under :locals_without_parens. So if you don’t want has_many and friends to have parens, you just need to list them in your .formatter.exs file. See the docs for mix format and Code.format_string!

1 Like

Everything seems to be correct. So no idea from here. Does it work for other functions just fine?

map comes from the Basic Types in https://hexdocs.pm/elixir/master/typespecs.html#types-and-their-syntax and has a () in the docs there so I’m surprised is isn’t added by the language formatter. (As an aside I don’t get why types are made to look like function calls, including the ones I create myself)

From reading the docs .formatter.exs isn’t going to help with Logger.info is it? (Haven’t tried yet).

@[quote=“josevalim, post:14, topic:11064”]
Everything seems to be correct. So no idea from here. Does it work for other functions just fine?
[/quote]
I found a problem. This does not work when:

  1. I copy module directly into iex shell and call this function
  2. Module and call to it are in same file (just call function directly after module declaration)

Both rules applies to normal guards and defguard. Is it expected?

Firstly I through that I have something broken. :smiley:

No, it won’t. Those will always have parens.

Yup! We need to locate the .beam file in disk in order to the blaming, which means it won’t be available in IEx nor in the middle of compilation.

1 Like

@josevalim: Thank you very much!
Last question (related to previous - see “My current version”):
Do you think that defguard could help me short my code?
Here is a old, but full version that uses macros:

defmodule Example do
  defmacro is_rgb_integer(integer) do
    quote do
      unquote(integer) in 0..255
    end
  end

  defmacro is_rgb_strict(rgb) do
    quote do
      Example.is_rgb_integer(elem(unquote(rgb), 0)) and
        Example.is_rgb_integer(elem(unquote(rgb), 1)) and
        Example.is_rgb_integer(elem(unquote(rgb), 2))
    end
  end

  defmacro is_css_alpha(alpha) do
    quote do
      unquote(alpha) >= 0 and unquote(alpha) <= 1
    end
  end

  defmacro is_rgba(rgba) do
    quote do
      tuple_size(unquote(rgba)) == 4 and Example.is_rgb_strict(unquote(rgba)) and
        Example.is_css_alpha(elem(unquote(rgba), 3))
    end
  end

  defmacro is_rgb(rgba) do
    quote do
      (tuple_size(unquote(rgba)) == 3 and Example.is_rgb_strict(unquote(rgba))) or
        (tuple_size(unquote(rgba)) == 4 and Example.is_rgb_strict(unquote(rgba)) and
           Example.is_css_alpha(elem(unquote(rgba), 3)))
    end
  end
end

It would be awesome if it will be possible to somehow declare guards with pattern matching in future - just like in function head.

This should work:

defmodule Example do
  defguard is_rgb_integer(integer) when integer in 0..255

  defguard is_rgb_strict(rgb)
           when is_rgb_integer(elem(rgb, 0)) and
                  is_rgb_integer(elem(rgb, 1)) and
                  is_rgb_integer(elem(rgb, 2))

  defguard is_css_alpha(alpha) when alpha >= 0 and alpha <= 1
    
  defguard is_rgba(rgba)
           when tuple_size(rgba) == 4 and is_rgb_strict(rgba) and is_css_alpha(elem(rgba, 3))

  defguard is_rgb(rgba)
           when (tuple_size(rgba) == 3 and is_rgb_strict(rgba)) or
                (tuple_size(rgba) == 4 and is_rgb_strict(rgba))
end
1 Like

Unfortunately not. :icon_sad:

iex(1)> require Example           
Example
iex(2)> Example.is_rgb({255, 255})
** (CompileError) iex:2: undefined function is_rgb_strict/1
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (elixir) expanding macro: Kernel.and/2

I tried it myself similarly too, but I can’t call defguard inside defguard.

Can you please open up a bug report? I am almost sure we have a test case for this, so I need to investigate exactly what is happening here.