Skip to content

Instantly share code, notes, and snippets.

@palpha
Last active August 20, 2021 05:53
Show Gist options
  • Save palpha/8999787 to your computer and use it in GitHub Desktop.
Save palpha/8999787 to your computer and use it in GitHub Desktop.
On parentheses in F#

There's a blog post currently making the rounds, about a developer's experience of using F# for anything serious for the first time: http://www.knowing.net/index.php/2014/02/13/notes-on-my-first-real-f-program/

Among other things, it touches the subject of parentheses. This is a subject that seems to be widely misunderstood, and the blog post exhibits one such misunderstanding by calling parentheses in F# optional.

I will try to bring some sanity to the table, but am fully aware that I don't know a fraction of what I would need to know in order to call myself an expert in F#. Comments and corrections are very welcome (bergius on Twitter).

Parentheses in F#

TL;DR: It's all about precedence and associativity. It has nothing to do with differences between methods and functions. It is not the parentheses that are optional, but the spaces between parentheses and other things.

A function in F# takes exactly one argument. A C# method with multiple parameters is exposed to F# as a function that takes a tuple. That really should be everything that needs to be said, but for some reason most people still format their F# like this:

System.Console.WriteLine("I {0} know which language I'm using!", "don't")

(Naturally, no one would actually use that kind of string formatting instead of Printf; it's just an example.)

If you had functions like these:

let f (x: int * int) (y: int) = (* some body *) ()
let f2 x y = (* some body *) ()

Would you call them like this:

f(1, 2)3
(((((((f)<|((1),(2)))(3))))))
let x = 1, 2
f(x) 3
f2("foo")("bar")

Or like this:

f (1, 2) 3
f2 "foo" "bar"

If the latter, think about your other uses of parentheses. Here's the style that follows naturally from how the language actually works:

let f () = (* some body *) () // val f : unit -> unit
let f2 x = (* some body *) () // val f2 : 'a -> unit
f ()
f2 "foo"
f2 ()

System.Console.WriteLine "foo"
System.Console.WriteLine ("<{0}>", "foo")

// overloaded methods are a pain:
let arg = "<{0}>", box "foo"
(System.Console.WriteLine: string * obj -> unit) arg

let f = System.Console.WriteLine: string * obj -> unit
let arg = "<{0}>", box "foo"
f arg

API problems

Function chaining makes properly formatted F# look like Lisp:

(((Some.Bloody "thing").That "is").Designed "for").Chaining <- "..."

This is where the very forgiving nature of F# comes in handy:

Some.Bloody("thing").That("is").Designed("for").Chaining <- "..."

Just remember that you're writing a bastardized form of F# and loudly express anger towards the API designer. Or write wrapper functions that allow you to express yourself in beautiful, readable code:

let that x (y: Bloody) = y.That x
let designed x (y: That) = y.Designed x
let chaining x (y: Designed) = y.Chaining <- x

Some.Bloody "thing"
|> that "is"
|> designed "for"
|> chaining "..."

Still with me?

At this point, you're either in the choir, about to send me angry comments or have learned something new about our favorite language. Properly written, it is easily one of the most beautiful languages out there. My only problem with it (well, I might have a few more) is that it is so forgiving, which has given rise to all sorts of unnecessary habits and misinformed notions of how the language works.

End of rant.

Edit: while it is technically correct to say that parentheses are optional, they are often superfluous as used by many developers -- you're free to add as many parentheses as you like to your code, but know that it most likely does not do your code any good.

@sashang
Copy link

sashang commented Jun 28, 2018

Thanks. Very insightful explanation.

@jdh30
Copy link

jdh30 commented Feb 26, 2020

@s-trooper

In Python, what is the difference between the function calls f(x) and f((x))?

@nxtlo
Copy link

nxtlo commented Aug 20, 2021

@s-trooper

In Python, what is the difference between the function calls f(x) and f((x))?
@jdh30

You're basically making x a tuple object.

def f(x) -> int:
    return x + 1

assert f(2) == 3
# True

# Also equals to
assert f((2)) == 3
# True

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