Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Monday, November 03, 2008
Common Conceptual Issues with F#
Several times I've had to explain a few basic F# things to new users. I ran into some of these too when I first looked at F#. If you've been looking at F#, but some things still don't click, I hope this will help break the chains of C/imperative languages. I'm assuming you know a bit about F#, perhaps from the Quick Tour or the F# Tutorial file that comes with the Visual Studio integration.

Functions always take and receive exactly one argument.
Despite anything you see, in F#, there's only one argument, and only one return value. Let's look at how this plays out (I suggest firing up F# interactive and playing around):

> let inc x = x + 1;;
val inc : int -> int

> let add x y = x + y;;
val add : int -> int -> int

[I like to read -> as "to", because it's short and sweet.] So, for the case of "inc x", we see the signature is quite simple and expected "int to int". But for add? We see "int to int to int". What this actually means is "a function that takes an integer, and returns a function that takes an integer and returns an integer".

The same signature in C# would look like this: Func<int, <Func<int,int>>> or "Func<int,int> Add(int x)". [Some day, think how odd it is to have two ways of representing the function.] So when we call the F# function, we're really passing in the first argument, getting a new function, then applying the second argument. Something like this:

1. add x y
2. (add x) y
3. closure1 y
4. final-result

"closure1" is what we get from the "partial" application of add. I'll touch on this in a minute.

So how do we _really_ pass in two arguments at once? We put two arguments into one value, a tuple. We write it like this:

> let add (x, y) = x + y;;
val add : int * int -> int

Notice the new type signature: "int by int to int" or "int int tuple to int". In C#, this would be:

int add(Tuple<int,int> arguments) { return Tuple.A + Tuple.B; }

But F# nicely provides support for tuples [via pattern matching], so we can write them much more naturally. This syntax also works on the way out:

> let pow23 x = x*x, x*x*x;;
val pow23 : int -> int * int
// "int to int int tuple"

> let square, cube = pow23 7;;
val square : int
val cube : int

> square, cube;;
val it : int * int = (49, 343)


There's never no value
Another common typo/misunderstanding is when creating a function with no arguments:

> let sayHi = printfn "Hi";;
val sayHi : unit

Hi
> sayHi;;
val it : unit = ()

Oh, what happened here? Remember how _everything_ takes and returns exactly one value? The same is true of functions that "don't return a value", such as printf. Instead of "not returning a value" like C's void, functions return a special type called unit with one value, ().

Armed with this, let's see what the sayHi definition actually says. It says "let a value called sayHi equal to the result of printfn". Well, since the result of printfn is unit, sayHi becomes unit. The execution happens immediately, and subsequent uses of sayHi just get the value, unit.

To actually do what we want, we need to take an argument. Then F# knows we're a function value:

> let sayHi() = printfn "Hi";;
val sayHi : unit -> unit

> sayHi;;
val it : (unit -> unit) = <fun:clo@0>

> sayHi();;
Hi
val it : unit = ()


By explicitly taking a unit parameter, sayHi becomes a function (type unit to unit). Notice that if we just write "sayHi", we're just going to get the _value_ of it (a function), not apply (execute) the function.

Partial application
OK, so a side effect of this system of "take on param and return a function that takes the next", is that we can compose with just parts of a function. For instance, to write an increment function, we can do this:

> let add x y = x + y;;
val add : int -> int -> int

> let inc x = add x 1;;
val inc : int -> int


So far, nothing interesting. However, we can also write this more effectively:

> let inc = add 1;;
val inc : (int -> int)


Aha! What's happening here is what happens "secretly" each time we call add with 2 parameters. We're just using the intermediate result, the closure of "add 1", and assigning that function value to inc. In C#, it'd be something like this:
public static Func<int, int> Add(int x) {
    return y => x + y;
}
public static Func<int, int> Inc = Add(1);
// and "full application" of add looks like this: Add(1)(2)
[BTW, this isn't really currying in F#. Currying is taking a method of type "a' * 'b -> 'c" and turning it into "'a -> 'b -> 'c". Since F# methods are "automatically curried", there's no need for a "curry" step (well, except perhaps when using .NET methods, which are always "tupled", but that's another story).]

[Side note: a function like "add" is superfluous, because in F#, operators are functions:
> (+);;
val it : (int -> int -> int) = <fun:clo@18>
> let inc = (+) 1;;
val inc : (int -> int)
]

The pipeline


F# appears to have all this complicated syntax, with |> <| >> << and so on. But, these operators are defined in F# code, and follow some basic rules. They aren't magic or have any special compiler support.

The most important function operator is |>. A quick search of the F# source shows:
C:\Program Files\FSharp-1.9.6.2\source\fsharp\FSharp.Core\prim-types.fs(2062):       
        let inline (|>) x f = f x


(Operators are surrounded in parentheses to define them and to use them as functions with prefix notation.)
The type signature is:
> (|>);;
val it : ('a -> ('a -> 'b) -> 'b) = <fun:clo@23>

This is "alpha to a function alpha to beta to beta". That probably didn't help. Perhaps looking at the type of function application will help:

> let apply f x = f x;;
val apply : ('a -> 'b) -> 'a -> 'b


This demonstrates that a function application is really just taking a function "alpha to beta", giving it an alpha, and getting a beta. [I'm open to suggestions on better ways to pronounce type arguments.]

So, glance back up at the pipeline operator. We can see it's really just function application _in reverse_. What is the use of such a construct? If you've used C# 3.0's LINQ extension methods or a Unix shell, you probably already know. By reversing the function application, we can write things in a much more natural order. To modify something from the F# Quick Tour:

> let filterTypes name =
-   System.AppDomain.CurrentDomain.GetAssemblies()
-   |> Seq.map (fun a -> a.GetTypes()) |> Seq.concat
-   |> Seq.map (fun a -> a.Name)
-   |> Seq.filter (fun s-> s.Contains name);;

val filterTypes : string -> seq<string>

> filterTypes "Coll";;
val it : seq<string>
= seq ["ICollection"; "EvidenceCollection"; "GCCollectionMode"; "CollectionBase"; ...]
>


Now, this is a bit embarrassing, but this took me a long time to get. I stared at this and re-read the F# manual for probably an hour. How could something so simple do such "complex" stuff?? Once I finally got used to the idea of functions being normal values, and the whole "one arg one value" bit, it snapped together. The other operators (<|, >>, <<) are pretty easy to follow once the basics are understood (going through prim-types.fs is a great experience).

What else?
I hope this all helps fit some pieces together. Feel free to use the MSN thing on the side of my site to ask questions or give me suggestions. Thanks!

Edit: Also check out F# function types: fun with tuples and currying (From an F# team member's blog.)


Code | FSharp
Monday, November 03, 2008 9:07:15 PM UTC  #    Comments [3]  |  Trackback

Tuesday, November 04, 2008 3:44:29 AM UTC
Nice; I will reciprocal link this from

http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!169.entry

which talks about similar topics; this is a good discussion of common questions that are important to grok when getting into F#!
Brian
Thursday, November 06, 2008 12:05:04 PM UTC
Thanks Michael.
I also found Luca Bolognese's Intro to F# PDC 2008 vid helpful, esp at the very end where a question about pipeline and nested function calls was addressed. It helped in thinking through an OCaml to F# translation.
Sunday, February 08, 2009 10:04:51 AM UTC
I found this article very useful. Thanks a lot for your effort.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview