F# usage

Dec 28, 2011 at 6:14 AM

Hello Mathias, I have a few questions about your use of F#. I don't know this language, but enough of OCaml to read it. For example, what is the rule for using pipelines instead of regular calls? I'd guess it is clearer to use pipelines when you have at east two treatments that are chained, and "something" is passed and transformed along the way. When an argument A is passed to some function F resulting in a B different from A, which is passed to function G, I prefer reading:

G (F (A))

rather than

A |> F |> G

So, in my view, most pipelines you use could be removed.

I just had a look at the F# doc online, and it seems you don't need to yield results in a loop to create a list of elements. For example, you could rewrite

         let inactives = 
            ref [
               for i in 1 .. config.InactiveBees do 
                  yield generate () ]


         let inactives = 
            ref [
               for i in 1 .. config.InactiveBees -> 
                  generate () ]
At the end of Solver, you're using matching on Booleans which I found not very readable. Replacing it with simple "if" tests would improve readability I think. Especially because you don't care about one of the cases each time, so you don't need the "else" case of the "if" expression. Also for readability, I would name the function that you pass to Task.Factory.StartNew, which is more than one screen long, instead of using an anonymous function here. Anonymous functions are best for small treatments to avoid cluttering the code with useless names. Here, using a name makes sense for such a long treatment, plus it will help grasp which are the additional arguments in the call, like cancel.Token which follows the anonymous function here.

Overall, I think you're using well the ability to state types sometimes, which helps readability at crucial points. Good thing not to overuse type inference, I've seen it too much in OCaml!

Also, one question about visibility: it seems everything you define in a module becomes public. So from Solver you can access everything from Hive. Is there a way to limit this visibility, like a declaration for the module restricting what's visible? (a .fsi file? similar to OCaml .mli)

Dec 31, 2011 at 11:22 PM
Edited Dec 31, 2011 at 11:23 PM

Hi Yannick,

Thank you for the feedback!

Pipelines: I guess it's a question of taste. You are correct in your interpretation of what it does. Personally, I find that the pipe-forward helps readability when the functions can be thought as a workflow, applying multiple functions to progressively transform an input, but I heard other people stating that they found it confusing. While G (F (A)) is very clear mathematically, it also requires to unwind the sequence and read it from the end, whereas something like input |> doThis |> doThat |> doSomeMore is very human-readable. The "forward pipe operator" section in this post http://blogs.msdn.com/b/chrsmith/archive/2008/05/09/f-in-20-minutes-part-ii.aspx describes well what is going on , and this StackOverflow answer http://stackoverflow.com/a/2331018/114519 goes into more advanced cases, going to places where the operator becomes a bit harder to follow.

Loops & yield: I believe you are correct, and I agree that, as it ads nothing, I should go for your suggested notation, which is equally clear but more compact.

Matching on booleans: agreed, it's not very readable. Overall, the entire while loop is hard to follow. I think the matching approach could work, but I should re-organize it a bit: for both matches, I started with the match leading to the longest body, which is not very clever, because the corresponding match is not immediately obvious. At least I should change the order, so that it looks like

match success with

| false -> ignore ()

| true ->

   // do lots of stuff here

Using if sounds like a good idea, though - because the functions don't return anything, there is really no need to match against both cases.

Task.Factory.StartNew: agreed on naming the function to avoid having a lengthy, anonymous function in there. In general, I need to break that function in smaller chunks, because it is hard to follow.

Module visibility: I haven't tried it, but I believe functions don't have to be public. The main reason I kept them public is that I wrote some unit tests in a separate projects against these functions, which requires them to be visible from the test assembly. I have to try it out, normally this is feasible by making them internal, defining what assembly they are visible to. In other words, I was lazy. Given that these are not intended to be used directly, I totally agree that making them public clutters the API, will look into it!

Again, thanks a lot for the feedback, I'll incorporate it into the code shortly!