Keyword list weird behaviour

Hi, a relatively new user of Elixir here.

So, as far as i realised, keyword lists can be simply inserted into the function as the last argument without using the brackets syntax, which i tried to do today, when working with some gen_tcp in my GenServer. FYI i run gentoo and am at v1.17.2.

When ran in the usual way, i.e. ;gen_tcp.connect(~c"example.com",80,[:binary, packet: 0]) everything is fine, but as soon as i tru to use the syntactic sugar provided to me by the language, it fails.
From the error message i have read, apparently the connect/4 is called and {packet: 0} gets passed as a timeout parameter.

So the question is… Is it a bug? Am i not understanding the syntactic sugar mechanism well enough?

Thank you for the help in advance

PS: Updated the question title as the problem was moslty in my understanding of the KeywordList syntax sugar :saluting_face:

It sounds like you may be running into this error. If I’m off track, let me know

iex(3)> :gen_tcp.connect(~c"example.com", 80, :binary, packet: 0)
** (ArgumentError) errors were found at the given arguments:

  * 1st argument: not an integer

    :erlang.start_timer([packet: 0], #PID<0.108.0>, :inet)
    (kernel 10.0.1) inet.erl:3914: :inet.start_timer/1
    (kernel 10.0.1) gen_tcp.erl:572: :gen_tcp.connect/4
    iex:3: (file)

The expressions below are not equivalent:

# correct
:gen_tcp.connect(~c"example.com",80, [:binary, packet: 0])

# error
:gen_tcp.connect(~c"example.com", 80, :binary, packet: 0)

The syntactic sugar for Keyword-as-final-argument is that if it’s the last argument of a function call, you can omit the square brackets. The error case is actually equivalent to.

# error
:gen_tcp.connect(~c"example.com", 80, :binary, [packet: 0])

You’ve run into a situation where Elixir allows creation of an Erlang proplist via the Keyword literal syntax, but is not compatible with the Keyword-as-final-argument syntax.

You should use one of these forms

# works using a supported Keyword literal syntax
:gen_tcp.connect(~c"example.com", 80, [:binary, packet: 0])

# equivalent and closer to the Erlang style
:gen_tcp.connect(~c"example.com", 80, [:binary, {:packet, 0}])

Personally I would choose the second form since we are calling an Erlang function that does not support Keyword.t() directly, but this is a style choice.

2 Likes

The syntactic sugar only works for the last argument when it is to contain a list of key/value tuples. Your :binary is just an atom.

3 Likes

To clarify this and @jstimps’ answer, it’s not just the last function arg that this works for but also the last members of lists and tuples.

[:binary, packet: 0] # this what you're seeing
{:binary, packet: 0} # also works, this is the same as:
{:binary, [{:packet, 0}]}

PS, welcome to Elixir Forum!

1 Like

Ah, now i see…
Because it would not be able to actually tell in that case if :binary was just an atom-type parameter or a start of the keyword list.
What made it even more confusing for me was the fact that it worked and gave me a keyword list ordering error if i switched the 2 parameters (which i expected lmao)

But thanks a lot

1 Like

Thanks for the welcome lmao