Feature Request: Add to_string for Tuple

580 views
Skip to first unread message

Steve Pallen

unread,
Apr 5, 2014, 4:09:28 PM4/5/14
to elixir-l...@googlegroups.com
I hope this is the correct place to suggest feature requests...

My code is plastered with "... #{inspect something}". Furthermore, I'm forever fixing crashes because I forget to add the inspect, or don't expect a tuple, Record, etc.

Adding the following to the language to would be a big help!

defimpl String.Chars, for: Tuple do
  def to_string(value), do: inspect(value)
end

Steve

José Valim

unread,
Apr 5, 2014, 4:28:47 PM4/5/14
to elixir-l...@googlegroups.com
I hope this is the correct place to suggest feature requests...

The one and only. :)

My code is plastered with "... #{inspect something}". Furthermore, I'm forever fixing crashes because I forget to add the inspect, or don't expect a tuple, Record, etc.

to_string is meant to be implemented only for data types that can be effectively converted to strings. For example, a list can contain integers that represent a string underneath. A URL can be represented as a string too (and is often more in the string format than in the URI.Info record one).

Inspect, on the other hand, is about printing the internal structure and should not be mixed with to_string.

For this reason it doesn't make sense to implement to_string for tuples. I am aware this is a more strict definition that the one found in some languages but it is an important one to make because by calling inspect/1 you are consenting to the fact internal/structural information of your system will be printed.




José Valim
Skype: jv.ptec
Founder and Lead Developer


--
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.
For more options, visit https://groups.google.com/d/optout.

Carsten Bormann

unread,
Apr 5, 2014, 6:07:42 PM4/5/14
to elixir-l...@googlegroups.com
On 05 Apr 2014, at 22:28, José Valim <jose....@plataformatec.com.br> wrote:

> it doesn't make sense to implement to_string for tuples.

+1.

Still, Elixir might want to provide a way to write the equivalent of #{inspect foo} without having to spell out “inspect", just as #{foo} doesn’t need spelling out “to_string".

Grüße, Carsten

Dave Thomas

unread,
Apr 5, 2014, 6:57:15 PM4/5/14
to elixir-l...@googlegroups.com
Could string interpolation be special? 

val = 123
"val = #{x}" where x supports to_string is "val = 123"

val = {1,2,3}
"val = #{x}" where x does not support to_string is "val = #{1,2,3}"

(that is, fall back to inspect, and prefix a character (such as #))

Dave


Steve Pallen

unread,
Apr 5, 2014, 7:19:29 PM4/5/14
to elixir-l...@googlegroups.com
I was struggling with this, but figured out my confusion. I was assuming 'Test' == inspect('Test') which is not true. Furthermore, I did not get that iex does the 'automatic' conversion to inspect on the result of an command it it does not support to_string, as demonstrated by 

iex(6)> 'Test'
'Test'
iex(7)> 'Test' ++ [0]
[84, 101, 115, 116, 0]
iex(8)>

So, I guess my issue is with interpolation and the treatment of types that don't support to_string. It is such a source of exceptions in my code...

A couple other suggests:
  • val = {1,2} "val = #{val}" falls back to "val = #Tuple"
  • val = {1,2} "val = ~I{val}" returns "val = {1,2}"
I know this is an easy fix, I'll just add one of these to every project I create. But the fact that I have to this points to some problem. Either there is an issue with the language, or perhaps there is an issue with my programming style :)

Steve

Eric Meadows-Jönsson

unread,
Apr 5, 2014, 7:30:17 PM4/5/14
to elixir-l...@googlegroups.com
iex always inspects the result, it never calls `to_string`. `inspect` should really, only be used during development or when debugging. If you have `inspect` in production code it is likely you are doing something wrong. With this mind do we really need a special syntax for `"#{inspect expr}"`?

The falling back behaviour seems worse. If `"#{...}"` was confusing before, this will definitely make it more confusing. 


--
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.
For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson

Steve Pallen

unread,
Apr 5, 2014, 8:26:32 PM4/5/14
to elixir-l...@googlegroups.com
After thing more about this, I'm going to solve this for myself with a new sigil. I now appreciate this is a discussion around intent. My intent is to inspect so creating a new 'inspect' sigil make my code more explicit and solves the problem.

At this point, I'm not sure how opinionated I am as to whether I think this should be part of the core language. Perhaps this is a pattern unique to the type of applications I'm developing, or I have just not found a more elegant way of doing it. 

BTW, this is mainly an issue with application logging statements which I plan on being part of the production system. For example, I need to parse a binary protocol which handles variable length message organized by category, subcategory, and optional data. I've organized the code so that my parser returns a tuple to my GenFSM! (get the inference?). During the handling of these messages, I sometime log them for tracing purposes or use them in error logging. Furthermore, the message my be just an atom (if no extra parameters are present), it can my be a tuple.

Now, to pick the sigil ...

Thanks for the discussion,
Steve

Dave Thomas

unread,
Apr 5, 2014, 10:30:38 PM4/5/14
to elixir-l...@googlegroups.com

On Sat, Apr 5, 2014 at 6:30 PM, Eric Meadows-Jönsson <eric.meado...@gmail.com> wrote:

iex always inspects the result, it never calls `to_string`. `inspect` should really, only be used during development or when debugging. If you have `inspect` in production code it is likely you are doing something wrong. With this mind do we really need a special syntax for `"#{inspect expr}"`?

The falling back behaviour seems worse. If `"#{...}"` was confusing before, this will definitely make it more confusing. 

​I always find it interesting just how differently we approach coding. I’d much rather make things convenient for other developers, rather than come up with perfectly good reasons why they aren’t.

I don’t necessarily want inspect output being sent to customers, but it really is quite convenient in logs, and while developing. When I write

IO.puts "the result is #{answer}"

to a log, I​ would like to see output regardless of the type of result. The alternative is either always to use inspect or to put up with exceptions.

However, I can see the other argument—that you’d rather raise an exception than have the value {1,2,3} leak into some string interpolation.

On balance, I find it more useful for interpolation to always give a result.

Dave

Eric Meadows-Jönsson

unread,
Apr 6, 2014, 7:13:48 AM4/6/14
to elixir-l...@googlegroups.com
It's not just a matter of convenience.

It is not just about not wanting to have tuples "leak" into string interpolations. Something can be very convenient to write, but doesn't make much sense otherwise.

The fallback behaviour of `#{}` just seems very arbitrary. `to_string` is not a subset of `inspect`; `to_string(:atom) => "atom"`, `inspect(:atom) => ":atom"`. So what if I want the inspected value of an atom, not the to_string one in my interpolation?

I think it is better to error than to try and guess if the developer really wanted `to_string` or `inspect`. Fail early.



--
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.
For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson

Carsten Bormann

unread,
Apr 6, 2014, 7:22:20 AM4/6/14
to elixir-l...@googlegroups.com
On 06 Apr 2014, at 13:13, Eric Meadows-Jönsson <eric.meado...@gmail.com> wrote:

> I think it is better to error than to try and guess if the developer really wanted `to_string` or `inspect`. Fail early.

+1.

And I repeat my suggestion to make it more natural for the developer to explicitly say the ‘inspect’ case without having to litter the code with that word.

Grüße, Carsten

James Fish

unread,
Apr 6, 2014, 8:29:50 AM4/6/14
to elixir-l...@googlegroups.com
Hi Steve,

Could you give some example code on how you are using inspect? There is probably a better way than #{inspect(term)}.

When using :error_logger or :lager you should:
:error_logger.error_msg('term: ~ts~n', [inspect(term)])

When using IO you should:
IO.puts(["term: " | inspect(term)])

When you say logging for tracing, perhaps you want to use the builtin support for tracing (several erlang trace utilities available on BEAM) rather than littering code with debug printing when developing. There is also the :sys module that can log messages (both in and out) or retrieve the state of a process when using an OTP behaviour.


Dave Thomas

unread,
Apr 6, 2014, 10:00:21 AM4/6/14
to elixir-l...@googlegroups.com
I think it is better to error than to try and guess
​…

​So you’ll be removing this behavio(u)r in the next release?

a = 1230000.0
IO.puts "value is #{a}"

"value is 1.23e6"​

and, logically, you definitely need to remove this, too:

b = "\024"
IO.puts "value is #{b}"
"value is ^T"

And, of course, there’s always this one:

c = Enum.map ~w{ I am sad }, &String.length/1
IO.puts "The lengths are #{c}"

The lengths are ^A^B^C

In all these cases you are choosing a representation for a value where the value is not originally a string. to_string has to guess on a way of converting that value into a human-readable representation. And that representation “leaks” into a string.

I see no difference between that and

d = {1,2,3}
IO.puts "value is #{d}"

IO.puts “Signed: #{[4, 1, 22, 5]}”

Steve Pallen

unread,
Apr 6, 2014, 11:36:13 AM4/6/14
to elixir-l...@googlegroups.com
I'm addressing several points below:

After thinking more about this, I'm going to solve this for myself with a new sigil. I now appreciate this is a discussion around intent. My intent is to inspect so creating a new 'inspect' sigil make my code more explicit and solves the problem.
I didn't think this through. A sigil doesn't really help since I would need to wrap it with interpolation syntax:

I didn't think this through. A sigil isn't any better than using inspect...
IO.puts "the result is #{~I(answer)}"
Here is another inconstancy:
IO.puts "the result = #{:ok}"
the result = ok

Ruby's approach makes more sense than raising an exception:
a = Array.new [1,2,3]
puts "Array = #{a}"
Array = [1, 2, 3]

a = {one: 5, two: 3}
puts "a = #{a}"
a = {:one=>5, :two=>3}

class Steve
end

steve = Steve.new
puts "steve = #{steve}"
steve = #<Steve:0x007fe225839528>

In response to James Fish's Post:

Could you give some example code on how you are using inspect? There is probably a better way than #{inspect(term)}. 

Here is an example of my error logging. 
cond do
  dsp_unit == 0 ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid dsp_unit: #{inspect dsp_unit}"
  far_end_port <= 0 ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid far_end_port: #{far_end_port}"
  far_end_ip == [] ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid dsp_unit: #{inspect far_end_ip}"
  true ->
    if media_path(cx, dsp_unit, channel, channel + 2, far_end_ip, far_end_port, :clear) != :ok do
      Mdse.error "dsetsm.close_audio_stream #{h2s cx.unit} could not idle media path"
    end
    Mdse.ResourceManager.idle_unit!(dsp_unit)
end

When you say logging for tracing, perhaps you want to use the builtin support for tracing (several erlang trace utilities available on BEAM) rather than littering code with debug printing when developing. There is also the :sys module that can log messages (both in and out) or retrieve the state of a process when using an OTP behaviour.

I looked at number of different logging solutions before I decided to roll my own. I would love to find a better solution than littering my code with all these log statements. To understand my requirements, I should explain the application.
  • My application sits between two legacy phone systems and basically acts a protocol converter.
  • On one side, I talk over a reliable UDP protocol, establishing a connection for 1 to 1000 registered phones
  • On the other side, I manage 1..n servers with 4 different TCP links and a reliable UDP connection
  • I have 3 different :gen_fsm and about 10 :gen_servers
My logging requirements based on a similar solution that I'm replacing:
  • An administrator console where they can print status information about various resources, issue commands to enable and disable various things, as well as change run time log levels
  • Enable / disable individual component log levels
  • Write logs to a file that is managed with log rotate
  • Write logs in a specific format that can be reformatted by existing external program
I would love to find a solution that allows developer debugging and administrator access run time log level control that does not plaster my code with log statements and does not crash sometime in the future because I missed an inspect in some obscure case that happens only on a blue moon.

Steve

José Valim

unread,
Apr 6, 2014, 12:07:41 PM4/6/14
to elixir-l...@googlegroups.com
On Sun, Apr 6, 2014 at 4:00 PM, Dave Thomas <da...@pragprog.com> wrote:

I think it is better to error than to try and guess

So you’ll be removing this behavio(u)r in the next release?

a = 1230000.0
IO.puts "value is #{a}"

"value is 1.23e6"

and, logically, you definitely need to remove this, too:

b = "\024"
IO.puts "value is #{b}"
"value is ^T"

And, of course, there’s always this one:

c = Enum.map ~w{ I am sad }, &String.length/1
IO.puts "The lengths are #{c}"

The lengths are ^A^B^C

In all these cases you are choosing a representation for a value where the value is not originally a string. to_string has to guess on a way of converting that value into a human-readable representation. And that representation “leaks” into a string.

Dave, that's not what is happening with 2 of your 3 examples. When you see "value is ^T", you are seeing the inspected value. to_string for binaries is receiving bytes and outputting bytes. to_string for char lists are treating them as chars. There is no other option for to_string(<<0, 10, 11>>) to return than <<0, 10, 11>>. The fact you see ^T is actually a decision made by inspect, not by to_string nor interpolation.

For floats, I agree we are picking a representation. :)

José Valim

unread,
Apr 6, 2014, 12:18:46 PM4/6/14
to elixir-l...@googlegroups.com
Here is another inconstancy:
IO.puts "the result = #{:ok}"
the result = ok

I wouldn't call it an inconsistency. An atom is a textual representation and it has a direct translation to a string. :) I am aware Ruby disagrees though (as symbols do not implement .to_str). 

cond do
  dsp_unit == 0 ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid dsp_unit: #{inspect dsp_unit}"
  far_end_port <= 0 ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid far_end_port: #{far_end_port}"
  far_end_ip == [] ->
    Mdse.error "DsetSm.close_audio_stream #{h2s cx.unit} invalid dsp_unit: #{inspect far_end_ip}"
  true ->
    if media_path(cx, dsp_unit, channel, channel + 2, far_end_ip, far_end_port, :clear) != :ok do
      Mdse.error "dsetsm.close_audio_stream #{h2s cx.unit} could not idle media path"
    end
    Mdse.ResourceManager.idle_unit!(dsp_unit)
end

This is a wild guess and I don't have a complete idea of the application requirements. But what if far_end_ip was a record/struct? If you implement it as so, you can implement the String.Chars protocol for such structure and get a fair representation of it that makes sense to your application.

I looked at number of different logging solutions before I decided to roll my own. I would love to find a better solution than littering my code with all these log statements. To understand my requirements, I should explain the application.
We have plans for a logging system before 1.0 which is based on OTP ones. That said, I would *love* your opinion once we start working on those.

Thanks for the details Steve!

Dave Thomas

unread,
Apr 6, 2014, 12:37:19 PM4/6/14
to elixir-l...@googlegroups.com


Dave, that's not what is happening with 2 of your 3 examples. When you see "value is ^T", you are seeing the inspected value. to_string for binaries is receiving bytes and outputting bytes. to_string for char lists are treating them as chars. There is no other option for to_string(<<0, 10, 11>>) to return than <<0, 10, 11>>. The fact you see ^T is actually a decision made by inspect, not by to_string nor interpolation.

​Umm, no.

What we’re talking about is the way #{…} works. You’re talking about implementation, but the real question comes from the developer’s side.

People argued that "value: #{ {1,2,3} }" should​ result in "value: {1,2,3}". Others said “No, because that’s inspect behavio(u)r.

But my point is that we already do something similar with lists and non-printing values. Whether that is implemented using inspect inside to_string or by emailing the elves and asking what they’d do, the result (as far as the developer is concerned) is the same—string interpolation is not and has never been pure—the concept of to_string already involves some interpretation (read “guessing”) on the part of the runtime.

So why not just accept that and extend it further. Doing so would be a great convenience to users of the language.

Dave

José Valim

unread,
Apr 6, 2014, 1:41:50 PM4/6/14
to elixir-l...@googlegroups.com

Umm, no.

What we’re talking about is the way #{…} works. You’re talking about implementation, but the real question comes from the developer’s side.

Perfect, I see your point.

the concept of to_string already involves some interpretation (read “guessing”) on the part of the runtime.

But there are certain guesses and not so certain guesses... :)

And making to_string/1 work for {1, 2, 3} makes it wrong for the majority of the other places to_string is used. So any solution on this area would require changing interpolation to rely on different mechanism, like falling back from to_string to inspect (or alternatively e-mailing elves) :D.

I am still uncertain though this change is worthy the downsides. We are talking about logging but there are tons of cases where we actually want interpolation to be meaningful to non-developers. Example:

    "Hi, my name is: #{name}"

If you print anything coming from an inspect, it is a bug in your application. There are even more severe cases where you mistakenly pass a connection, which has the session store, which has your session secret. Oops.

Also, when "debugging" in Ruby, it was used to write "puts obj" only to find out that someone implemented to_s to actually mean something. So I need to go back there and change it to "puts obj.inspect". At this point, my muscle memory almost always write it in the "obj.inspect" format.

So if the main goal is logging, I would rather find ways to make it better for logging. Maybe providing a %i"this will be inspected: #{val}".

Eric Meadows-Jönsson

unread,
Apr 6, 2014, 1:44:48 PM4/6/14
to elixir-l...@googlegroups.com

You bring up a good point Dave.

Maybe we should error when running to_string on binaries and lists that are not printable. On the other hand, those are the only two types we do that for. They are also the only two types in elixir that are strings, so maybe we should make an exception for actual string types.



--
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.
For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson

Steve Pallen

unread,
Apr 6, 2014, 3:50:37 PM4/6/14
to elixir-l...@googlegroups.com
This is a wild guess and I don't have a complete idea of the application requirements. But what if far_end_ip was a record/struct? If you implement it as so, you can implement the String.Chars protocol for such structure and get a fair representation of it that makes sense to your application.

In this case far_end_ip is a tuple as used by :gen_tcp. What 's interesting is that if I implement String.Chars for Tuple, I get records, HashDicts, etc. automatically.

So if the main goal is logging, I would rather find ways to make it better for logging. Maybe providing a %i"this will be inspected: #{val}".

I could probably live with the sigil approach. This is how I may solve the problem for my application If I can figure how to to implement it :)

But, I still feel treating "value = #{ {1} }" with an exception is a royal pain. I've trip over this time and time again. I'd be happier if this returned "value = #Tuple" or something like that. This is a big issue for me since I have not been able to get mocks working reliably in my test suites. So, don't have a lot of tests written for these complex state machines. I reported a bug in Amrita a couple weeks ago so I'm patiently waiting on a fix for this. 

Steve
Reply all
Reply to author
Forward
0 new messages