Thoughts on F Sharp

F# is an attempt to bring a functional programming language (based on ML) to the .NET framework. It seems to have Microsoft blessing, with support out of the box in Visual Studio 2015 (now free for personal use). I thought I’d give some initial impressions of the language, mainly coming from a Haskell perspective. I am happy to be corrected, as I’m still quite new to the language.

I quite like F#, but the main impression I get is that whatever I want to do, there’s two or more separate ways to do it, and no obvious way to choose between them. F# is, very clearly, the result of stitching together two programming systems: the Object-Oriented Programming [OOP] of .NET and the Functional Programming [FP] of the ML language. Much like Java post-generics (with its “int” and “Integer”) or Windows 8 with its tablets-meets-desktop, systems like this often have quite glaring joins. And especially if you come from neither a .NET nor an ML background, you are just left a bit bamboozled as to which you should be using. Where there is a reasonable set of rules to choose between int and Integer in Java, in F# it often just seems to come down to preference or convention. This multiplicity of approaches is especially evident in the type system.

Types

Let’s start with lists. You can specify a list’s type in F# using the postfix syntax “int list” or the prefix syntax “List<int>”. Both mean the same thing and there’s no obvious reason to choose one or the other (this StackOverflow answer mentions a suggestion to use prefix for all except four types, but with no clear justification). Lists have some properties which can be accessed directly, like “myList.Head”. But there’s also a function you can call using “List.head myList” for the same purpose. The property is probably an OOP thing, the function is FP, but as a programmer who just wants to write some code, what’s the difference? The dividing line seems to vary depending on the origin of what you’re using: a C# class is going to have lots of member functions, an F# library will have lots of external functions, and your code will seemingly always end up with a mess of the two. When writing your own code, it’s often not clear which way you should lean, either.

The Class System

F# allows you to write classes which override other .NET classes, which makes for a potentially great marriage of functional programming with existing [OOP] .NET libraries. In the classes you define in F#, there’s multiple ways to define fields:

type MyClass2 =
  let readOnlyPrivateValue = 1
  let mutable readWritePrivateValue = 2
  [<DefaultValue>] val mutable private readWritePrivateValueNoInit : int
  [<DefaultValue>] val mutable readWritePublicValueNoInit : int
  member this.ReadOnlyPublicValue = 5
  member private this.ReadOnlyPrivateValue = 6
  member val ReadWritePublicValue = 7 with get, set
  member val private ReadWritePrivateValue = 8 with get, set

This StackOverflow answer suggests when to use each, but I was still left puzzled. I’m slowly getting the hang of it: let when you have an initial value, val when you don’t, mutable when you want to change it, but I’m not much wiser as to when to use member vs let/val — are they semantically equivalent if I’m not customising the get/set? For example, I found this code in another StackOverflow answer which does a “memberwise clone”. Will that clone my “let mutable” fields or just my “member” fields?

To define a member method you use a similar member syntax, but with brackets for any parameters. You also need an object reference, which you can see is also required for some field declarations but not others. It took me a while to understand the syntax:

type MyClass() =
  member MyClass.GetHundredA() = 100
  member x.GetHundredB() = 100
  member y.GetHundredC() = 100
  member whateveryoulikeiguess.GetHundredD() = 100

I thought the part before the dot was some reference to the class I was making the method for, hence I ended up using the class name (like the first member, above) because that made sense to me, and it compiled fine (not sure it should!). I saw people using “x” instead and thought it was a keyword. It turns out the first part, before the dot, is a name declaration for the “this” concept. So in the last method above, “whateveryoulikeiguess” defines a name to refer to what you would use “this” for in Java or C++. I’m not sure yet why they didn’t just swallow a keyword and always use “this”, and it still strikes me as a pretty weird syntax.

Data kinds

There’s many ways to define a data structure with a couple of members. If you want an int and string value pair, you could use:

type aPair = int * string
type anADT = Combo of int * string
type aRecord = {theInt : int; theString : string}
type aClass(i : int, s : string) =
  member this.TheInt = i
  member this.TheString =s
type aStruct =
   struct 
      val theInt : int
      val theString: string
   end 

Quite the variety! Sometimes there’s a good reason to choose one or the other, but quite often you just stick your finger in the air and guess. (I tend towards the second one because it’s what I’d use in Haskell, but I suspect the third is the best choice.) Suddenly Java with its everything-is-a-class philosophy seems sensibly restrained.

Ignorance is Dangerous Bliss

F# has a feature shared with Haskell where it tries to warn you if you are discarding a return value. Let’s say you have some function to write a file, which takes a path, some content and returns a boolean for success:

// F#:
let writeFile (path : string) (content : string) : bool = ...
-- Haskell:
writeFile :: String -> String -> IO Bool

If you call this function without assigning the return value or otherwise using it, F# and Haskell will both give you a warning (I think Haskell may require a compiler flag to turn the warning on) that the return value is discarded:

// F#, gives warning:
writeFile "C:/log.txt" "Bad stuff happened" 
-- Haskell, gives warning:
writeFile "C:/log.txt" "Bad stuff happened" 

You can suppress this using the ignore/void functions respectively:

// F#, no warning:
ignore (writeFile "C:/log.txt" "Bad stuff happened")
-- Haskell, no warning:
void (writeFile "C:/log.txt" "Bad stuff happened")

But if you mess up and miss out a parameter, you get quite different results in each language:

// F#, missed parameter: compiles fine, no warning:
ignore (writeFile "C:/log.txt")
-- Haskell, missed parameter: compiler type error:
void (writeFile "C:/log.txt") 

I’ll avoid an in-depth discussion of type systems here, but Haskell’s type system (in particular, monads) saves you here from a mistake, while F# happily ignores the return value, which is a partially applied function that does nothing. Lesson: use ignore with great care!

Misc Notes

A few more brief notes:

  • I know this one comes from ML, but whoever came up with expressing a triple type as “int * string * float” needs to be put back in the box with all the other mathematicians. Haskell’s equivalent syntax “(Int, String, Float)” is much nicer and easier to remember.
  • I got caught out at one point by bracketing pattern matches. I had a type “type Position = Position (int * int)”. I wanted to pull the values out using:

    let Position (x, y) = calculatePos()
    printfn "Position: %d, %d" x y
    // Error above: x and y not found

    I’m not sure what F# thinks that code is doing, but it doesn’t give a compile error on the let binding. Turns out you need more brackets to achieve what I was aiming for:

    let (Position (x, y)) = calculatePos()
    printfn "Position: %d, %d" x y
  • I remember, many years back, when I learnt Java after using C++, it was very refreshing to not worry about declaration locations. No header files, no needing to have forward declarations of classes before using them — you just put each type in its own file and off you go. Haskell cannot have cycles in its module dependencies; if module A imports B then B cannot import A, even indirectly — but within each module, declaration order doesn’t matter. Thus it felt like a bit of a backwards step in F# to start worrying about declaration order within a file; you can’t use any function or value before you have declared it in the file.
  • So far, I find writing F# unexpectedly similar to writing modern Java. A lot of the streams or lambda code I’d now write in Java 8 is very similar to what I find myself writing in F# with Seq and fun. If Java were to add algebraic data types (aka discriminated unions) with pattern matching, and syntactic sugar for currying, the core of two languages don’t seem like they would be that far apart. I guess both are part of the recent convergence/smashing together of OOP and FP, but coming at the problem from different sides.

The Good

I’ve mainly talked about the bad and the ugly instead of the good here, but I do actually like F#! There’s several aspects that improve on Haskell. The convention to use the |> operator is good, meaning you write “source |> operation1 |> operation2 …” which I find more readable than Haskell’s conventional “operation2 . operation1 $ source”. Similarly, I prefer <<, or more often >>, as function composition rather than “.”. Records are nice and easy to use; if you know Haskell, you’ll understand what a relief writing myRecord.myField is. Similarly, the ability to stick printf/trace statements anywhere in your code is very welcome.

I’d still take Haskell over F# as a functional language given a free choice (for its purity and type system), but as a way to bridge the gap between FP and OOP (with a recognised VM and thus lots of libraries behind it), F# is a good language. I’ve complained a lot about the joins in the FP/.NET aspects here, but with most of them I can see the language designers’ quandaries which led to the compromise. I suspect you can’t get much better than this when trying to bolt FP on to an OOP framework and still retain easy interoperability. I should look at Scala again at some point as a comparison, but at least in .NET land, F# seems like a good choice of language, if you can just find a set of conventions that keep your code nice and tidy when you so often have multiple approaches to choose from.

Advertisements

3 Comments

Filed under Uncategorized

3 responses to “Thoughts on F Sharp

  1. Don’t forgot immutability by default, Option types, Algebraic Data Types, exhaustive pattern matching, Active Patterns, units of measure, computation expressions, Type Providers, non-nullable reference types, tail call optimisation, Hindley–Milner type inference, and more.

    As far as the similarity with Java, that’s a bit of a stretch, even it bolted on some sort ADT support.

  2. I won’t get to much into the class stuff (I never use `var`/`mutable` and always go with the prime-constructor one) – but here are a few remarks:

    – the `int` and `’a option` stuff is most likely there to get some kind of compatibility with ML – the rest is what everyone is used to in .net/CLR
    – something like `type Name = Int` is just the same as in Haskell: it’s just a synonym, `type Name = Name of string` would be `data Name = Name String` in Haskell – sadly there is no `newtype`
    – Structs are interesting because they are *Value-types* – more or less those usually end up on the stack (not the heap) and are passed-by-value rather than by-reference – so you can get some performance boosts if you use them. (This too is a .net thing F# just needs to support)
    – `ignore` is not quite like `void` because `void :: IO a -> IO ()` but `ignore` would be `const ()` in Haskell (it’s `ignore :: a -> ()` it discards every input – including of course the function of your example – that’s no difference to Haskell at all)
    – for the `let Position (x, y) = calculatePos()` part: you have to remember that in F# there is no rule like *everything in Uppercase is a Type or a Value-Constructor – what you did here (at least I think – not 100% sure without seeing the rest of your code – btw: is this a script or a compiled file?) is define **a function** named `Position` getting a tuple `(x,y)` as input and returning whatever `calculatePos` produces.

    • Re void/ignore: yes, the monadic types in void is what makes it better than ignore. I think this difference is a real plus point for Haskell and the use of the IO monad; the side effecting parts are marked in the type system. You could easily have a partially applied function in F# that actually does have a side effect before it’s received all its arguments, so you may sometimes want to ignore a partially applied function.

      Re the position: ah, I hadn’t even considered that I’d written a function! You’re right that I was thinking in Haskell terms, and thought of the capitalised identifier as being a data constructor. Now I see why there needs to be extra brackets to mark the difference. But I think I quite like the Haskell distinction, if it saves this confusion.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s