10 cool things about f# that aren’t functional-specific
Cats: Uncategorized
Tags: f#, oo
i'm going to be speaking about f# and applicability outside the lab in a few days (see here), so i figured i'd put this up here as part of my prep for the meeting. so - 10 cool things about f# as a .net/oo language
- #light syntax. Indentation based syntax, while i'm sure viewed as the bane of our existence, is amazingly easy to deal with, given a proper editor and assistance. Yes, sometimes it's quirky, but not having to look at 40 pages of curly braces is worth it. You simply end up writing less code, seeing more code per screenful, and writing code that's easier to read.
- type inference. F# is the ultimate combination - it's a statically typed language, that reads like a dynamic one. That's because the F# compiler infers value and parameter types from their usage. There are mechanisms to specify data types, but unless you're doing funky reflective programming, you don't need them often. Most of the time, the compiler will hit it spot on. That means that this works
let f x y = x + y // val f : int -> int -> int
but so does this
let f2 (x: float) y = x + y // val f2 : float -> float -> float
- record types. Or rather how you consume them. Records are compound data structures (kind of like structs). With a record definition of
type SomeRecord = { first_name: string last_name: string dob: DateTime }
you can get an instance by just writing
let alex = { first_name = "alex"; last_name = "p"; dob = (System.DateTime.Parse("01/01/1900")) }
Cool, huh? Notice i didn't have to specify what the type of alex is. It's actually inferred from the record labels i supplied in the expression. What's more, is that i can now create a clone of that record:
let alexs_twin = { alex with first_name = "twin" }
- object expressions. Ever have a piddly little object that you needed to return? Ever wonder why you have to go through the hoopla of defining a class for something that'll never be used outside of where it's declared? Well, in f#, you don't. This example comes from a parser implementation for a markup language. It was broken up into a tag class, and a node class. Tag class did the parsing, node was the AST node representation, and typically did very little work, with most of the functionality being defined in the base clas Node. This specific tag translates words into template syntax:
type TemplateTag() = interface ITag with member this.Perform token parser tokens = let args = List.tl <| smart_split token let buf = match args with | "openblock"::[] -> "{%" | _ -> raise (TemplateSyntaxError ("invalid format for 'template' tag")) [{ new Node(Block "templatetag") with override this.walk walker = {walker with buffer=Some buf} }], tokens
What this little code snippet does is rather than explicitly defining a new class TemplateNode that just overrides the "walk" method on Node, it combines the instantiation and definition steps, and returns an anonymous subclass of node, with the "walk" method redefined to do something else. Now, you can take this even further, and use object expressions to define anonymous interface implementations. Say you wanted to make sure that when an operation was done, some resource was released, but that's all you cared about. Simple - use an object expression to implement the IDisposable interface, and you're done:
let disp = { new Object() interface IDisposable with member x.Dispose() = (*do stuff*) }
Side note here - object expressions make a lot of sense when coupled with the fact that F# has the concept of closures, but that's more functional so just a quick blurb on this. A closure means that as you define your instance/type combination, you have access to local values of all of your parent scopes. That's why, in the TemplateTag example, my Node class implementation can access the local value "buf" from within it's method definition.
- tuples. Ever need to return 2 values from a function? Okay, you do a return value and an output parameter. Annoying to consume, but doable. But what about 3? or 4? At that point it gets to be just unmanageable. In F# this is simple. A tuple is a composition of multiple other types. If i have a function (or method, to stay with our theme) that took my "SomeRecord" from before, and returned the first and last names only, it would look like this
member x.get_names record = record.first_name, record.last_name
That's it! Now, when i consume it, i can either treat it as a single value of type tuple, and then use special accessor functions to read from it:
let names = someclass.get_names alex printf "first name %s\r\n" <| fst names printf "last name %s\r\n" <| snd names
or using f# syntactical sugar to unfold the value explicitly:
let first, last = someclass.get_names alex printf "first name %s\r\n" <| first printf "last name %s\r\n" <| last
or ignore one of the values entirely:
let _, last = someclass.get_names alex printf "last name %s\r\n" <| last
-
discriminated unions. When the set of values you care about depend on what you're representing, discriminated unions are awesome. Example here is - if you wanted to have a single data type that represented a vehicle, but wanted to store different attributes for different vehicle types, you could define a discriminated union that stores passenger capacity and Color for passenger cars, and maximum load and number of wheels for trucks:
type Vehicle = | Passenger of int * Color | Truck of float * int
- null value handling. F# doesn't really have the concept of a null. I mean it does, but it mostly comes up when interfacing with external libraries that'll slip one in here or there. Outside of that, if you say that method someclass.get_names takes a record of type SomeRecord, you can be sure that you'll never get a null there. Now, if you want to allow for that, you can use the "Option" type. Option is a discriminated union of
type Option<'a> = | None | Some of 'a
which means that it either contains "None", or Some value of type 'a (the whole ' thing is to identify a generic type parameter, so the Option type is a generic type). That means that whenever you're dealing with values that may not be supplied, you have to explicitly handle that scenario. That also means that the pesky NullReferenceException goes bye-bye.
-
lists, list literals and other fun built-in structures. We're starting to get closer to f#'s functional nature, but this is still very much oo-applicable. Lists. F# has awesome support for lists. For starters, you can do stuff like this:
// new list, containing 0 through 5 let l = [0..5] // new list, concatenating l with the list containing 6 let l2 = l @ [6] // h is the head element in l, t is the remainder let h::t = l // pull the first three elements out into h1,2,3 respectively let h1::h2::h3::t = l2
then you can move on to generating expressions
// 10 squares let l3 = [for i in 0..10 -> i * i]
The other thing is that default lists in F# are immutable structures, so when you modify them, you're actually creating new instances that contain the modified values. But fear not - there's no copying involved. Since each instance is immutable, f# can share elements among instances, allowing the operation
some_giant_list @ [0]to not have to copy the contents of some_giant_list. Oh and with arrays, you can also do slicing notation:
// generate array of 0 through 5 let a = [|0..5|] // get the first 4 elements let a2 = a.[..3]
.
-
pattern matching. We're getting closer and closer to functional territory, but with f# that line is somewhat blurred. Pattern matching is effectively a very concise way to write a long if-then-elseif. You can
// match over literals match alex.first_name with | "alex" -> printf "woo" | _ as name -> printf "don't know %s" name // and over lists match l with | h::t -> printf "the first element is %d, and there are %d in the tail" h <| List.length t | [] -> printf "the list is empty" // and over Types (there shouldn't be a space between the : and the ?) match disp with | : ? System.ICloneable as cloneable -> printf "%A is clonable" cloneable | : ? System.IDisposable as disposable -> printf "%A is disposable" disposable | _ as unk -> printf "dunno what %A is" unk
you can also pattern match over discriminated unions (that's actually the main way of working with those), and various other data structures.
- access to .net libraries. Okay, i'm cheating a little here, because the remarkable part isn't that there's access to the .net library, but that a functional, statically-typed language has access to the .net library. The fact that I can reach out to components written in c#, with no penalty, is what will make this language more mainstream than most other functional implementations (scala and the like, of course, notwithstanding).