Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Core/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Added

* Added generic `print` and `println` functions (`'T -> unit`) to `ExtraTopLevelOperators` for simple value printing to stdout. ([RFC FS-1125](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1125-print-printn-functions.md), [PR #19265](https://github.com/dotnet/fsharp/pull/19265))

### Changed

* Added complexity documentation (Big-O notation) to all 462 functions across Array, List, Seq, Map, and Set collection modules. ([PR #19240](https://github.com/dotnet/fsharp/pull/19240))
Expand Down
17 changes: 17 additions & 0 deletions src/FSharp.Core/fslib-extra-pervasives.fs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,23 @@ module ExtraTopLevelOperators =
let eprintfn format =
Printf.eprintfn format

[<CompiledName("PrintValue")>]
let inline print (value: 'T) =
Console.Out.Write(string value)
// Culture-independent types can bypass 'string' and use TextWriter overloads directly.
// Numeric types must go through 'string' to ensure InvariantCulture formatting,
// since TextWriter.Write(int/float/...) uses the writer's FormatProvider (CurrentCulture).
when 'T : string = Console.Out.Write((# "" value : string #))
when 'T : char = Console.Out.Write((# "" value : char #))
when 'T : bool = Console.Out.Write((# "" value : bool #))

[<CompiledName("PrintValueLine")>]
let inline println (value: 'T) =
Console.Out.WriteLine(string value)
when 'T : string = Console.Out.WriteLine((# "" value : string #))
when 'T : char = Console.Out.WriteLine((# "" value : char #))
when 'T : bool = Console.Out.WriteLine((# "" value : bool #))

Comment on lines 280 to 296
Copy link
Member

Choose a reason for hiding this comment

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

Should functions be inline?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, @bbatsov pls mark at as inline so that it can specialize around the string call for certain types and avoid a .ToString() call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do!

[<CompiledName("DefaultAsyncBuilder")>]
let async = AsyncBuilder()

Expand Down
27 changes: 27 additions & 0 deletions src/FSharp.Core/fslib-extra-pervasives.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,33 @@ module ExtraTopLevelOperators =
[<CompiledName("PrintFormatLineToError")>]
val eprintfn: format: Printf.TextWriterFormat<'T> -> 'T

/// <summary>Converts the value to a string using the <c>string</c> operator and writes it to the standard output.</summary>
///
/// <param name="value">The value to print.</param>
///
/// <example id="print-example">
/// <code lang="fsharp">
/// print "Hello, "
/// print "World!"
/// // output: Hello, World!
/// </code>
/// </example>
[<CompiledName("PrintValue")>]
val inline print: value: 'T -> unit

/// <summary>Converts the value to a string using the <c>string</c> operator and writes it to the standard output, followed by a newline.</summary>
///
/// <param name="value">The value to print.</param>
///
/// <example id="println-example">
/// <code lang="fsharp">
/// println "Hello, World!"
/// // output: Hello, World!
/// </code>
/// </example>
[<CompiledName("PrintValueLine")>]
val inline println: value: 'T -> unit

/// <summary>Print to a string using the given format.</summary>
///
/// <param name="format">The formatter.</param>
Expand Down
98 changes: 98 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/EmittedIL/PrintFunction.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace EmittedIL

open Xunit
open FSharp.Test.Compiler

module PrintFunction =

[<Fact>]
let ``print with int specializes to Int32 ToString with InvariantCulture``() =
FSharp """
module PrintInt

let printInt () = print 42
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()"""
"""call instance string [netstandard]System.Int32::ToString(string,"""
"""callvirt instance void [netstandard]System.IO.TextWriter::Write(string)"""]

[<Fact>]
let ``println with int specializes to Int32 ToString with InvariantCulture``() =
FSharp """
module PrintlnInt

let printlnInt () = println 42
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()"""
"""call instance string [netstandard]System.Int32::ToString(string,"""
"""callvirt instance void [netstandard]System.IO.TextWriter::WriteLine(string)"""]

[<Fact>]
let ``print with string calls Write directly``() =
FSharp """
module PrintString

let printStr () = print "hello"
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""callvirt instance void [netstandard]System.IO.TextWriter::Write(string)"""]

[<Fact>]
let ``print with char calls Write char overload``() =
FSharp """
module PrintChar

let printChar () = print 'A'
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""callvirt instance void [netstandard]System.IO.TextWriter::Write(char)"""]

[<Fact>]
let ``print with bool calls Write bool overload``() =
FSharp """
module PrintBool

let printBool () = print true
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""callvirt instance void [netstandard]System.IO.TextWriter::Write(bool)"""]

[<Fact>]
let ``println with float specializes to Double ToString with InvariantCulture``() =
FSharp """
module PrintlnFloat

let printlnFloat () = println 3.14
"""
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
"""call class [netstandard]System.IO.TextWriter [netstandard]System.Console::get_Out()"""
"""call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture()"""
"""call instance string [netstandard]System.Double::ToString(string,"""
"""callvirt instance void [netstandard]System.IO.TextWriter::WriteLine(string)"""]
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
<Compile Include="EmittedIL\EmptyArray.fs" />
<Compile Include="EmittedIL\Enums.fs" />
<Compile Include="EmittedIL\Literals.fs" />
<Compile Include="EmittedIL\PrintFunction.fs" />
<Compile Include="EmittedIL\NoCompilerInlining.fs" />
<Compile Include="EmittedIL\RealInternalSignature\ModuleVisibility.fs" />
<Compile Include="EmittedIL\RealInternalSignature\ModuleInitialization.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,8 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToStringThenFail[T,TR
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToString[T](Microsoft.FSharp.Core.PrintfFormat`4[T,Microsoft.FSharp.Core.Unit,System.String,System.String])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormatToTextWriter[T](System.IO.TextWriter, Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T PrintFormat[T](Microsoft.FSharp.Core.PrintfFormat`4[T,System.IO.TextWriter,Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit])
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValue[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: Void PrintValueLine[T](T)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceExpression[T](Microsoft.FSharp.Quotations.FSharpExpr`1[T])
Microsoft.FSharp.Core.ExtraTopLevelOperators: T SpliceUntypedExpression[T](Microsoft.FSharp.Quotations.FSharpExpr)
Microsoft.FSharp.Core.ExtraTopLevelOperators: T[,] CreateArray2D[a,T](System.Collections.Generic.IEnumerable`1[a])
Expand Down
1 change: 1 addition & 0 deletions tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<None Include="FSharp.Core\Microsoft.FSharp.Core\IntConversionsTestGenerator.fsx" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Core\OptionModule.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Core\PrintfTests.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Core\PrintTests.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Core\ResultTests.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Core\ExtraTopLevelOperatorsTests.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Control\EventTypes.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

// Various tests for:
// Microsoft.FSharp.Core.ExtraTopLevelOperators.print
// Microsoft.FSharp.Core.ExtraTopLevelOperators.println

namespace FSharp.Core.UnitTests

open System
open System.IO
open Xunit

[<Collection(nameof FSharp.Test.NotThreadSafeResourceCollection)>]
type PrintTests() =

let captureConsoleOut (f: unit -> unit) =
let oldOut = Console.Out
use sw = new StringWriter()
Console.SetOut(sw)
try
f ()
sw.ToString()
finally
Console.SetOut(oldOut)

[<Fact>]
member _.``print writes string value``() =
let result = captureConsoleOut (fun () -> print "hello")
Assert.Equal("hello", result)

[<Fact>]
member _.``print writes integer value``() =
let result = captureConsoleOut (fun () -> print 42)
Assert.Equal("42", result)

[<Fact>]
member _.``print writes float with InvariantCulture``() =
let result = captureConsoleOut (fun () -> print 3.14)
Assert.Equal("3.14", result)

[<Fact>]
member _.``print writes bool value``() =
let result = captureConsoleOut (fun () -> print true)
Assert.Equal("True", result)

[<Fact>]
member _.``print writes Some value``() =
let result = captureConsoleOut (fun () -> print (Some 42))
Assert.Equal("Some(42)", result)

[<Fact>]
member _.``print writes None value``() =
let result = captureConsoleOut (fun () -> print None)
Assert.Equal("", result)

[<Fact>]
member _.``print writes list value``() =
let result = captureConsoleOut (fun () -> print [1; 2; 3])
Assert.Equal("[1; 2; 3]", result)

[<Fact>]
member _.``println writes value followed by newline``() =
let result = captureConsoleOut (fun () -> println "hello")
Assert.Equal("hello" + Environment.NewLine, result)

[<Fact>]
member _.``multiple prints concatenate``() =
let result = captureConsoleOut (fun () ->
print "Hello, "
print "World!")
Assert.Equal("Hello, World!", result)

[<Fact>]
member _.``println writes integer with newline``() =
let result = captureConsoleOut (fun () -> println 42)
Assert.Equal("42" + Environment.NewLine, result)
Loading