So this is the 1st post in my F# series. So what are we going to cover then? Well as many programmers already know, it is customary to start with a “Hello World
” example.
So we will be doing just that. So without further ado, what does it take to create a stand alone “Hello World
” app in F#.
Well, here it is:
open System
[<EntryPoint>]
let main argv =
printfn "Hello World"
Console.ReadLine() |> ignore
0
Now that might not seem like enough to talk about, but it is, and further more, it actually has enough content to allow us to talk about a few core F# principles to boot.
So what exactly is going on here?
Well since this is a stand alone application, we obviously need an entry point (just as we would for any other .NET language. So how is the entry point defined? Well it’s much the same as it is with other .NET languages, we have a main
method (or function in F# lingo) that takes astring
array, and we use the [<EntryPoint>]
attribute to indicate that the method that has this attribute is the main one. The compiler and Visual Studio love us for this.
Even in that sentence alarm bells, or at least some sort of spider sense should be going off. So we said there was a main
function that takes an array of string
array. All I can see there is a variable called “argv
” I certainly can’t see any string
array. This “argv
” variable is clearly the string
array that we would expect, but why doesn’t it have a type… Mmm interesting.
In F#, there is a very very good type inference system, which means in a lot of cases, you may choose to leave out the type all together and F# will correctly infer it. You are of course still able to fully declare the type of an input variable if you wanted to, where it would typically take the form:
(variableName :variableType)
So taking the “argv
” example stated above, we could instead declare the above program like this, and that would be fine:
open System
[<EntryPoint>]
let main (argv :string[]) =
printfn "Hello World"
Console.ReadLine() |> ignore
0
Can you see that we fully qualified the name of the input variable with its type, which in this case is a string[]
array.
What else is of interest there? Well like I say, there is more to talk about than you think.
Why do I have a line that has this strange |> ignore
incantation at the end of it. What is that all about?
Well since this is a F# Console Application, I wanted the application not to exit until the user types a character. So I need to do 2 things:
- Open the “
System
” namespace, which allows me to use the types in the “System
” namespace, much the same as it would if I used “using System
” in C#, or “Imports System
” in VB .NET”. - Since F# is a functional language, it will complain if the actual function (in this case the “
main
” function does not return a value, so I must choose to use the forward pipe operator to pipe the output of the function of the left, which is in this case a “Console.ReadLine(…)
” which may return a string
, to an “ignore
” function. Which effectively means return ()
( that is F#’s way of saying void
, but it calls it Unit
, i.e., don’t care). This explicitly tells F# to ignore
the result of the “Console.ReadLine(..)
” method call.
For example, this is what the code would look like if I chose to not include the |> ignore
incantation.
To the regular C# / VB .NET programmer, you may not see what this prompt is talking about here. But F# is quite rigid about what a function can and can’t do. And one thing a function MUST ALWAYS do is return a result. In the case of the “main
” function, the result is expected to be an integer value of 0
. But what about the case of the “Console.ReadLine(..)
”. OK in this case, it is not a big deal and we are not interested in the result, and the program will still run and work just fine if we omit the |> ignore
, but if you are truly not interested in the result, you should get in the habit of using the |> ignore
combo. Just in case you are wondering, the forward pipe operator “|>
” will pipe the return value of the left hand expression to the right hand function, which in this case equates to “take the return value of Console.Readline(…) and pipe it to ignore, as no one cares”. Don’t worry if this means nothing to you at this stage, we will see more on this later.
The last thing I need to do is ensure that the F# function always returns a value, this is done in the line where you simply see a 0 line. This is enough for F# to realize this is the return value of “0
” which the type inference system will deduce is an Int
value of 0
. The reason the F# compiler knows this is the return value is that in this case, this is the last statement, so it MUST be the return value. Obviously, this could get more complex with conditional if
, else
, etc. logic. The important thing to note is that in F#, a function MUST ALWAYS return a value, and in this case since the 0 Int
value was the last line encountered (albeit things could be more complex, and they will be jest not yet) it is used as the return value. You can of course choose to return Unit
which is done by using "()
", which is effectively return nothing (void
if you like).
One other thing I would like to mention for this trivial example, is that whitespace plays a pivotal role in F# development.
For example, suppose my trivial example looked like this in the editor (in my case Visual Studio 2012):
This is a tiny program and ALL I have done is delete some whitespace at the beginning of the “printfn
” line. Mmmm seems like the F# compiler did not like that at all. Yes, that is a fair assessment.
In F#, whitespace is VERY VERY important, so play nice with it, otherwise get used to debugging cryptic errors such as the one shown above, which is caused by a simple issue of incorrect semantic white space.
Anyway that is the “Hello World
” example. Until we meet again next time, I bid you all farewell.