Stream: ideas

Topic: display and log


view this post on Zulip Richard Feldman (Jun 28 2022 at 02:48):

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!

view this post on Zulip Ayaz Hafiz (Jun 28 2022 at 03:02):

What would be the syntax for Log in a string interpolation?

view this post on Zulip Ayaz Hafiz (Jun 28 2022 at 03:04):

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

view this post on Zulip Richard Feldman (Jun 28 2022 at 04:03):

oh I wasn't thinking that Log would interact with string interpolation

view this post on Zulip Richard Feldman (Jun 28 2022 at 04:04):

like you'd just call Str.log on your value to get a log-formatted Str out, which you could then use with interpolation

view this post on Zulip Richard Feldman (Jun 28 2022 at 04:05):

it would be used with expect failures though, so you'd get something similar to Rust's dbg! that way anyway

view this post on Zulip Richard Feldman (Jun 28 2022 at 04:06):

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

view this post on Zulip Brian Carroll (Jun 28 2022 at 05:14):

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.

view this post on Zulip Brian Carroll (Jun 28 2022 at 05:15):

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

view this post on Zulip Brian Carroll (Jun 28 2022 at 05:18):

How about Inspect?
I'm thinking of Node.js Utils.inspect and Ruby's .inspect method.

view this post on Zulip Brian Carroll (Jun 28 2022 at 05:23):

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.

view this post on Zulip Martin Stewart (Jun 28 2022 at 06:31):

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?

view this post on Zulip Brian Carroll (Jun 28 2022 at 06:40):

That seems like something for a package rather than built into the language.

view this post on Zulip Brian Carroll (Jun 28 2022 at 06:43):

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]

view this post on Zulip Martin Stewart (Jun 28 2022 at 07:42):

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:

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.

view this post on Zulip Qqwy / Marten (Jun 28 2022 at 10:49):

:thinking: I have the following notes about the proposal:

view this post on Zulip Richard Feldman (Jun 28 2022 at 13:03):

How about Inspect?
I'm thinking of Node.js Utils.inspect and Ruby's .inspect method.

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:

view this post on Zulip Richard Feldman (Jun 28 2022 at 13:16):

an algebraic pretty-printer

what are those? Never heard of this!

view this post on Zulip Richard Feldman (Jun 28 2022 at 13:16):

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?

view this post on Zulip Richard Feldman (Jun 28 2022 at 13:17):

I do not think having both Display and Render might be confusing. […] Also, namespacing it as e.g. Editor.Render might 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:

view this post on Zulip jan kili (Jun 28 2022 at 14:02):

@Qqwy / Marten

A lot can be said for having the REPL output be 1:1 Log.

:100:

view this post on Zulip Qqwy / Marten (Jun 28 2022 at 14:19):

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.

view this post on Zulip Qqwy / Marten (Jun 28 2022 at 14:23):

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.

view this post on Zulip Brian Carroll (Jun 28 2022 at 15:58):

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

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 14:38):

I'm all for avoiding printf style modifiers, but I still find the "identifiers only" restriction slightly annoying.

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 14:39):

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.

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 14:44):

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.

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 15:03):

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)

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 15:04):

Maybe Roc can support something similar with Abilities

view this post on Zulip Ayaz Hafiz (Apr 17 2023 at 15:05):

I think we'd like to allow interpolating any value that implements a Display ability (or similarly named) for human-readable formatting.

view this post on Zulip Richard Feldman (Apr 17 2023 at 17:03):

also @Joshua Warner and I have talked about wanting to allow any expression in the parens, it's just harder to implement :big_smile:

view this post on Zulip Richard Feldman (Apr 17 2023 at 17:04):

(for the parser)

view this post on Zulip Agus Zubiaga (Apr 17 2023 at 19:07):

Oh nice! I thought it was by design

view this post on Zulip Richard Feldman (Apr 17 2023 at 22:05):

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"

view this post on Zulip Richard Feldman (Apr 17 2023 at 22:05):

turns out we have wanted more :big_smile:

view this post on Zulip Brendan Hansknecht (Apr 18 2023 at 01:03):

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.

view this post on Zulip Richard Feldman (Apr 18 2023 at 01:33):

oh that's a good call :thinking:

view this post on Zulip Richard Feldman (Apr 18 2023 at 01:33):

yeah a Display ability which both Str and Num implement would cover the #1 most common use case there

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 10:02):

Would custom types automatically get Display? That would be helpful for https://github.com/roc-lang/basic-cli/issues/12#issue-1498740617

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:15):

so we've talked about 2 different ideas here:

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:16):

as 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

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:16):

and if MyOpaqueType is a wrapper around something that implements Display, you could include Display in that list

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:17):

(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)

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:17):

so Inspect would be for things like debugging and logging errors

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:17):

and Display would be for things like displaying numbers to users

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:18):

but I think string interpolation should do Display and not Inspect

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:18):

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

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:19):

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:

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:51):

Richard Feldman said:

as 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

That's great!

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:52):

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?

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:53):

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)

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:53):

like I don't think you'd ever want Display for something like a tree or record

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:54):

Right, but if you're priting numbers for end users in a serious application, you really want to take locale into consideration

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:55):

That said, it could work fine for standardized formats like Email, Url, and Path

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:56):

but probably not for things like Date either

view this post on Zulip Richard Feldman (Apr 18 2023 at 11:57):

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)"

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 11:59):

Yep, I do that all the time

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:02):

yeah so we could say "this is the way" and then have everyone do something like fooStr = toLocaleStr foo locale

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 12:03):

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

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:03):

but that feels more inconvenient than "pit of success" to me

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:03):

hmm that's interesting

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:03):

I don't think we've discussed that option!

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 12:04):

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:

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:04):

oh we have

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:04):

I just mean we (somehow) have never talked about the design of "explicitly decide not to have Display for numbers"

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:05):

(which could also arguably be "don't have Display at all, just call Url.toStr explicitly")

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 12:05):

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

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:06):

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:

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 12:10):

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

view this post on Zulip Notification Bot (Apr 18 2023 at 12:13):

45 messages were moved here from #ideas > reddit string interpolation syntax by Richard Feldman.

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:13):

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

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:14):

and if not, just don't implement it (or consider implementing it but not for numbers)

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 12:15):

That sounds great to me

view this post on Zulip Richard Feldman (Apr 18 2023 at 12:16):

what do others think?

view this post on Zulip Brendan Hansknecht (Apr 18 2023 at 15:25):

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.

view this post on Zulip Brendan Hansknecht (Apr 18 2023 at 15:27):

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)".

view this post on Zulip Brendan Hansknecht (Apr 18 2023 at 15:30):

I guess maybe this suggests instead of enabling any express in interpolation, just enable a single function application.

view this post on Zulip Georges Boris (Apr 18 2023 at 15:33):

+1 for explicit over Display

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 15:36):

Brendan Hansknecht said:

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)".

Definitely. I think Inspect is super useful for printing errors especially.

view this post on Zulip Luke Boswell (Apr 18 2023 at 18:24):

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.

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 18:24):

That’s what Inspect would be for

view this post on Zulip Agus Zubiaga (Apr 18 2023 at 18:25):

So we’d be able to do crash (Str.inspect err)

view this post on Zulip Luke Boswell (Jul 12 2024 at 07:58):

Brendan Hansknecht said:

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)".

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

view this post on Zulip Sam Mohr (Jul 12 2024 at 07:59):

For now, you can always do "Hello, $(Inspect.toStr thingA)!" on anything that implements Inspect

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:00):

That's my bread and butter for debugging

view this post on Zulip Luke Boswell (Jul 12 2024 at 08:00):

I guess we could hack that in and just Inspect.toStr literally everything including the Str values -- I'll give it a go

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:01):

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?

view this post on Zulip Luke Boswell (Jul 12 2024 at 08:06):

Yeah, I'm thinking about RTL

view this post on Zulip Luke Boswell (Jul 12 2024 at 08:07):

But, the thread/idea is that we enable String interpolation for things that have Inspect

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:09):

I'm against it in general because Inspect is basically implemented for everything, so people will willy nilly print everything in debug format

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:09):

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

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:10):

So any string, by itself, in a tuple or struct, will get quotes

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:10):

I like the idea though

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:11):

Maybe there can be a prefix for interpolations that when passed uses Inspect.toStr

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:11):

Or the other way around, if you'd prefer it to be the default

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:12):

Something like {{$ (123, 456) }}

view this post on Zulip Sam Mohr (Jul 12 2024 at 08:12):

Or maybe {$ ... $} for matching the symmetry of the other handlebars

view this post on Zulip Luke Boswell (Jul 12 2024 at 08:19):

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.

view this post on Zulip Luke Boswell (Jul 12 2024 at 08:20):

Using Inspect.toStr works well, so we still don't need type annotations in the generated roc module.

view this post on Zulip Brendan Hansknecht (Jul 12 2024 at 15:32):

I thinkInspect.toStr should always be explicit, but we still could add a Display equivalent that is implicit in interpolation

view this post on Zulip Brendan Hansknecht (Jul 12 2024 at 15:33):

That said, for something like RTL, I assume you need something special that stops inserting script tags, SQL injection, and what not?

view this post on Zulip Richard Feldman (Jul 12 2024 at 15:53):

yeah I definitely think Inspect.toStr should always be explicit when it comes to interpolation

view this post on Zulip Richard Feldman (Jul 12 2024 at 15:53):

I'd also prefer not to do a Display equivalent

view this post on Zulip Richard Feldman (Jul 12 2024 at 15:55):

I do agree about dbg (and the repl) using Inspect though :thumbs_up:

view this post on Zulip Richard Feldman (Jul 12 2024 at 15:59):

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

view this post on Zulip Richard Feldman (Jul 12 2024 at 16:00):

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

view this post on Zulip Richard Feldman (Jul 12 2024 at 16:02):

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.

view this post on Zulip Richard Feldman (Jul 12 2024 at 16:02):

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

view this post on Zulip Richard Feldman (Jul 12 2024 at 16:03):

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