here's a write-up of an idea: https://docs.google.com/document/d/1Gkflz2k9uN3HFyw5xR_WJh7Latx_uNOaB9tW9tjWKiM/edit?usp=sharing
any feedback and thoughts welcome!
What would be the syntax for Log in a string interpolation?
one thought is that calling toStr explicitly may sometimes be more readable, especially in a program relying heavily on type inference - otherwise, it is difficult to distinguish "is this a string, or a value that has Display"? That might be valuable at user input points where you might have a string representation of some input that you're about to turn into a data structure
oh I wasn't thinking that Log would interact with string interpolation
like you'd just call Str.log on your value to get a log-formatted Str out, which you could then use with interpolation
it would be used with expect failures though, so you'd get something similar to Rust's dbg! that way anyway
so it would mean that if you wanted to interpolate when literally writing to a logging service, that would be more verbose, but I'm ok with that
I like the proposal. And I think these abilities are perfect to add to the short list of builtin ones. They're the kind of thing you'd expect to be built in.
But the name Log misleads me, because it suggests it should be actually logging as a side effect, like Elm's Debug.log or JavaScript's console.log
How about Inspect?
I'm thinking of Node.js Utils.inspect and Ruby's .inspect method.
I don't really have the same reaction that the production logging use case makes Debug the wrong word. I see the logic of that point of view, I just don't have the same reaction. If I was logging something in Rust I'd use the Debug representation, no problem. Because I might want to use my logs to debug stuff some day. And it's obviously for developers.
For Display, what happens when the app is more complicated and needs to support multiple spoken languages? You can't provide a language parameter to Display unless that data is already stored in the type?
That seems like something for a package rather than built into the language.
You could, for example, make a tag union that contains the language and a string, and has a Display instance that acts accordingly.
[English Str, French Str, Dutch Str, Spanish Str]
Yeah I agree that Roc shouldn't handle localization directly.
Brian Carroll said:
You could, for example, make a tag union that contains the language and a string, and has a Display instance that acts accordingly.
[English Str, French Str, Dutch Str, Spanish Str]
Makes sense.
I thought about it some more and I think these are my concerns:
Person.fullNamePerson.firstName gets used for toStr but not Person.lastName or Person.fullName because firstName was the first function needed for string interpolation.foo : a -> SomeOutput | a has Display without considering that the user needs to call foo with different toStr functions. foo : (a -> Str) -> a -> SomeOutput would be better in this example.Maybe these are all nitpicks but to me, having Display feels like a step away from the pit of success in order to save some effort when doing string interpolation.
:thinking: I have the following notes about the proposal:
Display and Render might be confusing. Something can be said for requiring an implementation of Display for Render. Also, namespacing it as e.g. Editor.Render might make it even more clear what the purpose is.Log is might confuse people. The name Inspect is also used in Elixir (besides Ruby and NodeJS which were already mentioned).Log and what people should see as output in the REPL? A lot can be said for having the REPL output be 1:1 Log.Log at some point. But that can wait for a time.Log be default implemented for everything. This somewhat breaks the 'opaque'ness of opaque types, but does mean that ending up with a value that cannot be logged during error monitoring to be very rare. (And you can always customize it or opt-out alltogether).Show is much closer to Log than to Display, as the output is intended to be copy-pastable as valid Haskell back into a REPL.)How about
Inspect?
I'm thinking of Node.jsUtils.inspectand Ruby's.inspectmethod.
love it! :100:
Inspect definitely communicates "this is for internal use," and it's also nice that there's precedent in other languages :thumbs_up:
an algebraic pretty-printer
what are those? Never heard of this!
Maybe these are all nitpicks but to me, having Display feels like a step away from the pit of success in order to save some effort when doing string interpolation.
what do others think about this?
I do not think having both
DisplayandRendermight be confusing. […] Also, namespacing it as e.g.Editor.Rendermight make it even more clear what the purpose is.
that's a good point - also it would be Str.Display, which would probably help too. Okay, maybe Display is fine! :thumbs_up:
@Qqwy / Marten
A lot can be said for having the REPL output be 1:1
Log.
:100:
Richard Feldman said:
an algebraic pretty-printer
what are those? Never heard of this!
The best introduction is probably the paper "Strictly Pretty" (2000) by Christian Lindig (http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200)
It is a very systematic way to do pretty-printing. Introduced by Hughes and improved by Wadler.
The basic idea is that you build up a declarative representation of the to-be-printed structure using six basic constructors (this is provably enough and is where the 'algebraic' descriptor comes from), and then this representation can be printed in a pretty style that conforms to whatever layout policy (things like max. line width, how deeply nested the data structure is, do you want to show all elements in a list or only a prefix for long lists, etc.) you currently have.
Mainly it gives a systematic answer to the question "When do I want to display my list as
[1, 2, 3, 4, 5]
And when do I want to display it as e.g.
[
1,
2,
3,
4,
5
]
?"
based on both what elements are contained inside and how long the line is allowed to be.
The pretty crate that we use in the compiler is based on these ideas
The README mentions Wadler's Haskell version https://crates.io/crates/pretty
I'm all for avoiding printf style modifiers, but I still find the "identifiers only" restriction slightly annoying.
I get why you want to avoid arbitrary expressions, but I wonder if it could be extended to a single function application or something like that.
I don't know, I might just get used to declaring that separately all the time, but some times I'd prefer to have the good old string concatenation operator which doesn't make you do that.
In Swift, \(value) (basically) desugars to something like appendInterpolation(value).
Which allows you to define overloads like appendInterpolation(_ value: Date, style: DateStyle) so you can do: \(date, style: .iso)
Maybe Roc can support something similar with Abilities
I think we'd like to allow interpolating any value that implements a Display ability (or similarly named) for human-readable formatting.
also @Joshua Warner and I have talked about wanting to allow any expression in the parens, it's just harder to implement :big_smile:
(for the parser)
Oh nice! I thought it was by design
it was at first, in the sense of "this is simpler both to implement and as a design, so let's try it this way and see if we want more"
turns out we have wanted more :big_smile:
I definitely feel like we should try display first and the re-evaluate expanding to be more flexible after we have had display for a while.
oh that's a good call :thinking:
yeah a Display ability which both Str and Num implement would cover the #1 most common use case there
Would custom types automatically get Display? That would be helpful for https://github.com/roc-lang/basic-cli/issues/12#issue-1498740617
so we've talked about 2 different ideas here:
Display, which is for end users (e.g. Str and Num would both get this, but records would not; you typically shouldn't display programming language syntax to directly to end users!)Inspect, which is for programmers - and there'd be a Str.inspect : a -> Str where a implements Inspect function, so anything can be inspectedas far as opaque types, just like Eq and Hash (and in the future Ord, but we haven't implemented that one yet in the compiler) you'd be able to say MyOpaqueType := { ...whatever... } implements [Eq, Hash, Inspect] to have it auto-derive implementations if you don't care
and if MyOpaqueType is a wrapper around something that implements Display, you could include Display in that list
(the default Inspect implementation would be something like "<opaque>" so it doesn't leak implementation details by default, but you could opt into making it inspectable without having to handwrite an implementation)
so Inspect would be for things like debugging and logging errors
and Display would be for things like displaying numbers to users
but I think string interpolation should do Display and not Inspect
because otherwise you could very easily accidentally display programmer stuff to end users and you wouldn't get a type mismatch to let you know
we did that once at NoRedInk back when Elm had toString : a -> String - we ended up with the string <function> in our UI, and the compiler of course didn't catch it because toString accepts any type :sweat_smile:
Richard Feldman said:
as far as opaque types, just like
EqandHash(and in the futureOrd, but we haven't implemented that one yet in the compiler) you'd be able to sayMyOpaqueType := { ...whatever... } implements [Eq, Hash, Inspect]to have it auto-derive implementations if you don't care
That's great!
I didn't think Display would be meant for end users because the same type usually has to be formatted differently for each use case, so could there ever be a good enough implementation?
to me the main idea there is to have it be for things like strings, numbers, and opaque wrappers around those (e.g. Email, Url, Path)
like I don't think you'd ever want Display for something like a tree or record
Right, but if you're priting numbers for end users in a serious application, you really want to take locale into consideration
That said, it could work fine for standardized formats like Email, Url, and Path
but probably not for things like Date either
Agus Zubiaga said:
Right, but if you're priting numbers for end users in a serious application, you really want to take locale into consideration
yeah I've thought about that, but in practice it seems like what basically always happens today is fooStr = Num.toStr foo followed by "...\(fooStr)"
Yep, I do that all the time
yeah so we could say "this is the way" and then have everyone do something like fooStr = toLocaleStr foo locale
I think I'd prefer to be explicit about the function I'm using to format my thing, e.g. \(Num.toStr foo) if I don't care about locale and want . decimal separators
but that feels more inconvenient than "pit of success" to me
hmm that's interesting
I don't think we've discussed that option!
I thought that's what you meant by
Richard Feldman said:
also Joshua Warner and I have talked about wanting to allow any expression in the parens, it's just harder to implement :big_smile:
oh we have
I just mean we (somehow) have never talked about the design of "explicitly decide not to have Display for numbers"
(which could also arguably be "don't have Display at all, just call Url.toStr explicitly")
Yeah, to me Display seems like the kind of type-class gone too far, where you always want specific implementation but it's not obvious which you're going to get
a funny thing that appeals to me about that idea is that I don't like the name Display (because it sounds like it's about rendering to the screen, which it's not) but I don't have a better idea for a name :laughing:
If I had to choose between Display and arbitrary expressions in string interpolation, I'd chose the latter because I think that'd drive devs to write more intentional / predictable applications
45 messages were moved here from #ideas > reddit string interpolation syntax by Richard Feldman.
yeah I think I'm sold on the idea of implementing arbitrary expressions inside string interpolation first, and then see if there's still demand for Display
and if not, just don't implement it (or consider implementing it but not for numbers)
That sounds great to me
what do others think?
With Agus's example, i prefer explicit. That said, i think i prefer it because it is a simple explicit and well named function application. If it were more complicated and abused by devs, i would much prefer display. But yeah, that is probably better than display especially when you consider locales and such.
I still think we should at least add Inspect that would be used for debug printing. It would get used implicitly in dbg statements and could be used explicitly in string interpolation "something: \(inspect var)".
I guess maybe this suggests instead of enabling any express in interpolation, just enable a single function application.
+1 for explicit over Display
Brendan Hansknecht said:
I still think we should at least add
Inspectthat would be used for debug printing. It would get used implicitly indbgstatements and could be used explicitly in string interpolation"something: \(inspect var)".
Definitely. I think Inspect is super useful for printing errors especially.
This sounds great. Do we have a way forward on changing basic-cli main Task return type and the point @Agus Zubiaga raised about crashing? I thought that Display was also wanted for that.
That’s what Inspect would be for
So we’d be able to do crash (Str.inspect err)
Brendan Hansknecht said:
I still think we should at least add
Inspectthat would be used for debug printing. It would get used implicitly indbgstatements and could be used explicitly in string interpolation"something: \(inspect var)".
Do we have an issue to track this? I can't find one, but happy to make something.
I've been using the RTL tool, and this feature would be super helpful I think to enable type safe string interpolation. Specifically something like "string things $(model.myThing)" ... where myThing implements Inspect. This would me we don't need to store raw Str in the model record.
How hard would it be to implement this? Could anyone give me a pointer?
Currently if we try this we get the following,
app [main] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br",
}
import cli.Stdout
import cli.Task exposing [Task]
main : Task {} _
main =
thingA = newThing "Thing A"
Stdout.line! "Hello, $(thingA)!"
Thing := Str implements [Inspect]
newThing = @Thing
── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────
This argument to this string interpolation has an unexpected type:
11│ Stdout.line! "Hello, $(thingA)!"
^^^^^^
This thingA value is a:
Thing
But this string interpolation needs its argument to be:
Str
For now, you can always do "Hello, $(Inspect.toStr thingA)!" on anything that implements Inspect
That's my bread and butter for debugging
I guess we could hack that in and just Inspect.toStr literally everything including the Str values -- I'll give it a go
I think it's probably good that we don't do it by default. Are you talking about in Roc in general, or just in RTL?
Yeah, I'm thinking about RTL
But, the thread/idea is that we enable String interpolation for things that have Inspect
I'm against it in general because Inspect is basically implemented for everything, so people will willy nilly print everything in debug format
In RTL's case, I just verified that Inspect.toStr "abc" puts the quotes around the string, so this may not be as free as it sounds
So any string, by itself, in a tuple or struct, will get quotes
I like the idea though
Maybe there can be a prefix for interpolations that when passed uses Inspect.toStr
Or the other way around, if you'd prefer it to be the default
Something like {{$ (123, 456) }}
Or maybe {$ ... $} for matching the symmetry of the other handlebars
I think it works for RTL, I've basically made each template return an opaque type RTL := Str and so now you can't accidentally pass that into another template where there should be a Str.
Using Inspect.toStr works well, so we still don't need type annotations in the generated roc module.
I thinkInspect.toStr should always be explicit, but we still could add a Display equivalent that is implicit in interpolation
That said, for something like RTL, I assume you need something special that stops inserting script tags, SQL injection, and what not?
yeah I definitely think Inspect.toStr should always be explicit when it comes to interpolation
I'd also prefer not to do a Display equivalent
I do agree about dbg (and the repl) using Inspect though :thumbs_up:
one reason I don't want to do Display is that if I'm doing "number is: $(Num.toStr num)" then if I want to do formatting on the number (e.g. specify how many digits after a decimal point to show, scientific notation rules, etc.) then it's both really obvious how to do that, and also the code doesn't look bad
whereas if it's super normal to do $(num) and then all of a sudden if you want to improve user experience by doing formatting, it looks really different to introduce this function call that never happens anywhere else, which discourages doing the thing that's better for users
another is that it can subtly hide type mismatches; if I do "Name: $(x)" and x is a number, this will type-check and do the wrong thing. I've seen this happen in production (extremely rarely, granted) - those bugs are nasty to track down.
whereas if x has to be a Str, then because I didn't call Num.toStr (or a formatting alternative) on it, I'll get a type mismatch that highlights the mistake
all that said, I could see a potential future where we do decide to add a Display, I just want to default to not doing it and see how that goes :big_smile:
Last updated: Jun 16 2026 at 16:19 UTC