Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for string interpolation #9

Open
gridbox opened this issue Sep 7, 2017 · 1 comment
Open

Support for string interpolation #9

gridbox opened this issue Sep 7, 2017 · 1 comment

Comments

@gridbox
Copy link

gridbox commented Sep 7, 2017

I have a use case in which I have a list of fields names that I would like to interpolate into the ~m and ~M sigils.

Current functionality

iex(9)> ~m{#{Enum.join(["hello","world"], ",")}} = %{"hello" => "hello", "world" => "world"}
** (FunctionClauseError) no function clause matching in Kernel.=~/2
    (elixir) lib/kernel.ex:1580: Kernel.=~({:::, [line: 9], [{{:., [line: 9], [Kernel, :to_string]}, [line: 9], [{{:., [line: 9], [{:__aliases__, [counter: 0, line: 9], [:Enum]}, :join]}, [line: 9], [["hello", "world"], ","]}]}, {:binary, [line: 9], nil}]}, ~r/\A\s*[a-zA-Z_]\w*\s*\|/)
             lib/shorter_maps.ex:106: ShorterMaps.get_old_map/1
             lib/shorter_maps.ex:78: ShorterMaps.do_sigil_m/3
             expanding macro: ShorterMaps.sigil_m/2
             iex:9: (file)
iex(9)> ~M{#{Enum.join(["hello","world"], ",")}} = %{hello: "hello", world: "world"}        
** (SyntaxError) iex:9: unexpected token: }

Desired functionality

iex(9)> ~m{#{Enum.join(["hello","world"], ",")}} = %{"hello" => "hello", "world" => "world"}
%{"hello" => "hello", "world" => "world"}
iex(9)> ~M{#{Enum.join(["hello","world"], ",")}} = %{hello: "hello", world: "world"}        
%{hello: "hello", world: "world"}
@meyercm
Copy link
Owner

meyercm commented Sep 8, 2017

So there are three problems in our way.

First, your desired syntax of ~m{ #{...} } will probably never work, as I was unable to make a good enough argument for nested braces in this Elixir issue. Currently, the sigil will be terminated with the first }, which will be found at the end of your interpolation (This is the actual error in your example, but it still wouldn't work, see below). The easy work around is to just use ~m( #{...} ). The harder solution is to succeed where I failed, and convince @josevalim that embedding braces in sigils is worthwhile.

Second: ShortMaps, the project we started with, didn't support interpolation, and when I copied the codebase, I never thought to add it. This is probably the easiest of our three hurdles.

Third, and most difficult to circumnavigate. Consider this simple module:

defmodule TestSigils do
  defmacro sigil_z(args, opts), do: Macro.escape(args)
  defmacro sigil_Z(args, opts), do: Macro.escape(args)
end
iex> import TestSigils
iex> ~Z(#{:a})
{:<<>>, [line: 2], ["\#{:a}"]}
iex> ~z(#{:a})
{:<<>>, [line: 3],
 [{:::, [line: 3],
   [{{:., [line: 3], [Kernel, :to_string]}, [line: 3], [:a]},
    {:binary, [line: 3], nil}]}]}

In case that isn't clear; ~Z is actually receiving different parameters from the compiler. This is a surprise to me, but careful re-reading of the Elixir Sigils documentation proves that is the intended behavior, even if it seems like something that should be left up to the sigil (as I thought it was). I'm not sure I see a great way around this problem while continuing to use Sigils... the best I have is to use the ~m(...)a construction, which I generally disdain.

In the destructure library, the author bypasses sigils, and writes a macro d() to accomplish the map expansions. I don't think I like the paren-omissions and the % inclusion that he uses, but we could add a m() and M() macro to ShorterMaps to accomplish the string interpolation goal, as well as gain back syntax highlighting.

Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants