fsharp / fsharp/fslang-suggestions

Better (prettier) output for printing lists of records

Open
#547 6 comments 17 reactions 0 assignees View on GitHub
approved-in-principle area: tooling
Dominant language
No language data
Stars
373
Forks
21
PR merge metrics
No merged PRs in 30d

Description

I propose we improve the output of `printfn "%A" listOfRecords` by preventing it from "stair-stepping".

Currently, if you run the following code:

```fsharp
type CityAndState = { City : string; State : string }
let cities = [{City="Seattle";State="WA"};{City="New York";State="NY"};{City="Chicago";State="IL"}]
printfn "%A" cities
```

The output will be:

```fsharp
[{City = "Seattle";
State = "WA";}; {City = "New York";
State = "NY";}; {City = "Chicago";
State = "IL";}]
```

What I'd like to see instead:

```fsharp
[{City = "Seattle";
State = "WA";};
{City = "New York"}
State = "NY";};
{City = "Chicago";
State = "IL";}]
```

Or possibly omitting the semicolons that aren't necessary when things are lined up vertically (note that this example *also* omits the unnecessary semicolons in record rendering, which isn't strictly part of this proposal but would also make sense to do):

```fsharp
[{City = "Seattle"
State = "WA"}
{City = "New York"}
State = "NY"}
{City = "Chicago"
State = "IL"}]
```

The specific proposal I'm making is to change the `%A` rendering logic for lists (and sequences and arrays) as follows:

- If the items in the list render on one line (e.g., numbers or strings, whose rendered form does not contain a newline), then use the existing rendering logic, where items are separated by a semicolon and a space.
- If the items in the list render on more than one line (their rendered form contains a newline), such as is the case with records, then line the items up vertically, with the start of each item in the same character position (so in the example, since the opening `{` of the first record was in column 2, each subsequent record is indented by 1 space so that each record's opening `{` will be in column 2).
- Since it would probably be expensive to check the rendering of *every* item in the list, check only how the first item renders before making the decision about whether to separate items with semicolons or newlines-plus-indentation. Since lists must be homogenous, this will produce good results *most* of the time, and any time when it doesn't produce good results, it will look no worse than the current stair-stepping.
- **Optionally**, if the list is rendering each item on a new line, the semicolons between items could still be present rather than being removed. I don't know the internals of how a list renders itself for the `%A` format specifier, so if it would be expensive or complicated to omit semicolons when a newline is inserted, then the semicolons could be kept. The main thrust of my proposal is to eliminate the stair-stepping; if there's a good reason to keep the semicolons when the stair-stepping is eliminated, then keep them.

This change would apply to all uses of the `%A` format specifier: `printfn`, `sprintf`, `kprintf`, etc.

## Pros and Cons

The advantages of making this adjustment to F# are that lists (and seqs and arrays) would have a nicer rendering in FSI. I was inspired by seeing the FSI output in the lower right corner of the screen during [this "Intro to F#" presentation](https://youtu.be/dtRhIRhWd6Q?t=756): the stair-stepping looked ugly, and if I were an F# newbie, it would have distracted me from getting the rest of his point. Since he was demonstrating type providers (one of F#'s great strengths) at the time, that would have been a shame.

The disadvantages of making this adjustment to F# are that large lists might take even more vertical screen space at this rate, so a cutoff of (say) 100 lines might be needed instead of 100 items. Or maybe the cutoff could be dependent on whether the items are vertically or horizontally arranged: keep the cutoff at 100 items for lists whose items are arranged horizontally, but if the items are arranged vertically, drop the cutoff down to 20 or 30 items.

## Extra information

Estimated cost (XS, S, M, L, XL, XXL): M? Maybe S, depending on what the current code looks like.

Related suggestions: None found

## Affidavit (must be submitted)

Please tick this by placing a cross in the box:
* [X] This is not a question (e.g. like one you might ask on [Stack Overflow](http://stackoverflow.com)) and I have searched Stack Overflow for discussions of this issue
* [X] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [X] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:
* [X] This is not a breaking change to the F# language design
* [X] I would be willing to help implement and/or test this
* [ ] I or my company would be willing to help crowdfund F# Software Foundation members to work on this

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by locating the `%A` rendering logic for lists, sequences, and arrays, and trace how multiline records are formatted. Compare the first-item-based layout proposal and its semicolon and cutoff alternatives; done means multiline items no longer stair-step while one-line items retain current separators across printfn, sprintf, and kprintf.

Written by the indexing model from the issue text.

Assessment

Tech stack
fsharp
Domain
developer-experience
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.