fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

print and printn alongside printf and printfn

Open Happypig375 opened this issue 2 years ago • 25 comments

print and printn alongside printf and printfn

Currently, F#'s "Hello World" looks like this: printf "Hello World" We currently couple formatting with printing to the console, as introduction. Problems with printf:

  • It forces the notion of "f" - formatting, and all the variations on formatting specifiers
  • It does not take string, so
let x = "Hello world"
printf x

Would surprise beginners, and we have to

let x = "Hello world"
printf "%s" x
  • For formatting we should first introduce string interpolation anyways, since that understanding of formatting specifiers for it is optional.

Compare this with Python's print("Hello World") which has no f to understand.

Moreover, we already have failwith alongside failwithf, but no print alongside printf.

With string interpolation in F# 5, printf formatting should be considered the partial application friendly version of string interpolation when used with lambdas, like function is to fun x -> match x with.

List.iter (printfn "Here is an integer: %d") [1..9]
List.iter (fun i -> printn $"Here is an integer: {i}") [1..9]

I propose that we add two variations of print: print and printn, as printf and printfn variants that take strings. Our "Hello World" is now as simple as Python's, even simpler without parentheses:

print "Hello World"

And this would work:

["String 1"; "String 2"; "String 3"]
|> List.iter print

Pros and Cons

The advantages of making this adjustment to F# are

  1. We can make a good impression with a simple print "Hello World" and avoid introducing printf formatters with the "f" in "printf" too early, rather going for interpolation first, and formatting later with partial application
  2. Have an actual function that can print messages without adding an unnecessary %s and prevent subtleties like
let x = "Hello World"
printf x // Why is this not working!?
  1. Easier piping with pre-interpolated strings, |> List.iter (printfn "%s") vs |> List.iter print

The disadvantages of making this adjustment to F# is another way to do the same thing. However, it can make introductions to beginners easier. Without print, we can still use stdout.Write and stdout.WriteLine but with quirks related to inference of overloaded methods. Moreover, this also requires teaching the dot notation earlier.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): XS

Related suggestions: (none)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • [x] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • [x] I have searched both open and closed suggestions on this site 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 or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

Happypig375 avatar Nov 06 '21 13:11 Happypig375

let x = "Hello World"
printf x // Why is this not working!?

can be written as

let x = "Hello World"
printf $"{x}"

dulanov avatar Nov 06 '21 16:11 dulanov

@dulanov When we have the $ for interpolation, the f "formatting" in printf becomes redundant.

Happypig375 avatar Nov 06 '21 17:11 Happypig375

I like this suggestion. cc @KathleenDollard

dsyme avatar Nov 06 '21 23:11 dsyme

I didn't use old versions of F#, so I've in fact always used string interpolation instead of formatted strings 😄 , even when need to print a single object.

Though print will not add a new line at the end, which... might be a bit counter-intuitive?

WhiteBlackGoose avatar Nov 07 '21 20:11 WhiteBlackGoose

I didn't use old versions of F#, so I've in fact always used string interpolation instead of formatted strings 😄 , even when need to print a single object.

Though print will not add a new line at the end, which... might be a bit counter-intuitive?

print will not add a new line just like printf today does not add a new line. That is the purpose of the also proposed printn function, which will add a new line, just like printfn does currently.

TheJayMann avatar Nov 07 '21 21:11 TheJayMann

I think it's worth a quick survey of some languages with a good learning curve and what all they do:

Python

print("yeet") -- prints "yeet" and a new line print("yeet", end="") -- prints "yeet" without the new line

JavaScript

console.log("yeet") -- prints "yeet" and a new line ...much more complicated to print without the new line, ranging from process.stdout.write("yeet") to implementing a virtual browser console

Go

fmt.Println("yeet") -- prints "yeet" and a new line (recommended via hello world everywhere) fmt.Print("yeet") -- prints "yeet" but without a new line fmt.Printf("yeet") -- more general-purpose printer with format strings, does not append a new line

Swift

print("yeet") -- prints "yeet" and a new line print("yeet", terminator:"") -- prints "yeet" with no new line

I've excluded C/C++/Java/C# and don't think they're worth looking at.

This suggestion would make F# the most like Go in this list:

  • print - print to console
  • printn - prints to console with newline
  • printf - print with format string
  • printfn - print with format string with newline

I think that's the most reasonable, since changing this pattern will probably just get confusing to beginners once they get into formatted printing anyways.

cartermp avatar Nov 07 '21 22:11 cartermp

This is great. Currently to understand the typical beginning F# program printf "Hello world" (including the types involved), one needs to learn about type-directed inference and TextWriterFormat, concepts that are not even included in "Expert F#". This addition will mean that being an F# expert is not necessary to start learning F#.

charlesroddie avatar Nov 09 '21 21:11 charlesroddie

Marking this as approved-in-principle. Since the design is simple by all means proceed straight to an RFC and PR :)

dsyme avatar Nov 09 '21 22:11 dsyme

I'd like us to consider println instead of printn

printn parallels printfn, sure. But, for a beginner, what the heck is "n"? "ln" is clearly an abbreviation for "line".

I could also see printline

But it's a great idea, and other than thinking we should reconsider syntax, I love it!

KathleenDollard avatar Nov 10 '21 16:11 KathleenDollard

It feels like for consistency we have to use printn. It feels wrong to have all of println, printfn and WriteLine in the experience. But let's consider.

dsyme avatar Nov 10 '21 16:11 dsyme

I see parallels between println and printfn in that fn would expand to formatted ln, but n itself can also be interpreted as newline too. Hmm

Happypig375 avatar Nov 10 '21 17:11 Happypig375

How about write and writeLine? Rather than 4 print* functions that differ by 2 chars (not to mention kprintf/sprintf/etc. variations), it would leave the printf "family" of functions isolated for traditional formatting, and have a more distinct separation for the new functions.

zanaptak avatar Nov 12 '21 00:11 zanaptak

For comparison, I counted what other languages commonly use (see Rosetta for with and without newline):

With newline (520 examples)

  • printfn: 1x (only F#)
  • print: ~100-150x (hard to measure, quite a few require a \n explicitly added, and some others don't)
  • printn: 1x
  • println: 48x
  • put: 9x
  • puts: ~20x
  • putln: 1x

Some have other syntax, obviously, like write or cout, but let's stick to the variants closest to what we already have.

Without newline (171 examples)

  • print: ~70-90x (again hard to measure, but ~35-50% of all languages)
  • prints: 1x
  • printf: ~20x
  • puts: 3x
  • put: 5x

All in all, it appears that languages that have print and println are far in the majority. Some only have print, sometimes with optional args for the terminator.

I know that we don't "have to do what other languages do" just for the sake of it, but for easy discovery, and considering that printn is only used by one obscure language, my vote is for the pair of print and println as that seems by far the most common in the world.

abelbraaksma avatar Mar 27 '22 16:03 abelbraaksma

There should be no syntax for printing without a new line, as that just creates a pitfall for users. Printing without a new line is an very rare thing to want to do. Most of the time someone writes *printf, the output is unexpected and that *printfn is intended.

For printing (with a newline) I like print, printLine, printLn, printn in that order, noting that there will be very few places where it is best to use an old *printn function given this suggestion and existing interpolated strings, so existing precedents in F# don't need to outweigh what is common in other languages.

charlesroddie avatar Mar 27 '22 21:03 charlesroddie

Should eprint and eprintln be considered as well, for parity's sake? They're less likely to be used by beginners, but it would seem somewhat odd not to keep the existing stdoutstderr symmetry.

brianrourkeboll avatar Aug 01 '22 19:08 brianrourkeboll

stderr seems to be a much more advanced concept than just the console output. Would introducing this be desirable?

Will bprintfn/fprintfn etc have parity as well? When to stop?

Happypig375 avatar Aug 02 '22 06:08 Happypig375

For future reference, the final RFC 1125 is here, in case you missed it. And a PR is underway, see https://github.com/dotnet/fsharp/pull/13597, great work @albert-du!

abelbraaksma avatar Aug 03 '22 23:08 abelbraaksma

Based on the PR discussion thread I've clarified a number of important unresolved issues regarding the PR - most notably whether the functions should be generic, and if so what should their spec be.

@KevinRansom feels strongly that we should not add non-generic print given the potential future utility of a generic print - if we can iron out enough wrinkles. Despite having approved the simple non-generic version in this suggestion I can see his point. Additionally, the reliance on interpolated strings print $"..." to print arbitrary objects means the wrinkles concerning %A formatting come more to the fore if we add the generic version of this, again discussed in https://github.com/fsharp/fslang-suggestions/issues/897 and https://github.com/dotnet/fsharp/pull/13597.

I think this puts the RFC and this suggestion on ice somewhat.

dsyme avatar Aug 16 '22 21:08 dsyme

@dsyme, I noticed that you reverted the RFC's function back from println to printn. I know it is only one letter, but you stated above "Let's consider", and in this comment I showed what common names were "in the wild" for such functions.

To follow the principle of least surprise, this would bring us to print & println (150+ and 50-something times resp.), which is well understood, where printn happens to exist in only one other language.

I'll certainly be able to live with printn though, I just find it a very odd name and think parity with existing practice in other language (when and where applicable) makes sense for easier adoption. And I'm not alone in that opinion.

abelbraaksma avatar Aug 16 '22 22:08 abelbraaksma

@abelbraaksma Yes the consistency argument is too strong IMHO - I just can't justify another abbreviation for "Line" in FSharp.Core. People got used to printfn (which was introduced by F#), they'd get used to printn.

In any case, the title of this suggestion was always print and printn, and to the extent we can go ahead with it at all we should stick to that.

dsyme avatar Aug 16 '22 23:08 dsyme

Ok, fair enough. In the end there'll always be tooling that shows what we have once we start typing pri, so the burden on newcomers won't be that large.

abelbraaksma avatar Aug 17 '22 09:08 abelbraaksma

For reference, here's a good motivational example of how newcomers (still) struggle with this: https://stackoverflow.com/questions/73416468/hello-printfn-generates-an-error-in-f.

abelbraaksma avatar Aug 19 '22 13:08 abelbraaksma

I’m a fairly seasoned C# and python developer and tonight attempted my first foray into F# to potentially use as a tool to teach undergrad intro stats. I currently use either Python or R.

Based on my research and initial trials it has huge potential over python. No virtual environments, easy to install, no anaconda, no silly dynamic typing. No silly this everywhere in classes. 😃

However, I spent literally an hour trying to figure out why you had to do string formatting to print simple things. I even made a stackoverflow question before stumbling upon this thread. https://stackoverflow.com/q/73823075/4163879

I really support this thread. However, I really hope you all consider just making print() do what everyone expects it to do and not even include println. Imagine trying to teach a class of 90 undergrads with no programming experience why print is NOT what that want, and instead that they need to use println. 🧐“Why?!” They will inevitably ask. To which my answer will be, “because the world hates you and doesn’t want you to learn programming”.

I can already picture 3 students coming to me after the first class in tears because they need to pass my class to continue their degree and their program isn’t working because print doesn’t add a new line. 😓

Do the right thing. Anyone who wants to print without a new line will already have the skills required to Google it and figure out another function. F# has so much potential to beginners. Please make it easy on them. Add print, and make it add a new line. I implore you! 🙏

AdamBebko avatar Sep 23 '22 05:09 AdamBebko

@AdamBebko Thank you for this feedback!

dsyme avatar Oct 27 '22 18:10 dsyme