fsharp / fsharp/fslang-suggestions

Possible plain-text formatting improvements

Open
#897 9 comments 4 reactions 0 assignees View on GitHub
approved-in-principle area: library
Dominant language
No language data
Stars
373
Forks
21
PR merge metrics
No merged PRs in 30d

Description

I'd like to start a thread documenting possible improvements to plain-text formatting in F#. This is driven partly my [doing a deep look at getting quality plaintext printing for F# in .NET Interactive Notebooks](https://github.com/dotnet/interactive/issues/642)

The current state of plain-text formatting in F# is documented in these articles:

* `printf` printing - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting

* F# Interactive output - https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/fsharp-interactive-options

The possible adjustments I've spotted are:

1. Be more consistent about using globalization settings (default to InvariantCulture)

In all formatting (both `%A` and F# Interactive), be more consistent about globalization settings for things of type IFormattable. In particular, [arbitrary .NET values](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting#net-values) currently format using CurrentCulture settings, where numeric values do not. This is inconsistent

2. Find a way to allow `printf` formatting to use user-defined culture settings. This is partly possible with interpolated strings using FormattableString, but that can't use `%d`, `%A` etc. patterns.

3. Respect `DebuggerBrowsableAttribute` `DebuggerBrowsableState.Never` in F# Interactive printing to automatically suppress the printing of some properties. [.NET Interactive does this](https://github.com/dotnet/interactive/issues/692#issuecomment-670918750)

4. Allow multi-line `%A` printing to respect indentation. So for example `sprintf "fooo %*A goo" v` would put the format of `v` indented 5 spaces if it is multi-line. Currently multiline %A printing doesn't respect indentation (see [here](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting#primitive-values) and this makes outputs look much messier unfortunately).

Further. some limited "break if necessary specifications" may be allowed, so something like `sprintf "tensor [%40*4A]" v` might allow a line break to be inserted if the value hits 40 characters width. These are really useful though not entirely suited to specification in printf strings.

Alternatively we could expose a variation of the box layout code available for us internally in FSharp.Core as an API independent of sprintf.

5. Implement `ToString()` on F# function and other closure values so they do more than print the obscure closure name - as least add some indication the thing is a function

6. Allow `%f`, `%A` and friends to optionally take an additional specifier to force CurrentCulture, e.g. `%$d`, `%$A` (or some other character). There could also be a corresponding option to force InvariantCulture, e.g. `%!d`, `%!A`

```fsharp
// %$f ---> Use the culture from context (UI or FormattableString formatting operation)
// %!f ---> Use invariant culture
// %f == %!f

// %$A ---> For IFormattable, use the culture from context (UI or FormattableString formatting operation)
// %!A ---> For IFormattable, use invariant culture
// %A != %!A
// %A != %$A

// %$O ---> For IFormattable, use the culture from context (UI or FormattableString), otherwise ToString()
// %!O ---> For IFormattable, use invariant culture, otherwise ToString()
// %O == %$O
```

7. Allow `%A` (or some API access point to the same functionality) take a (varying) line width as a parameter, e.g. `%*A` or have `%A` respect Console.WindowWidth by default

8. Allow `%A` (or some API access point to the same functionality) to take a specifier that disables PrintLength for strings and collections.

9. Allow `%A` (or some API access point to the same functionality) to take a specifier that gives colorized output

10. Give a warning when a mix of culture-aware and culture-invariant format specifiers are used in a single interpolated string.

One overall aim may be to allow for the satisfactory implementation of a generic print using

```fsharp
/// Generic unstructured culture-invariant single-line string
let string x = ...

/// Generic structured culture-invariant multi-line colorized
/// output fitting to console width
let print x = ...

/// Generic structured culture-invariant multi-line colorized
/// output fitting to console width with new line
let printn x = ...
```
Plus this:
```fsharp
module CurrentCulture =
/// Generic unstructured culture-aware single-line string
let string x = ...

/// Generic structured culture-aware multi-line colorized
/// output fitting to console width
let print x = ...

/// Generic structured culture-aware multi-line colorized
/// output fitting to console width with new line
let printn x = ... ```
```

This would raise questions about whether `CurrentCulture` module would have functions like `int` and `double` for parsing strings using current culture (the default `int` and `double` parse using invariant culture).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.