Skip to content

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

Merged
T-Gro merged 7 commits into
dotnet:mainfrom
bbatsov:feature/print-println
Aug 18, 2026
Merged

Add generic print and println functions to FSharp.Core#19265
T-Gro merged 7 commits into
dotnet:mainfrom
bbatsov:feature/print-println

Conversation

@bbatsov

@bbatsovbbatsov 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":

letprint(value:'T)= Console.Out.Write(string value)letprintln(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-actionsBot 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 pathRelease notes pathDescription
`src/FSharp.Core`docs/release-notes/.FSharp.Core/11.0.100.md

Comment threadsrc/FSharp.Core/fslib-extra-pervasives.fs
@bbatsov
bbatsovforce-pushed the feature/print-println branch from 53c25f7 to c00b32eCompareFebruary 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

Copy link
Copy Markdown
ContributorAuthor

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

Copy link
Copy Markdown
ContributorAuthor

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
ContributorAuthor

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
bbatsovforce-pushed the feature/print-println branch from 485c8dc to 43b247fCompareMarch 5, 2026 10:35
@github-project-automationgithub-project-automationBot moved this from New to In Progress in F# Compiler and ToolingMar 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
bbatsovforce-pushed the feature/print-println branch 4 times, most recently from 2db7c71 to 5833768CompareMarch 16, 2026 14:50
@bbatsov

Copy link
Copy Markdown
ContributorAuthor

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

Copy link
Copy Markdown
Member

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

@bbatsov

Copy link
Copy Markdown
ContributorAuthor

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
bbatsovforce-pushed the feature/print-println branch from 11ad62e to 72c8b44CompareMay 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
bbatsovforce-pushed the feature/print-println branch from 72c8b44 to 1344cb9CompareJune 22, 2026 13:48
@bbatsov

Copy link
Copy Markdown
ContributorAuthor

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

Copy link
Copy Markdown
Member

Just run fantomas now and it is ready to go 👍

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
bbatsovforce-pushed the feature/print-println branch from 1344cb9 to 0f9b3baCompareJune 23, 2026 17:00
@bbatsov

Copy link
Copy Markdown
ContributorAuthor

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.

@bbatsov

Copy link
Copy Markdown
ContributorAuthor

🤞 I got everything right this time around. If this gets merged I'm never using printfn again. 😂

@charlesroddie

Copy link
Copy Markdown
Contributor

Only thing left is to click the merge button @T-Gro@vzarytovskii ?

@github-actionsgithub-actionsBot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Aug 14, 2026
@T-Gro
T-Gro enabled auto-merge (squash) August 18, 2026 07:49
@T-Gro
T-Gro requested a review from abonieAugust 18, 2026 07:50
@bbatsov

Copy link
Copy Markdown
ContributorAuthor

Seems this now needs one more review to be merged. I hope @vzarytovskii can help with this.

@T-Gro
T-Gro merged commit f8287a1 into dotnet:mainAug 18, 2026
49 checks passed
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in F# Compiler and ToolingAug 18, 2026
@bbatsov

Copy link
Copy Markdown
ContributorAuthor

🎉

I'm really glad we managed to drive this to the finish line! Thanks, everyone! 🙇

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

Labels

AI-Tooling-Check-Scanned-CleanTooling check: diff analyzed, no interesting infrastructure files

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

8 participants

@bbatsov@vzarytovskii@brianrourkeboll@Happypig375@T-Gro@esbenbjerre@charlesroddie@abonie