Here is one quick thought: type inference interacts. Consider:

1
2
3
 
let f a =
    a |> Array.map (fun x -> x + 1)

Here, the type of 'a' is inferred, whereas with your strategy, the type of 'a' must be known to be an array 'a priori'. That's one trade-off that springs to mind.

By on 5/11/2009 9:57 AM ()

Here is one quick thought: type inference interacts. Consider:

1
2
3
 
let f a =
    a |> Array.map (fun x -> x + 1)

Here, the type of 'a' is inferred, whereas with your strategy, the type of 'a' must be known to be an array 'a priori'. That's one trade-off that springs to mind.

F# coders need to stop abusing the pipeline operator so much, in general you should prefer function composition operator(s) (<< is fg and >> is gf in F#) over pipeline operators (which are just function application with right associativity and thus eliminates the need for parentheses in most cases).

You should prefer function composition over pipeline in most cases because:

  • Function composition promotes point-free programming ([link:www.haskell.org]
  • Promotes code reuse, you can build complicated functions out of smaller simple ones.
  • Removes the need of explicit lambda expressions in most cases.

This is how I would write your example:

let f = Array.map ((+) 1)

Note I'm not saying you should never use the pipeline operators i'm saying if you can you should prefer function composition over it.

By on 5/12/2009 12:40 PM ()

Regarding the original question, I think it is more idiomatic for F# programming to use pipelining operator rather than fluent style, but that's probably only a matter of style. F# is rich enough to allow you to use the style you prefer and that's one of the great things about it. On the other hand it's also dangerous because everyone can use his own F# style...

One point why the pipelining style may be better when using "functional style" is that it works better with functions taking multiple arguments using currying:

1
2
3
4
5
6
7
8
// using pipelining
x |> Test.foo (fun a -> a + 1) 1 |> Test.bar 3 4 |> Test.count

// using fluent style
((x.foo (fun a -> a + 1) 1).bar 3 4).count

// alternatively if parameters are tupled
x.foo((fun a -> a + 1), 1).bar(3, 4).count

The last option is shorter than the usual version with pipelining, but I still find it more convenient to use the first one. Also, I'm wondering how long these processing pipelines usually are..? They can contain a lot of operations, but that's usually more frequent in the early development/experimental phase. I'd say that later they are refactored into a few functions that are themselves relatively simple....

F# coders need to stop abusing the pipeline operator so much, in general you should prefer function composition operator(s) (<< is fg and >> is gf in F#) over pipeline operators (which are just function application with right associativity and thus eliminates the need for parentheses in most cases).

I'd disagree with this. Of course, it is again just a matter of style, but overusing function composition and various other "pointfree" tricks from Haskell can lead to code that's impossible to read and impossible to debug. I think that a very careful ballance is needed for good F# code.

By on 5/16/2009 8:20 AM ()

F# coders need to stop abusing the pipeline operator so much, in general you should prefer function composition operator(s) (<< is fg and >> is gf  in F#) over pipeline operators (which are just function application with right associativity and thus eliminates the need for parentheses in most cases).

Nyeah. While I tend to agree in principle, value restriction often causes me to introduce the pipeline instead of functional composition. Also, due to the way infix is handled, it could be confusing, e.g.

1
let result s = Seq.filter ((<) 10) s

filters integers greater than 10, not smaller than.

By on 5/13/2009 11:35 AM ()

Hi Brian,

Agreed, when type information flows from a method's argument back to the object, fluency is impossible. The pipe in conjunction with static methods certainly can provide information that otherwise is missing - which is a benefit of the more verbose syntax.

A tricky question is what to do with the methods on Seq? Whereas adding Array functions to 'a[] and List functions to List<'a> is simple, if we all add Seq functions to IEnumerable<'a> so:

1
2
3
4
5
6
open System.Collections.Generic

type IEnumerable<'a> with
    /// Return the first n elements of the sequence.
    member x.take n = x |> Seq.take n
    /// Another 67 member definitions to go !!!

we end up with many conflicts with the Array and List methods i.e. overridden type extensions - which FSharp currently handles poorly.

Should I use type extensions on Seq or Array and List? Or perhaps both taking extreme care to open one of the extension modules at at time (no [<AutoOpen>]!) Any ideas?

regards,

Danny

By on 5/12/2009 11:31 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper