Skip to content

Add generic print and println functions to FSharp.Core#19265

Open
bbatsov wants to merge 4 commits into
dotnet:mainfrom
bbatsov:feature/print-println
Open

Add generic print and println functions to FSharp.Core#19265
bbatsov wants to merge 4 commits into
dotnet:mainfrom
bbatsov:feature/print-println

Conversation

@bbatsov

@bbatsov bbatsov commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This is another take on RFC FS-1125, following up on the closed #13597. I guess that's mostly an attempt to see if anyone's interested in driving the old proposal to the finish line. For me those would definitely be handy functions to have, but obviously they are not something very important.

The key difference from the original PR is that print and println are generic ('T -> unit) rather than string -> unit, addressing the feedback from @dsyme that a generic print function would be a better use of the "good name":

let print (value: 'T) = Console.Out.Write(string value)
let println (value: 'T) = Console.Out.WriteLine(string value)

Both functions use the existing string operator for conversion, which already:

  • Uses InvariantCulture for IFormattable types (consistent with printfn, sprintf, etc.)
  • Passes strings through as-is
  • Falls back to .ToString() for other types (F# types like Option, List, etc. have good .ToString() overrides)

Changes

  • Added print and println signatures with XML docs to fslib-extra-pervasives.fsi
  • Added implementations in fslib-extra-pervasives.fs
  • Added unit tests (PrintTests.fs) covering: string, int, float, bool, Option, None, list, newline behavior, and concatenation
  • Updated all 4 surface area baselines

Open questions

  • What about eprintf(n) and fprintf(n)? Do they generic counterparts as well?

@github-actions

github-actions Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/FSharp.Core docs/release-notes/.FSharp.Core/11.0.100.md

Comment thread src/FSharp.Core/fslib-extra-pervasives.fs
@bbatsov bbatsov force-pushed the feature/print-println branch from 53c25f7 to c00b32e Compare February 14, 2026 13:30
@vzarytovskii

Copy link
Copy Markdown
Member

Will also probably want IL baseline tests to make sure correct overloads are getting called.

@bbatsov

bbatsov commented Feb 14, 2026

Copy link
Copy Markdown
Contributor Author

I've added some IL tests - hopefully I got those right. First contributions as always the hardest... 😅

@vzarytovskii

vzarytovskii commented Feb 14, 2026

Copy link
Copy Markdown
Member

I've added some IL tests - hopefully I got those right. First contributions as always the hardest... 😅

Yeah, those are the ones I meant. However, I do see that for some of them it is calling ToString for some value types (i32 for example), and calls string overload for the Write(Line)

@brianrourkeboll

Copy link
Copy Markdown
Contributor

However, I do see that for some of them it is calling ToString for some value types (i32 for example), and calls string overload for the Write(Line)

That's inherent to the approach taken in this PR (calling string on the input and passing it to Console.WriteLine), no?

We could delegate to the appropriate overload of Console.WriteLine at compile time by using static optimizations for common types (like the string function itself does).

@vzarytovskii

vzarytovskii commented Feb 14, 2026

Copy link
Copy Markdown
Member

However, I do see that for some of them it is calling ToString for some value types (i32 for example), and calls string overload for the Write(Line)

That's inherent to the approach taken in this PR (calling string on the input and passing it to Console.WriteLine), no?

Yes, I forgot to look at the implementation tbh.

We could delegate to the appropriate overload of Console.WriteLine at compile time by using static optimizations for common types (like the string function itself does).

We probably should, jit might lift some allocations to stack, but we shouldn't rely on it.

@bbatsov

bbatsov commented Feb 14, 2026

Copy link
Copy Markdown
Contributor Author

I think I'll need some more pointers about the next step, as I'm not quite sure how to proceed from here.

@Happypig375

Copy link
Copy Markdown
Member

@bbatsov consider looking at the implementation for the string function and use a similar syntax for Console.WriteLine overloads.

@bbatsov

bbatsov commented Feb 15, 2026

Copy link
Copy Markdown
Contributor Author

I've added static optimizations for string, char, and bool — these types are culture-independent, so we can call the corresponding TextWriter.Write/WriteLine overload directly, bypassing the string operator entirely.

For numeric types (int, float, decimal, etc.), we must continue going through the string operator to ensure InvariantCulture formatting. TextWriter.Write(int) internally calls value.ToString(FormatProvider), and Console.Out.FormatProvider is CultureInfo.CurrentCulture — so delegating directly would break InvariantCulture consistency for negative numbers and floating-point values in some locales.

The IL tests now verify:

  • int/floatToString(string, IFormatProvider) with InvariantCulture + Write(string)
  • string → direct Write(string) (no null check overhead)
  • char → direct Write(char)
  • bool → direct Write(bool)

I hope I got those right. I never thought a couple of print functions could be so involved. 😅

@bbatsov bbatsov force-pushed the feature/print-println branch from 485c8dc to 43b247f Compare March 5, 2026 10:35
@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Mar 6, 2026
@T-Gro T-Gro enabled auto-merge (squash) March 6, 2026 22:10
auto-merge was automatically disabled March 16, 2026 14:01

Head branch was pushed to by a user without write access

@bbatsov bbatsov force-pushed the feature/print-println branch 4 times, most recently from 2db7c71 to 5833768 Compare March 16, 2026 14:50
@bbatsov

bbatsov commented Mar 16, 2026

Copy link
Copy Markdown
Contributor Author

In case that matters - I rebased the PR on top of the current main. Also - should the release notes be in the file for F# 10 or 11? The CI checks seems to look for them in 11.

@T-Gro

T-Gro commented Mar 17, 2026

Copy link
Copy Markdown
Member

Release notes for 11 (it will now be more stable for requiring 11 for about half a year).

@bbatsov

bbatsov commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

Just moved the release notes to (hopefully) the right place.

@T-Gro T-Gro enabled auto-merge (squash) March 17, 2026 21:03
auto-merge was automatically disabled May 12, 2026 16:33

Head branch was pushed to by a user without write access

@bbatsov bbatsov force-pushed the feature/print-println branch from 11ad62e to 72c8b44 Compare May 12, 2026 16:33
@esbenbjerre

Copy link
Copy Markdown
Contributor

The RFC describes print and printn functions but this PR uses println - I thought it was decided to go with printn to be consistent with printfn, no?
Looks great otherwise - is there anything else blocking this?

@bbatsov

bbatsov commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@esbenbjerre Good point! I went with println by inertia and because this was the name used in #13597, but I can change it if that would be preferable.

@charlesroddie

Copy link
Copy Markdown

I agree go with the RFC printn.

/// </code>
/// </example>
[<CompiledName("PrintValueLine")>]
val inline println: value: 'T -> unit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(doing this to prevent accidental merging before renaming and getting rid of the l. Thanks everyone for being vigilant 👍 )

@bbatsov

bbatsov commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Now that we're aligned on the naming I'll update the PR soon.

bbatsov added 2 commits June 22, 2026 16:21
Add inline `print: 'T -> unit` and `printn: 'T -> unit` to
ExtraTopLevelOperators. These use the existing `string` function for
conversion (InvariantCulture for IFormattable, .ToString() fallback)
and write to Console.Out.

RFC FS-1125
Verify that inline specialization produces direct calls to
Int32.ToString, Double.ToString with InvariantCulture, and
Console.Out.Write/WriteLine for the respective types.
@bbatsov bbatsov force-pushed the feature/print-println branch from 72c8b44 to 1344cb9 Compare June 22, 2026 13:48
@bbatsov

bbatsov commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Renamed printlnprintn to match the RFC and stay consistent with printfn. Folded it into the existing commits and rebased on latest main, so there's no stray println left in the history. CompiledName stays PrintValueLine (same as printfnPrintFormatLine), so the surface area baselines are untouched. Thanks @esbenbjerre @charlesroddie @T-Gro for the nudge.

@T-Gro

T-Gro commented Jun 23, 2026

Copy link
Copy Markdown
Member

Just run fantomas now and it is ready to go 👍

bbatsov added 2 commits June 23, 2026 19:59
For culture-independent types (string, char, bool), bypass the
`string` operator and call the appropriate TextWriter.Write/WriteLine
overload directly.

Numeric types (int, float, etc.) must still go through `string` to
ensure InvariantCulture formatting, since TextWriter.Write(int/float)
uses the writer's FormatProvider which is CurrentCulture for Console.Out.

Add IL tests verifying char and bool use their direct overloads.
@bbatsov bbatsov force-pushed the feature/print-println branch from 1344cb9 to 0f9b3ba Compare June 23, 2026 17:00
@bbatsov

bbatsov commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Build's green now. The formatting failure wasn't actually about the rename - it was the static-optimization when clauses in print/printn, which Fantomas 7.0.1 can't format. That's the same limitation that already keeps prim-types.fs, printf.fs and friends in .fantomasignore (they use the same when ^T : ... = ... construct), so I added this file there too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

7 participants