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

toString behaves unintuitively #657

Closed
kirked opened this issue Jul 4, 2016 · 11 comments
Closed

toString behaves unintuitively #657

kirked opened this issue Jul 4, 2016 · 11 comments

Comments

@kirked
Copy link

kirked commented Jul 4, 2016

In most popular languages (Java, Javascript, Clojure, Scala to name a few), toString simply provides a string representation of a value. However, in Elm it seems to provide a syntax writer (I'm thinking the dual of something like the Clojure reader).

There's one edge case where that easily bites: when the value is a String already, toString wraps it in quotes:

> str = "Hello there."
"Hello there." : String
> toString str
"\"Hello there.\"" : String

Writing generalised code as I do, I wrote a helper to extract a value from Maybe:

get : Maybe a -> String
get ma = case ma of
  Just a  -> toString a
  Nothing -> ""

To my surprise, my displayed text was quoted. It seems there's no way in Elm to properly express the desired function above, since you can't tell at runtime if a value is a String.

After a short discussion on Slack #general 4 Jul 2016 1:12am CDT, I posited:

Well, if it's intended to be a syntax writer, then it's poorly named, given that ​_toString_​ in most other languages don't behave that way. And if it's intended to just give back a string representation of a value, then a string value already represents itself and shouldn't be changed.

I propose moving or renaming the current toString and providing an implementation that behaves as one would expect coming from other popular languages. Or just give me the ability to tell if a value is a string at runtime, and I can work around this edge case.

Thoughts?

@process-bot
Copy link

Thanks for the issue! Make sure it satisfies this checklist. My human colleagues will appreciate it!

Here is what to expect next, and if anyone wants to comment, keep these things in mind.

@pdamoc
Copy link

pdamoc commented Jul 4, 2016

Elm's toString behaves closer to Haskell's show. The current behavior allows one to distinguish between the string "Hello" and the value Hello defined in a type like type Greeting = Hello | Hi

If you want toString to not display the extra ", just implement your own version that checks if there are extra " :

toStr v =
  let 
    str = toString v
  in 
    if left 1 str == "\"" then 
      dropRight 1 (dropLeft 1 str)
    else 
      str

@shmookey
Copy link

shmookey commented Jul 4, 2016

To follow up from Slack - the closest you're going to get to the behaviour you want is (as @pdamoc suggests) stripping the quotes off yourself.

A function that could tell you at runtime whether or not a value of type a represents a string would not actually help you here. Let's try rewriting your get function, imagining that we have some function isString : a -> Bool

get : Maybe a -> String
get ma = case ma of
  Just x  -> if isString x 
             then ??? 
             else toString x
  Nothing -> ""

How could we fill in the blank? It can't just be x, because the only type the compiler knows for x is a, and it needs String. You still need to call toString, and you still need to strip off the quotes.

If you would like to see some other function of type a -> String that yields an unquoted value when a is a string, what do you propose it would do for other types?

I still say your function is too general. If a type variable appears once in a function signature, you can't do anything with a value of that type because the compiler doesn't know anything about it. toString does, but that's magic. If you need to do anything at all with a value you've explicitly generalised to a - other than combine it with or pass it to another argument typed a in the same signature - you need to introduce some constraints. That's how you express this function "properly". 😄

@rtfeldman
Copy link
Member

This came up in the string interpolation discussion, as toString would have to work as OP expected it to in order for string interpolation to work.

I'm curious - is anyone depending on the current behavior, and couldn't work around such a change with Json.Encode.string? (cc @eeue56, who has done a great deal of code-gen in Elm)

@rtfeldman
Copy link
Member

To be more specific on that last point, in a world where toString on a String worked the same way as the identity function, I'd expect toString >> Json.Encode.string >> Json.Encode.encode 0 to work exactly like toString does today.

I expect this based on how in JavaScript, JSON.stringify("foo") returns a string that is five characters long: foo with quotes around it.

@avh4
Copy link
Member

avh4 commented Jul 5, 2016

toString currently has a specific purpose: to give a representation of a thing that matches what you would type in Elm source code to get that thing. If you have a string already and want to display that string as it is, just display the string.. don't call toString on it.

If you are toStringing a record or a union type, it's because you are displaying it for debugging purposes. If you are toStringing a float, you really should be using some kind of number formatting. toStringing an int is currently the only reasonable case where you would use toString in production code, and this could be fixed by introducing String.fromInt.

The behavior of toString in other languages was mentioned, but I don't think those are good examples to follow. Especially in statically typed languages, it is generally not advised to use toString (for example). The current behavior of toString is consistent and clearly-defined and I don't think we want to introduce a change that will give Elm's toString all of the ambiguities that Java, Javascript, etc's toStrings have.

@rtfeldman
Copy link
Member

I hadn't considered the possibility that interpolation might not actually desugar to a toString call in the case of strings. Fair enough! I guess that discussion can be decoupled from this one.

@kirked
Copy link
Author

kirked commented Jul 5, 2016

@avh4 Thanks for the explanation as to the purpose of toString, it's not in the docs. Does it then have any use to an app programmer? (Should it be in the public API if it's useless?)

I don't buy your assertion that ...in statically typed languages, it is generally not advised to use toString. I've worked in statically typed languages for > 20 years, and have found it quite common to call toString to get a display value.

@rtfeldman, Thanks for pointing out the discussion about interpolation. If/when that is available (I wish it was today!), I would choose that way of representing the value in the view.

FWIW, I was lurking on the Slack list a bit last evening, and this very same issue came up from another person on the list (I didn't comment, but had a little chuckle). I think you're definitely working against the Principle of Least Surprise here.

@JoshCheek
Copy link

Looked in the Mailing List and didn't see an issue for this, read through the most promising one about toString but it was really about namespacing, so I'll post here despite the bot's suggestion that I don't:

Due to historical context, the name would lead everyone coming to Elm to assume it returns a String representation of the argument, for purposes like interpolation, where toString on a String should be identity. Now, I suspect that @avh4 is right in https://github.com/elm-lang/core/issues/657#issuecomment-230389564, but even if he is, the name is setting incorrect expectations, and there's no path from that expectation to whatever the better way is. So, regardless of opinions about whether a toString function as I described should exist, it seems clear to me that the existing one should be renamed.

What do other languages call it? Given that Haskell calls it show (docs)? Ruby calls it inspect (inspect, to_s), JavaScript calls it inspect and dir (inspect, dir, toString), Python has calls the module for this sort of thing inspect, and returns a list of functions from dir (inspect, dir).

What should Elm call it? For the reasons above, I'd advocate inspect or show, and I'd prefer inspect as show implies it should be readable, but that's never the context I use such a method in, and it would limit me from displaying useful information in the output. It's also worth considering a context outside the browser, like the shell (I assume Elm is headed in that direction).

@OvermindDL1
Copy link

Another vote for inspect.

However, an alternative is the ability to make functions of different types have the same name, that way something like this would be valid if-and-only-if the call path is resolvable from the point it is called at, if the function call is ambiguous (like not having enough arguments to fully determine which should be called) then the compiler should throw an error. This is how many statically typed functional languages work and would work around this issue. Another method would be a function matcher style, where you can have multiple heads of a function and they match on their values and value-types (only if resolvable at compile-time else compiler throws an error), which effectively gives the same ability as the above but is often easier to implement in the compiler while also being more powerful.

@evancz evancz mentioned this issue Aug 15, 2016
@rtfeldman
Copy link
Member

Added to #322

Let's batch discussion on whether toString should change, and if so, how, once there is a batch of active work being done in this area.

@elm elm locked and limited conversation to collaborators Aug 15, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants