Hi Cobo,

Here is a more declarative version of your function that shows that F# can be very concise:

1
let dateTransform2 date = date |> String.split ['/'] |> List.rev |> List.reduce_left (+)

Felix

By on 1/21/2009 11:27 AM ()

Here's an even more concise version:

1
2
let dateTransform = String.split ['/'] >> List.rev >> List.reduce_left (+)
By on 1/22/2009 1:53 AM ()

Or using StringBuilder, but it's less concise...

1
2
let dateTransform date = date |> String.split ['/'] |> List.rev |> List.fold_left (fun (b:StringBuilder) -> b.Append) (new StringBuilder()) |> fun b->b.ToString()
By on 1/22/2009 1:59 AM ()

People who don't worry about the inefficiency of their beautiful functional implementations generally live a happier life...

...everyone else might prefer the following solution

1
2
3
let dateTransform (date: string) =
    let words = date.Split([|'/'|])
    System.String.Concat(words.[2], words.[1], words.[0])

Not using Split would be even faster, of course.

BTW, the C# compiler automatically replaces "a" + "b" + "c" with System.String.Concat("a", "b", "c"), maybe a future F# compiler will too.

Best regards,
Stephan

By on 1/22/2009 2:51 AM ()

People who don't worry about the inefficiency of their beautiful functional implementations generally live a happier life...

Writing code in a functional style does not have to be less efficient. The real issue is micromanagement of code and the performance assumptions that are made. Using a StringBuilder is not by definition faster. The pipeline example that i gave is referentially transparent. That makes it a lot easier for a compiler to change the internal implementation. The compiler could implement the string reducing step internally with String.Concat or perhaps fuse steps together. Compiler writers like Don Syme are much more aware of real performance implications than the average programmer. By not micromanaging code unless it is a clear bottleneck the compiler has more room to optimize the code and you will have code that is more concise and easier to understand.

By on 1/22/2009 5:47 AM ()

It seems the original question was actually about rewriting date strings, and from there has gone down a rabbit hole about lists and folds and performance. Admittedly the title of the post was probably prematurely optimizing people's solutions down that path. But I would actually write the function like so:

1
let dateTransform date = DateTime.ParseExact(date, "dd/mm/yyyy", null).ToString("yyyymmdd")
By on 1/22/2009 1:15 PM ()

Wow!

Thank you all for all your comments and methods. All of them were very enlightening.

I guess I now have to choose one!

Thanks,

Cheers!

By on 1/26/2009 11:34 AM ()

If you wish to concatenate the words with a separator, is there a better way to do it than this?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let concatWords (words:List<string>) =

  let sb = new System.Text.StringBuilder()

  words |> List.iter( fun s ->

    sb.Append s |> ignore

    sb.Append ' ' |> ignore

  )

  sb.Length <- sb.Length - 1 // remove trailing space

  sb.ToString()

(side topic, forum posting issues:

* Why isn't my indentation with spaces showing up in the code snippet?

* The angle brackets messed things up for the string list. )

By on 3/18/2009 10:40 AM ()

I don't want to come over all trollish, but ... :)

Stephan, I agree your code would probably be faster, but is there also quite a high risk of throwing an exception. I think you need to verify the number of items in the array. Probably wouldn't cost much in terms of perf, but could add quite a bit of code, depending on whether you want to handle the cases where there's only one or two items in the array.

Rob

By on 1/22/2009 4:47 AM ()

Personally, I prefer an exception to a silent error ;-) If the input string isn't pre-validated, you'd have to check it anyway or risk breaking some other code. Checking the correctness with the split array around is probably easier than without.

Stephan

By on 1/22/2009 5:00 AM ()

Hi Cobo,

Here is a more declarative version of your function that shows that F# can be very concise:

1
let dateTransform2 date = date |> String.split ['/'] |> List.rev |> List.reduce_left (+)

Felix

That's by far more elegant, yes! As I'm getting started with F# I've learned about pipelining, but didn't think about using it though. I see it's pretty neat.
I don't quite fully understand the List.reduce_left(+) function. From what I've just read I deduce it takes the first to element starting from the left and does the operation indicated ( '+' in this case) and then continues to do it with the rest of the elements of the list?

Thanks for everything. I just feel I've learned a lot today... :).

Cheers!

By on 1/21/2009 11:45 AM ()

The List.reduce_left (+) takes

- the function (+) (that is just another way of saying: instead of using the operator as a + b use it as (+) a b - so (+) is of type (here) (+) : string -> string -> string

- and a List of (here) strings - now it just computes (+) over all elements of the list in this way:

if the List looks like [ s1, s2, s3, ..., sn ] it goes like this:

1.) process (+) s1 s2 and get r1

2.) process (+) r1 s3 and get r2

...

n.) process (+) r(n-1) sn and yield the final result

So you just really aggregate the (+) over all the list elements.

But for performance your implementation is the better one - because if the list is really long you've got a lot of + - Operations on string objects and those are in F# just the same as in C# (or any other .NET language) ... meaning "expensive" - that's the point in using the StringBuilder-Class

BTW: try using F# not in the interactive consolre but in a VS - Project. The ToolTips for functions like List.reduce_left are great!

By on 1/21/2009 9:58 PM ()

Calls to a function which returns a value (return type not unit) have to be called with the "do" keyword if the function result isn't used.

Try this:

1
words |> List.iter(fun x -> do buf.Append(x))

StringBuilder.Append returns another instance of StringBuilder.

By on 1/21/2009 11:01 AM ()

Oh, Thanks!

I don't know if I've missed that part of the book or I haven't gone there yet... but I didn't have idea about that.

Another thing learned.

Thanks a lot!

By on 1/21/2009 11:10 AM ()

Calls to a function which returns a value (return type not unit) have to be called with the "do" keyword if the function result isn't used.

Try this:

1
words |> List.iter(fun x -> do buf.Append(x))

StringBuilder.Append returns another instance of StringBuilder.

By on 1/21/2009 11:01 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