If you have C# code and just try to transliterate it directly to F#, the F# code you wind up with is unlikely to be idiomatic or "good". Nevertheless, when you are new to a language (F#), it is sometimes useful to know how to transliterate from a well-known language (C#) for those cases where you just don’t know the idioms yet, but don’t want that to prevent you from making progress. So today’s blog entry takes the general form of "here’s some C# code, how do I write the same thing in F#". It is intended to be used as a reference or as casual reading to discover some little-used or lesser-known F# syntax/operators/functions. Don’t use this blog post as a way to learn the language, instead use the many useful F# tutorials and samples on the web (start here), and just use this blog post to "fill in the gaps" if needed.
Today’s blog entry covers only code that goes inside a method body, so you won’t find anything about declaring classes or using namespaces here. Instead I will be covering the following bits of C#:
- casts
- operators
- expressions
- statements
and show a way to transliterate those C# forms into the corresponding F#. Along the way I will inject some editorial comments about what is good, bad, idiomatic, etc, but I will not go into much detail. If I leave anything common out, or something is very unclear, feel free to post a question as a comment on this blog entry.
Without further ado!
Casts
The cast "operator" in C# (saying "(Typename)expr") can mean all kinds of different things in C#, depending on the source type and destination type. I will just call out the four most common conversions done via casts:
char c = ‘a’;
int x = (int)c; // numerical conversion
object o = (object)c; // boxing conversion
Dog d = new Dog();
Animal a = (Animal)d; // upcast
d = (Dog)a; // downcast (may fail at runtime)
Here is the corresponding F# code, followed by some commentary.
let c = ‘a’
let x = int c // numerical conversion
let o = box c // boxing conversion
let d = new Dog()
let a = d :> Animal // upcast
let d2 = a :?> Dog // downcast (may fail at runtime)
Numerical conversions in F# are done via library functions. For example, the function "int" converts a char (or a float or a uint or a whatever) into an int. There are similar functions for each destination type, so for example the function "char" converts it argument to a char. You can read a little more about these functions in the library reference. Note that the "enum" function converts to an enumerated type; the specific enumeration type will be inferred, which sometimes requires a type annotation, as in
A boxing conversion turns a primitive value into an object. The F# function "box" does this. In fact, "box" will upcast anything to the object type System.Object (which in F# goes by the shorthand name "obj").
Upcast: If you want to make a conversion that goes up a class/interface hierarchy, use the F# syntax "expr :> type". If this compiles, then this conversion will always succeed at runtime. (Note: in F# 1.9.6.2, this expression form has somewhat weird precedence, and so you may sometimes need to surround it with parens, e.g. "(expr :> type)" to make the code parse.) You rarely need to use this operator.
Downcast: If you want to make a conversion that goes down a class/interface hierarchy, use the F# syntax "expr :?> type". The question mark inside this operator is meant to suggest that the operation may fail; just as in C#, you may get an InvalidCastException. You should rarely use this operator; in C# you typically prefer "is" and "as", see those expressions below for the corresponding idiomatic F#.
Operators
Many arithmetic (e.g. +, *) and conditional (e.g. &&, ||) operators are the same in F# as in C#. The most common places to get tripped up are (1) with logical negation: in C# it is spelled "!", whereas in F# it is spelled "not"; (2) with equality testing, in C# it is "==" whereas in F# it is just "="; and (3) with inequality, where C# has "!=" whereas F# has "<>". (As for logical negation, note that "!" already has a meaning in F# associated with the "ref" type, which is inherited from OCaml, oh well.) The other somewhat common place to get tripped up is that in C# the bitwise-logic operators are one character (e.g. "|" and "&"), whereas the F# operators are three characters ("|||" and "&&&"); you’ll need these operators when using e.g. "Flags" enums.
Most F# operators are overloaded, don’t be fooled by the type inference tooltips when hovering over code like this in Visual Studio:
This does not mean that "+" only works on ints; you can use "+" to add two floats, or two chars, or even two strings. (The mechanism by which this overloading works involves esoteric black magic involving "inline" and "^a" types. You’ll be happier if you stay ignorant of those details; the language/tooltips pick "int" as a default when things are otherwise unconstrained in order to hide the underlying complexity. (A much better "solution" to "how to overload common operators like ‘+’" is to use type classes, but neither the CLR nor even F# have sufficiently expressive type systems to handle type classes, oh well.))
In C#, the same operator (‘=’) is used for both initialization and destructive assignment:
i = 4; // destructive assignment
In F#, these are separate operators (‘=’ and ‘<-’), and only mutable variables admit assignment:
i <- 4 // does not compile, i is immutable
let mutable j = 3
j <- 4 // destructive assignment
Expressions
Most expressions involving constructing objects, methods, and properties look the same in C# and F#:
s.StartsWith("h", StringComparison.Ordinal) // method call
s.Length // property
One notable difference is the syntax for indexing of arrays or other types. Whereas in C# you say:
dict["foo"] = 42;
Console.WriteLine(dict["foo"]);
in F# you need a dot (‘.’) before the square brackets:
dict.["foo"] <- 42
printfn "%d" dict.["foo"]
There are tons of variations of syntax for doing lambdas/delegates in C#, I’ll show only one:
and the roughly corresponding F#:
There are lots of interesting differences between the language when it comes to representing lambdas/functions/delegates, and I will not go into any detail about these differences in today’s blog entry.
Both C# and F# have "typeof" that returns a System.Type, but C# uses round brackets whereas F# uses angle brackets:
Another distinction involves generic types; C# allows you to omit types to get the generic definition:
Console.WriteLine(typeof(List<>).IsGenericTypeDefinition); // true
whereas F# has a separate operator called "typedefof" for getting uninstantiated generic types:
printfn "%A" (typedefof<List<_>>.IsGenericTypeDefinition) // true
C# has "is" and "as" operators for type tests. F# uses a particular pattern in a match for this. So this C# code:
{
Dog dog = animal as Dog;
// …
}
else if (animal is Cat)
{
Cat cat = animal as Cat;
// …
}
becomes this F# code:
| :? Dog as dog -> // …
| :? Cat as cat -> // …
where ":? type" is a type test, and "as ident" names the resulting value of that type if the type test succeeds. (One other aside: in F# "else if" can be abbreviated as "elif".)
C# has the ternary operator "?:" for conditional expressions:
F# has the same operator, but its name is if-then-else:
(Note that "if" is used much less frequently in F# than in C#; in F#, many conditional expressions are done via pattern-matching rather than if-then-else.)
C# has an operator called "default" that returns the zero-initialization value of a given type:
It has limited utility; most commonly you may use default(T) in a generic. F# has a similar construct as a library function:
These respective constructs are rarely used in C# and F#.
Statements
This section describes how to transliterate C# statements into F#. F# doesn’t have "statements", per se; everything is just an expression, and evaluating a a method body just means evaluating its expressions. The expressions that are most statement-like in F# just return "()", the sole value of the "unit" type, which is akin to "void" in C#.
C# has three looping constructs:
while (condition)
{
SomeCode();
}
foreach (var e in someEnumerable)
{
SomeCode();
}
for (int i = 0; i < 10; ++i)
{
SomeCode();
}
F# has just "while" and a "foreach" (spelled "for" in F#); a C# "for" loop can usually be emulated with a "range":
while condition do
SomeCode()
for e in someEnumerable do // foreach
SomeCode()
for i in 0..9 do // compiles like a C# for loop
SomeCode()
F# lacks anything like "break" or "continue"; you must use control flow and/or boolean flag variables to emulate these constructs. "While" loops in F# are rare, since they imply mutable variables in the condition, and F# often eschews mutables, preferring recursion for simple loops. For(each) loops are also somewhat rarer in F#, since sometimes the form
is preferred (especially when the argument to Seq.iter is short).
C# has a switch statement. It looks something like this:
{
case 1:
SomeCode();
break;
default:
SomeCode();
break;
}
In F#, this is just one of many things that pattern matching expresses more succinctly:
| 1 -> SomeCode()
| _ -> SomeCode() // _ is a ‘catch all’ default
C# has a "return" keyword that exits the current function. There is no comparable construct in F#. Note that the "return" keyword in F# is part of computation expression syntax (a.k.a. "workflows", a.k.a. "monads") and has nothing to do with C#’s "return". As with C#’s "break", in F# you will need to use control-flow constructs to simulate "return" for an early exit. (Though I do occasionally wish for "break" in F#, I have never wished for "return" – I don’t recall ever needing/wanting it.)
In C#, you raise an exception with the "throw" keyword. In F#, you use the function "raise".
This is one of the few places in F# where I think it is very idiomatic to use the backward-pipeline operator "<|". Without it, the precedence rules of F# force you to parenthesize the expression:
which I think most people feel looks awkward.
C# has a try-catch-finally construct for exception handling. As a result you can write code like this:
{
SomeCode();
}
catch (NullReferenceException nre)
{
// swallow it
}
catch (Exception e)
{
throw;
}
finally
{
SomeOtherCode();
}
The equivalent F# code would be:
try
SomeCode()
with
| :? NullReferenceException as nre -> () // swallow it
| e -> rethrow()
finally
SomeOtherCode()
There are a few things to point out here. First, in F#, try-with and try-finally are separate constructs – there is no try-with-finally. In practice, you usually only do one or the other (and you should probably be using try-finally about ten times as often as try-catch, in either language – catching exceptions is rarely the right thing to do). F# "catch" blocks just use pattern matching type tests on the exception, much like we saw earlier with the transliteration of C# "is"/"as". In F#, to re-throw the exception, use "rethrow()".
C# has a "checked" statement for checking for arithmetic overflow; F# has checked operators in the library, so just open this module and use the operators there if you need overflow checking.
C# has a "lock" statement for locking an object for a critical section:
{
SomeCode();
}
In F#, "lock" is a function that takes the lock object and a lambda of the critical section:
SomeCode()
)
C# has "using" for IDisposables:
{
SomeCode();
}
F#’s corresponding syntax:
SomeCode()
F#’s "use" is like "let", it is scoped to the end of the surrounding block.
Inside a C# method that returns an IEnumerable, the following "yield" statement forms are allowed:
yield break; // C# end the enumeration
In F#, you don’t have to declare a method-returning-IEnumerable to use "yield", instead you can make a sequence expression anywhere using "seq{…}":
I added a type annotation just to remind you that "seq" in F# just means "IEnumerable". So the sequence expression on the right-hand-side of the line of code above evaluates to an IEnumerable<int> that yields a single value (42). As with a C# iterator block, you can put arbitrary code using yields in an F# sequence expression. Just as with loops and C# "break", there is no "yield break" in F#.
The end
I think that just about covers all the interesting C# expressions and statements, showing the corresponding F# counterparts. I hope this is a useful reference for filling in gaps in your F# knowledge while learning F#.