Skip to content

Conversation

wojtekmach
Copy link
Member

This is a proof-of-concept for adding module-based sigils to Elixir. Before improving implementation and writing docs and tests I'd like to see if this could be added to Elixir. Hope the PR is helpful to test different possible implementations of this idea.

This topic was mentioned a few times over the years, most recently on: https://groups.google.com/forum/#!topic/elixir-lang-core/C7-QgKKu1Mw. In that topic I mentioned a few possible use cases for the sigil that are perhaps worth repeating:

URI[https://elixir-lang.org]
Decimal[1]
Complex[0, 1]
Ratio[1, 3]
MapSet[1, 2, 3] # let's ignore this for now
Money[100 USD]
Date.Range[2019-01-01/12-31]

Looking forward to feedback about proposal as well as the implementation.


expand({'module_sigil', Meta, [Module, String, Modifiers]}, E) ->
Alias = {'__aliases__', [{alias, false}], [Module]},
RE = E#{requires := ordsets:add_element(Module, ?key(E, requires))},
Copy link
Member Author

@wojtekmach wojtekmach Dec 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the consequence of this is:

iex(1)> __ENV__.requires
[Application, IEx.Helpers, Kernel, Kernel.Typespec]
iex(2)> ~URI[https://elixir-lang.org]
iex(3)> __ENV__.requires
[Application, IEx.Helpers, Kernel, Kernel.Typespec, URI]

which might be surprising, e.g. we now don't need to explicitly require the module to use it's macros, we just need to use the sigil. At the same time, without automatically requiring the module, the feature feels much less ergonomic to use. (But fwiw it would match the behaviour of "regular" sigils, if it's a macro that module needs to be required.)

expand({'<<>>', Meta, Args}, E) ->
elixir_bitstring:expand(Meta, Args, E, false);

expand({'module_sigil', Meta, [Module, String, Modifiers]}, E) ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call the special form __sigil__ and it would have to be documented as such.

check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.PID',
Reserved = ['Elixir.Any', 'Elixir.BitString',
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would have to implement it for refs too.

@josevalim
Copy link
Member

Thank you @wojtekmach for the proof of concept. Unfortunately, we are not fully convinced on the usefulness of the feature. It has the following limitations:

  1. Ambiguity with single letter aliases. For example, if I do alias Foo, as: F and then ~F is a regular sigil or a module sigil
  2. It is able of handling only string values and not actual values, such as MapSet[...], which would require an Elixir parser, custom escaping rules, etc

So given the pitfalls and the fact it does not address all cases, I am not convinced we should move forward. Although it is indeed the best proposal we have received so far and the implementation looks quite solid so far!

@josevalim josevalim closed this Dec 10, 2019
@wojtekmach
Copy link
Member Author

wojtekmach commented Dec 10, 2019

Ambiguity with single letter aliases. For example, if I do alias Foo, as: F and then ~F is a regular sigil or a module sigil

Currently ~F would be a regular sigil but the ambiguity is definitely there and perhaps a warning could help. Although not sure if we should warn on module sigil usage, or alias, or either one of them, (depending on the order of expressions) which does not sound great.

It is able of handling only string values and not actual values, such as MapSet[...], which would require an Elixir parser, custom escaping rules, etc

I think that is actually a feature, that is regular sigils and module sigils always accept a binary and a modifier charlist, so they have the same features and limitations (not actual values, no interpolation). My only concern would be that if today we allow to do ~MapSet[1, 2] (which we wouldn't because as you mentioned we wouldn't want to parse the inner string), if there's ever a similar mechanism like we could actually do MapSet[1, 2] (note: no sigil, let say it's a new mechanism for some module-based collections), then well, we now have 2 mechanisms that look almost exactly the same but work quite differently, which is not great either. But yeah, the point I'm trying to get across is module sigil is an extension to regular sigils so they behave very similarly.

All that being said, I think the criticism is fair and if you'd ever want to move forward with this or something similar, let me know. Thanks for looking into it!

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

Successfully merging this pull request may close these issues.

2 participants