Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Proposal: iex> h {expr}

63 views
Skip to first unread message

Wojtek Mach

unread,
Jan 22, 2025, 4:37:42 AMJan 22
to elixir-l...@googlegroups.com
The other day I was talking with a friend and I said Elixir basically has no keywords, relatively little syntax, and we can look up a lot of things like `iex> h if`. Most of the things we'd look up are `fun` and `mod.fun` but others, especially operators, you need to know how to write it for `h` to accept it. For example:

```
iex> h %{}

iex> h Kernel.*
iex> h Kernel.&&

iex> h Kernel.SpecialForms.&
iex> h Kernel.SpecialForms.::
```

Today we have this:

```
iex> h 1 + 2
The "h" helper expects a Module, Module.fun or Module.fun/arity, got: 3
If instead of accessing documentation you would like more information about a value or about the result of an expression, use the "i" helper instead
```

I'd like to propose allowing arbitrary expressions in `h` so that we teach people to copy-paste whatever they are looking at to learn more:

```
iex> h 1 + 2
The "h" helper expects a Module, Module.fun or Module.fun/arity, got: 1 + 2

To learn about + operator, do:

    iex> h Kernel.+
```

With this I believe it would be easy to learn about otherwise kind of hard to discover things:

```elixir
iex> h %{map | k: v}  # info on map update syntax
iex> h ~D[2025-01-01] # info on sigil_D
iex> h @compile       # info on module attributes and/or that particular one
iex> h &is_atom/1     # info on captures
iex> h a..b
iex> h _
```

Another idea, and this is probably a bridge to far, is special casing in the IEx evaluator `h` so that:

```
iex> h &
```

just works (as opposed to wait until the expression is completed).

I believe the implementation should be fairly straightforward:

```
diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex
index 8964954aa..673f3abca 100644
--- a/lib/iex/lib/iex/helpers.ex
+++ b/lib/iex/lib/iex/helpers.ex
@@ -360,6 +360,22 @@ def h() do
       iex> h(Enum.all?)

   """
+  defmacro h({:+, _, [_, _]} = ast) do
+    puts_error("""
+    The "h" helper expects a Module, Module.fun or Module.fun/arity, got: #{Macro.to_string(ast)}
+
+    To learn about + operator, do:
+
+        iex> h Kernel.+
+    """)
+
+    dont_display_result()
+  end
+
+  defp puts_error(string) do
+    IO.puts(IEx.color(:eval_error, string))
+  end
+
   defmacro h(term) do
     quote do
       IEx.Introspection.h(unquote(IEx.Introspection.decompose(term, __CALLER__)))
```

José Valim

unread,
Jan 22, 2025, 4:40:02 AMJan 22
to elixir-l...@googlegroups.com
Yes, we should either improve the current error message or just be smart about it. Macro.operator? can be used to check if something is an operator, so this should be relatively easy to make trivial.


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/elixir-lang-core/D569DB44-82DA-415B-9DA9-36509C179497%40wojtekmach.pl.

Bruce Tate

unread,
Jan 22, 2025, 8:57:57 AMJan 22
to elixir-l...@googlegroups.com
This feature would be most useful. 

Maybe this is too much context or too tangential, but I typically train people to use IEx helpers like h and info to find the functions they need. I also often have them import IEx helpers into Livebook so they can see protocol information, which also provides clues about how to find modules. For example: 

iex(10)> [1, 2, 3]

[1, 2, 3]

iex(11)> i

Term

  [1, 2, 3]

Data type

  List

Reference modules

  List

Implemented protocols

  Collectable, Enumerable, IEx.Info, Inspect, List.Chars, String.Chars

iex(12)> h Enumerable


...

iex(13)> h Enum

...


This approach works well since Elixir is mostly organized around types. 

Having this extra layer to understand operators would make this approach that much more useful. 

Is it interesting to also show where functions come from? e.g. 


                       defmacro sigil_w(term, modifiers)                        


Handles the sigil ~w for list of words.


It returns a list of "words" split by whitespace. Character unescaping and

interpolation happens for each word.


## Modifiers


  s: words in the list are strings (default)

  a: words in the list are atoms

  c: words in the list are charlists


## Examples


    iex> ~w(foo #{:bar} baz)

    ["foo", "bar", "baz"]

    

    iex> ~w(foo #{" bar baz "})

    ["foo", "bar", "baz"]

    

    iex> ~w(--source test/enum_test.exs)

    ["--source", "test/enum_test.exs"]

    

    iex> ~w(foo bar baz)a

    [:foo, :bar, :baz]

    

    iex> ~w(foo bar baz)c

    [~c"foo", ~c"bar", ~c"baz"]




    iex> Kernel.sigil_w(<<"foo bar baz">>, 'c')

    [~c"foo", ~c"bar", ~c"baz"]



should provide a hint that it's imported from Kernel. (the last line)


Note that normally this isn't a problem. After importing is_odd from integer: 

h is_odd


                            defmacro is_odd(integer)                            


guard: true


Determines if integer is odd.


Returns true if the given integer is an odd number, otherwise it returns false.


Allowed in guard clauses.


## Examples


    iex> Integer.is_odd(5)

    true

    

    iex> Integer.is_odd(6)

    false

    

    iex> Integer.is_odd(-5)

    true

    

    iex> Integer.is_odd(0)

    false



-bt
Reply all
Reply to author
Forward
0 new messages