views:

145

answers:

4

Hello, I am just learning F# and have been converting a library of C# extension methods to F#. I am currently working on implementing a function called ConvertFirstLetterToUppercase based on the C# implementation below:

public static string ConvertFirstLetterToUppercase(this string value) {
    if (string.IsNullOrEmpty(value)) return value;
    if (value.Length == 1) return value.ToUpper();
    return value.Substring(0, 1).ToUpper() + value.Substring(1);
}

The F# implementation

[<System.Runtime.CompilerServices.ExtensionAttribute>]
module public StringHelper
    open System
    open System.Collections.Generic
    open System.Linq

    let ConvertHelper (x : char[]) =  
        match x with
            | [| |] | null -> ""
            | [| head; |] -> Char.ToUpper(head).ToString()
            | [| head; _ |] -> Char.ToUpper(head).ToString() + string(x.Skip(1).ToArray())

    [<System.Runtime.CompilerServices.ExtensionAttribute>]
    let ConvertFirstLetterToUppercase (_this : string) =
        match _this with
        | "" | null -> _this
        | _ -> ConvertHelper (_this.ToCharArray())

Can someone show me a more concise implementation utilizing more natural F# syntax?

+4  A: 

Try the following

[<System.Runtime.CompilerServices.ExtensionAttribute>]
module StringExtensions = 
    let ConvertFirstLetterToUpperCase (data:string) =
        match Seq.tryFind (fun _ -> true) data with
        | None -> data
        | Some(c) -> System.Char.ToUpper(c).ToString() + data.Substring(1)

The tryFind function will return the first element for which the lambda returns true. Since it always returns true it will simply return the first element or None. Once you've established there is at least one element you know data is not null and hence can call Substring

JaredPar
I like the solution! I was kind of hoping that cons would work with arrays so I could do pattern matching on the expression that actually created the result, but alas, that is not part of the implementation. :-/
Norman H
+12  A: 
open System

type System.String with
    member this.ConvertFirstLetterToUpperCase() =
        match this with
        | null -> null
        | "" -> ""
        | s -> s.[0..0].ToUpper() + s.[1..]

Usage:

> "juliet".ConvertFirstLetterToUpperCase();;
val it : string = "Juliet"
Juliet
Now if you want something a little more efficient than substrings, try `let chars = s.ToCharArray(); chars.[0] <- Char.ToUpper(chars.[0]); new String(chars)`
Juliet
+1, love the use of [n..n]. Haven't seen that applied in a good case bofer.
JaredPar
Is the array version really faster? The substring version makes a copy of almost the whole string (s.[1..]) and then another string is created when that string is added to the first char. The array version copies the string to an array, then the array is copied to a string. Seems like 2 copy operations either way - or is array conversion faster than string copying?
John Reynolds
You could add an extra case after null/empty string as: | s when s.Length = 1 -> s.ToUpper ()
jessicah
+3  A: 

Something like this?

[<System.Runtime.CompilerServices.ExtensionAttribute>]
module public StringHelper = 
[<System.Runtime.CompilerServices.ExtensionAttribute>]
let ConvertFirstLetterToUppercase (t : string) =
    match t.ToCharArray() with
    | null -> t
    | [||] -> t
    | x -> x.[0] <- Char.ToUpper(x.[0]); System.String(x)
desco
I like this one, just a note, you can combine the null or empty array checks with the following to reduce line count:| null | [||] -> this
Norman H
+1  A: 

There's nothing wrong with using .NET library functions from a .NET language. Maybe a direct translation of your C# extension method is most appropriate, particularly for such a simple function. Although I'd be tempted to use the slicing syntax like Juliet does, just because it's cool.

open System
open System.Runtime.CompilerServices

[<Extension>]
module public StringHelper =

    [<Extension>]
    let ConvertFirstLetterToUpperCase(this:string) =
        if String.IsNullOrEmpty this then this
        elif this.Length = 1 then this.ToUpper()
        else this.[0..0].ToUpper() + this.[1..]
Joel Mueller
I really prefer the use of the intrinsic language features over the Framework API's just because of the less verbose language style.
Norman H