The problems were all from Project Euler, so they were the sorts of things that tend to have deeply recursive solutions. So, in that department, F# was an advantage.
F# has some unpleasant quirks, though:
Monomorphism by default
We had this function:
let fibStep (a,b) = (b,a+b)
By default, it only works on ints! It should be valid for any numeric type! It's just addition! But no, we had to label the parameters:
let fibStep (a : BigInt ,b : BigInt) = (b,a+b)
Crippled type inference
Maybe I've been spoiled by Haskell, but I just assume that if I've got type inference, I've got Hindley–Milner over the entire program. In F#, types are only inferred within the boundaries of a single function. That means more type annotations.
Recursion requires annotation
And for some reason I don't understand, by default recursion is forbidden in a function, unless you define it with the "let rec" keyword. I have no idea why.
Find the missing library
Since F# is on the CLR, it inherits some VM design smells. Where's the Bigint type? Microsoft.FSharp.Math . You know, I would have expected the F# language to have implicit access to the FSharp namespaces. Nope.
Unusual punctuation
We knew about the confused usage of ;; for end of line and ; for comma, and , for a different kind of comma. But we kept having to look up operators (for method names we had autocomplete in the IDE! but dingbats were Google's territory.) I know we got confused on: cons (::), append (@), exponent (**), mod (%).
Cryptic error messages
And when you get it wrong, the interpreter is worthless. It had a tendency to entirely fail to parse our functions, and give us line/column numbers at the end of the code, rather than at the problem spot. I can't remember what the messages we were getting said, but it sure seemed like they just said "This is wrong"
Hard to google
Several times we tried googling some common function names like "head" or "tail" or "append" and found that mixed with the keyword "F#" you get nothing but music results. Way to go, marketing wizards.
2 comments:
Monomorphism - Mark it inline:
> let inline fibStep (a,b) = (b,a+b);;
val inline fibStep :
^a * ^b -> ^b * ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
With inline functions, you get things that work across _any_ type (not just numerics) so long the right members exist. "+" isn't just addition, it's the ( + ) function.
Type inference - well, it can infer constraints based on later usage sometimes.
Recursion - I think this is to avoid problems with redeclaring an identifier:
let test =
let inc i n = i + n
let rec inc i n = if n = 0 then i else inc (i + 2) (n - 1)
inc 1 3
The value of test depends on if the second inc is recursive or not. Without the rec annotation, inc refers to the previous function, not the current one.
Missing lib - BigInt _is_ automatically accessible, but it's called "bigint". From source/fsharp/FSharp.Core/math/z.fsi:
namespace Microsoft.FSharp.Core
type bigint = Microsoft.FSharp.Math.BigInt
Only some of the namespaces are opened by default, so you aren't flooded by a ton of possibly-irrelevant things.
Punctuation - Well ;; is only for interactive mode to end input -- not end of line. ; is separator, and , is for tuples. I suppose they can be confusing for a bit.
Errors - got some examples? Sometimes generic code produces hard messages.
Google - Yea, F# gives music or censored curse words :P. Try combining with Seq or List or whatever. I.e., "F# Seq.head" has no non-programming results in the first 10.
As a back-in-the-day C# programmer I had to get creative in my searching for examples/documentation. Mostly it seemed like a convention built up around "csharp" and "c-sharp". I suspect something similar will happen (has happened?) for F#.
Post a Comment