Any chance we get format for string interpolation?

sth like string — Common string operations — Python 3.10.4 documentation?

I really fear erlang’s ~F.P.PadModC (Erlang -- io)

1 Like

What is there to fear? It is almost the same as Python instead of % you use ~. If anything we will get wrapper over io_lib:format/2 as it is handy for report_cb in logger handlers.

2 Likes

For example I just had to format a CSS compatible hex color. (#RRGGBB, eg (0,0,255) -> #0000FF)
Could not figure that out from the documentation.

Also it is actually two things.

  • We got io_lib:format which I do not like (maybe I’m just too stupid, but not all coders geniuses, and Elixir is mostly a very easy language, so I might be spoiled also)
  • We got no format embedded in interpolations which is just very handy

I actually agree with you @Sebb.

I generally like the Erlang documentation and I no problems in translating that into Elixir, but I had a really hard time with the documentation for io_lib:format and the understanding of this. I believe it’s because I’ve got so used to everything else in Elixir is so intuitive and understandable that most times I just need to skim through the documentation to get back into the groove of things. Which I think is super important in a dynamic language where the language server isn’t always there to help you with introspection.

For future readers, here’s the format needed:

:io.fwrite("#~2.16.0B~2.16.0B~2.16.0B", [r, g, b])

# prints the string "#F27B04"

Values smaller than zero or larger than 255 will be replaced with **:

:io.fwrite("#~2.16.0B~2.16.0B~2.16.0B", [-r, g, b])

# prints "#**7B04"

:io.fwrite("#~2.16.0B~2.16.0B~2.16.0B", [10*r, g, b])

# prints "#**7B04"

Breakdown in detail:

~2.16.0B
  • 2 is the field width. Without it, values less than 16 will only output a single character
  • 16 is called “precision” in the API, but for the B control sequence it’s the base to use
  • 0 is the padding character, because we want 4 to print as 04 not <space>4
5 Likes

I admit, with some patience the docs are understandable.

One thing that confused me are the ., but I now understand, that they are only needed when one of P, Pad,or Mod right to it is used, here the dot right to 16 is wrong, only needed when Pad is used.

iex(23)> :io.fwrite("~.16.B", [15])
** (ArgumentError) errors were found at the given arguments:

  * 1st argument: format string invalid (truncated)

Interpolation

So what about

iex> "##{r:2.16.0B}{g:2.16.0B}{b:2.16.0B}"

I think that would be useful.

IMO that’s too big of a change - one of the nice things about Elixir is the regularity of the syntax, and that would make #{} magical in a way that’s not available elsewhere. A more “Elixir-y” solution would be to make the conversion explicit:

"##{to_hex(r)}#{to_hex(g)}#{to_hex(b)}"
...
defp to_hex(v), do: :io_lib.format("~2.16.0B", [v])

Some other gotchas that come to mind:

  • making runtime-determined values work (* in the various positions) means more tricky syntax
  • what would "#{some_calculation():i}" do? “Ignore one thing from the input” makes a lot more sense when the format and the data are fully separated (as in :io)
  • the term-handling codes don’t output Elixir-shaped strings:
:io.fwrite("~w", [DateTime.utc_now()])

prints

#{'__struct__' => 'Elixir.DateTime',calendar => 'Elixir.Calendar.ISO',day => 1,hour => 22,microsecond => {937508,6},minute => 47,month => 5,second => 18,std_offset => 0,time_zone => <<69,116,99,47,85,84,67>>,utc_offset => 0,year => 2022,zone_abbr => <<85,84,67>>}
3 Likes

That topic gave me an idea for a library. I will try to draft something this weekend.

4 Likes

That’s true. But maybe inside a sigil?

Really? Does not seem too hard, but I dont know too much about parsers.

Just take the output and pass it to the formatter?

# python 3.6
>>> def foo(): return 255
... 
>>> f"#{foo():0{2}x}"
'#ff'

may raise

# python 3.6
>>> def foo(): return None
... 
>>> f"#{foo():0{2}x}"
TypeError: unsupported format string passed to NoneType.__format__

I do not understand what you mean.

Yes, that’s not nice in this case. So we would be responsible to call sth like to_string().

:star_struck: