Welcome Guest! To access all forums & features, please register an account or sign-in. → Why register?




Photo * * * * - 1 votes

F#: exciting first impressions

In the wake of my "Why C# sucks" article, I've come to realize that there are real problems with the C-based, imperative-style languages I've learned (C++, Java, C#, Python, Javascript, etc). It's hard to pinpoint what because it's the only thing I've ever known, but dabbling in F# is giving me some savoury insights. I've rarely been this excited about a programming language, I feel like I'm about to learn an entire new way of thinking.

For now, what I'd like to mention is how the defaults in F# make more sense than in C#:
- Everything is immutable unless otherwise specified
- Nothing can ever be null except otherwise specified
- No function can call itself (even indirectly) unless you mark it as recursive
- All types are inferred from usage except in specific cases

Just the first 2 points are huge; I suspect that above 50% of all failures of all programs are caused by accidental mutation of variables and null references (Anders Hejlsberg once said that null reference exceptions alone caused 50% of failures). Just how many things can go wrong in the following code?

struct Point {
  public int X { get; set; }
  public int Y { get; set; }
}
(...)
List<Point> pointList = getPoints();
(...)
foreach(var point in pointList) {
  // do something with points assuming they are what was returned by getPoints
}
We have mutability at 3 levels here:
1) Every Point structure is mutable, so each point within the list can be changed before the foreach loop, potentially causing failure
2) The List itself is mutable: elements can be re-arranged, removed, inserted.
3) The pointList variable is mutable: it can be re-assigned to another list or even to null!

Each of these create state, which makes the program harder to reason about, to refactor (because order of statements matter) and to test.

To get immutability in C#, we have to be very disciplined and litter our code with "readonly" everywhere. This is not what the language encourages. The same effort would be required in F# to make things mutable, i.e. you have to explicitely confess your dirty intentions of creating state and potential points of failure in your program. I've never felt bad not writing readonly in C#: I can tell you that writing "mutable" in F# feels horrible every time (partly because it underlines my ignorance of functional programming - in a purely functional style, nothing needs to be mutable at all! Notably, Haskell has no mutable variables. None. F# is more forgiving to us sinners).

I'm currently reading Real-World Functional Programming and writing a Tetris game with XNA and F#, just to get the hang of the syntax. My program has an aweful lot of "mutable" keywords in it, but it still looks prettier, safer and more declarative than anything I've written in C# already. I'm looking forward to mastering the language and hopefully eliminating the need for C# most of the time (I'm sure C# does certain things better than F# - haven't figured out what yet though ;) ).

P.S.: because it's fun, the above program in F#:

type Point = {
  X : int
  Y : int
}
(...)
let pointList = getPoints() // where getPoints() typically returns a seq<Point>, i.e. IEnumerable<Point>
(...)
for point in pointList do
  // do something with points KNOWING it's the same points that were returned by getPoints
  // I like knowing better than assuming :)




Maybe it's just my style of programming, but when I declare a variable, I intend to change it. If things were immutable by default, my code would be cluttered with 'mutable' all over the place.

Unless a restriction really helps to prevent bugs, I think the most common behavior should be default (to a point, of course). Recursive functions are much less common, so I would be fine with non-recursive by default.

On another note, I think function arguments could be readonly by default. Depending on the input, you're unlikely to change it, and accidentally modifying a function argument could cause unexpected changes outside of the function. Sometimes, I'll purposely use function parameters instead of declaring a local variable, simply to optimize my code.

I think other programming languages can learn from the functional paradigm, but it's a style of programming I wouldn't want to use on a daily basis.

P.S. I love these posts, keep them coming!
Another interesting read Asik

Xinok, on 17 May 2012 - 03:01, said:

Maybe it's just my style of programming, but when I declare a variable, I intend to change it. If things were immutable by default, my code would be cluttered with 'mutable' all over the place.Unless a restriction really helps to prevent bugs, I think the most common behavior should be default (to a point, of course).
An example of immutability in C# is the String type; you can never modify a string! When you want to "modify" a string, you just create another one. No one seems to complain about it and it does rule out an entire class of bugs - you can never accidentally modify a string.

F# simply extends the concept to every type you define by default. And every variable. :p

I don't know if you have tried it, but even with my very imperative-oriented mind, I find myself having a lot less mutable keywords than I have variables ("values") in F#. To achieve the same in C#, I would need to write "readonly" a lot more often than I write "mutable" in F#.

A lot of mutability is easily avoidable, for example:

//typical C#:
void MovePlayer(Player p, Vector2 direction) {
	 p.Position += direction;
}
// typical F#:
let movePlayer(player:Player, direction:Vector2) {
	let movedPlayer = { player with Position = player.Position + direction }
	movedPlayer
}
Instead of changing the original player variable, I just create a new one with the updated value.

I must say I don't understand how one could fully architecture, say, a game without mutability, but I know it's possible (there is no mutability in Haskell) and understanding that is top priority for me right now. :)

Dr_Asik, on 17 May 2012 - 13:10, said:

Instead of changing the original player variable, I just create a new one with the updated value.
Isn't that horribly inefficient though? You have to allocate a new object and copy all the values over just to modify a single variable.

I remember your C# code, you can convert a string to a mutable Char array and vice versa. I suppose that's possible in F#, but there is no mutability in Haskell.

Xinok, on 17 May 2012 - 15:52, said:

Isn't that horribly inefficient though? You have to allocate a new object and copy all the values over just to modify a single variable.
From what I understand, you typically don't use large records that hold all sorts of various data and pass that around in FP, so idiomatic code shouldn't suffer too much copying overhead.

Further, the idea of "copying data" is an implementation detail: from my perspective I'm "creating a new object", whether the compiler chooses to accomplish that by allocating new memory and copy everything or silently re-use the same object (because I don't use the old one) is up to it. A lot of optimisations are made possible by a functional style, see how Haskell lazy-evaluates all function parameters for instance, this is possible because there are no side-effects. As well as making everything much more parallelizable.

Quote

I remember your C# code, you can convert a string to a mutable Char array and vice versa. I suppose that's possible in F#, but there is no mutability in Haskell.
But the char array is a copy of the original string, and you can only convert it to a new string, not modify the original.

Dr_Asik, on 17 May 2012 - 16:01, said:

From what I understand, you typically don't use large records that hold all sorts of various data and pass that around in FP, so idiomatic code shouldn't suffer too much copying overhead.
On a large scale, all of that copying overhead adds up and it'll prove to be highly inefficient.

Quote

Further, the idea of "copying data" is an implementation detail: from my perspective I'm "creating a new object", whether the compiler chooses to accomplish that by allocating new memory and copy everything or silently re-use the same object (because I don't use the old one) is up to it. A lot of optimisations are made possible by a functional style, see how Haskell lazy-evaluates all function parameters for instance, this is possible because there are no side-effects. As well as making everything much more parallelizable.
Optimizers are only so intelligent and I find it unlikely they can optimize functional code to be as efficient as imperative code.

Lazy evaluation adds more overhead as well. Under the hood, it's really passing a function which must be called to initialize the variable. As well, you also need a flag to track whether the variable has been initialized or not.

I think you covered this in a previous blog post yourself - You simply can't efficiently parallelize small bits of code. It requires keeping threads in sync which incurs too much overhead to be practical. For concurrency to work, you must be able to run two or more tasks for a period of time with little to no communication between threads.

Quote

But the char array is a copy of the original string, and you can only convert it to a new string, not modify the original.
I was acknowledging the possibility of mutable strings. What would it take to reverse a string without mutability?

Xinok, on 17 May 2012 - 18:29, said:

On a large scale, all of that copying overhead adds up and it'll prove to be highly inefficient.
Are you speaking from experience or theorizing? Performance expert Jon Harrop (who wrote F# for Technical Computing) reports having made large-scale C++ programs 10-100 times faster simply by converting them to F#; [link] and that F#'s inlining allowed them to write numerical methods several thousand times faster than the equivalent C# [link][moar linkz]. It seems F# is suitable for high-performance code, it runs as a statically typed, compiled language on .NET so its performance is certainly much better than imperative interpreted languages like Python or Ruby, so unless you're writing the next Unreal engine I don't think F# should give you any problems performance-wise.

Quote

Optimizers are only so intelligent and I find it unlikely they can optimize functional code to be as efficient as imperative code. Lazy evaluation adds more overhead as well. Under the hood, it's really passing a function which must be called to initialize the variable. As well, you also need a flag to track whether the variable has been initialized or not.
I suspect optimizers can optimize functional code in different ways than imperative code because functions are free of side effects, so aliasing problems go away and order of evaluation doesn't matter. Lazy evaluation doesn't necessarily have to imply adding a check, it can just mean moving a function call down in the assembly so it's only called if and when the value is needed. And "passing a function" sounds expensive because delegates are slow in .NET, but it doesn't have to be. F# has its own "delegate" type that is much faster than .NET's.

So, again, I don't think we can assume functional programming induces prohibitive or even very significant overhead compared to imperative. It has and is, today more than ever, used successfully in the industry. Scala is an example of an impure functional language (basically like F# but on the JVM) that has been gaining a lot of popularity lately. Twitter notably switched to Scala rather than Ruby, in good part for performance reasons.

Quote

I think you covered this in a previous blog post yourself - You simply can't efficiently parallelize small bits of code. It requires keeping threads in sync which incurs too much overhead to be practical.
But that's the entire point of immutability: you have to maintain threads in sync when they are destructively updating shared memory, but if they don't, there's no need for synchronization! Any pure function can be parallelized in a lock-free manner precisely because it's pure. That doesn't mean it makes it worth it to parallelize very short computations and I don't think that's what I was implying.

Quote

I was acknowledging the possibility of mutable strings. What would it take to reverse a string without mutability?
A recursive function: http://en.wikipedia....e)#List_reverse
By the way, remember that routine that reverses lines in a file and writes them back to the same file I used to benchmark C++ vs C#? Well a naive port to F# turned out to be 25% faster than the C# version, because of the reliance on lazy evaluation (IEnumerable<T>) versus eager (array of []). The same could have been achieved easily in C# of course, but the language doesn't push towards that like F# does.
Great read!
I tried a few Project Euler problems with F#...the naive F# implementation is often the same thing I did in C#, probably because I use LINQ a lot. C# already allows for a lot of functional stuff (e.g. (someCondition ? (Action) this.Show : this.Hide)() ) ; writing F# makes me think C# vNext should get pure, immutable, inlined, some/none, etc. as keywords. That's the really interesting part - I don't like the compiler automagically inferring return types for me, or the syntax for function arguments.

Do you know how to benchmark something that is not a function such as the below code ?
    let p1 = {1..999}				    
		  |> Seq.filter (fun x -> x % 3 = 0 || x % 5 = 0)
		  |> Seq.sum
I'd like to see how fast F# is compared to C# for simple and not-so-simple stuff, but I can't find a way to do it :/

Aethec, on 19 May 2012 - 13:37, said:

Do you know how to benchmark something that is not a function such as the below code ?
	let p1 = {1..999}							  |> Seq.filter (fun x -> x % 3 = 0 || x % 5 = 0)		  |> Seq.sum
I'd like to see how fast F# is compared to C# for simple and not-so-simple stuff, but I can't find a way to do it :/
The same way you'd do it in C#, System.Diagnostics.Stopwatch.StartNew() before the call and ElapsedMilliseconds after... am I missing something here?

Based on some tests I did, LINQ seems faster than the equivalent functions in the Seq module, but is less type inference friendly. F# 3.0 will have integrated LINQ syntax like C#, so that should make it more idiomatic. I still find the duplicate functionality somewhat confusing though.

Aethec, on 19 May 2012 - 13:37, said:

That's the really interesting part - I don't like the compiler automagically inferring return types for me, or the syntax for function arguments.
Sophisticated type inference is essential for functional programming: when you got functions that take functions that curry functions that return functions, in C# that looks like a completly unreadable mess of Func and angle brackets; in F# it looks obvious and natural.

Dr_Asik, on 19 May 2012 - 20:34, said:

The same way you'd do it in C#, System.Diagnostics.Stopwatch.StartNew() before the call and ElapsedMilliseconds after... am I missing something here?Based on some tests I did, LINQ seems faster than the equivalent functions in the Seq module, but is less type inference friendly. F# 3.0 will have integrated LINQ syntax like C#, so that should make it more idiomatic. I still find the duplicate functionality somewhat confusing though.
Yes, but that code is not a function. It's actually a value, which means it's only calculated once, thus it's impossible to benchmark it reliably (with more than one iteration).
In C# you have to create methods for everything that is not a simple calculation and these are super-simple to benchmark; in F# I can't find a way to force this thing to be a function or method.

I hope F# will not have too much duplicate functionality ; they already avoided some of the problems C# had and removed some useless things (e.g. the + unary operator)...it'd be too bad if they started having duplicate functionality everywhere.

Aethec, on 20 May 2012 - 09:12, said:

Yes, but that code is not a function. It's actually a value, which means it's only calculated once, thus it's impossible to benchmark it reliably (with more than one iteration).In C# you have to create methods for everything that is not a simple calculation and these are super-simple to benchmark; in F# I can't find a way to force this thing to be a function or method.
Ah, I see what you mean. It's actually very simple:


let p1() = {1..999}							  
           |> Seq.filter (fun x -> x % 3 = 0 || x % 5 = 0)
           |> Seq.sum
Thanks! I knew it'd be simple :)
I've been thinking about this for a few days. I'm finding it difficult to convince myself that functional programming could be more efficient. However, I've nailed down two key advantages:

1. Scalability. AFAIK, it's much easier to use functional programming in large projects. Everything being immutable by default reduces the chance of conflicts in code.

2. Asynchronous garbage collection. In the D community, there are calls for manual memory management because the GC has to suspend everything in order to run. In terms of game design, this means noticeable lag during gameplay while the GC is running. But in a pure immutable language, the GC can run in the background without interrupting gameplay.

This seems true in F# for the most part. Mutable data can't escape the scope of the function, with the exception of Ref cells which allows for shareable mutable data.

May 2013

S M T W T F S
   1234
567891011
12131415161718
192021 22 232425
262728293031 

Categories