Ta Thanh Dinh
Ta Thanh Dinh

Reputation: 661

Generic string to integer conversion in F#

I'm implementing a simple conversion string to integer convertor in F#; the logic is, e.g from string to uint32:

let inline sToUint s =
    let mutable v = 0u

    for c in s do
        v <- v * 10u + uint32 c - uint32 '0'

    v

Now I would like to use this implementation for other integer types, e.g. uint64. How can I implement a single generic function to do that?

Thank for all, SRTP (as used in the answers) is what I need for such a situation. The following implementation is just slightly changed from the answer of @Brian Berns

// (char -> ^a) -> (string -> ^b)
let inline sToNum f =
    let tenTimes v = (v <<< 3) + (v <<< 1)
    fun (s: string) ->
        let mutable v = Unchecked.defaultof<_>
        for c in s do
            v <- (tenTimes v) + f c - f '0'
        v

Upvotes: 3

Views: 121

Answers (2)

JL0PD
JL0PD

Reputation: 4498

If it's done for practical reasons, it's better to use SRTP to create function that could be used to parse anything that have static method Parse: string -> ^a

let inline parse (input: string) : ^a =
    ( ^a: (static member Parse: string -> ^a) (input))

(parse "123" : int) |> printfn "%A"
(parse "123" : int64) |> printfn "%A"
(parse "123" : byte) |> printfn "%A"
(parse "123" : int16) |> printfn "%A"
(parse "123" : float32) |> printfn "%A"
(parse "123" : double) |> printfn "%A"
(parse "123" : string) |> printfn "%A" // fails to compile, as string doesn't have Parse method

Note that parse throws exception if input in invalid format. To be able to handle that, you can use TryParse active pattern

let inline (|TryParse|_|) (input: string) : ^a option =
    let mutable result = Unchecked.defaultof< ^a >
    let success = ( ^a: (static member TryParse: string * ^a byref -> bool) (input, &result))
    if success then
        Some result
    else
        None

match "123" with
| TryParse (a : int) -> printfn "it's int %d" a
| TryParse (a : int64) -> printfn "it's very big int %d" a
| TryParse (a : float) -> printfn "it's double %f" a
| x -> printfn "it's something I can't handle: %s" x

Note that both functions will use CultureInfo.CurrentCulture as default. They can be edited to use CultureInfo.InvariantCulture, but I will leave it for homework

Upvotes: 2

Brian Berns
Brian Berns

Reputation: 17038

You can do something like this:

let inline sToNum f =
    let ten = Seq.replicate 10 LanguagePrimitives.GenericOne |> Seq.sum
    fun s ->
        let mutable v = LanguagePrimitives.GenericZero
        for c in s do
            v <- v * ten + f c - f '0'
        v

let sToUint16 = sToNum uint16
let sToUint32 = sToNum uint32
let sToUint64 = sToNum uint64
let sToInt16 = sToNum int16
let sToInt32 = sToNum int32
let sToInt64 = sToNum int64

Test code:

sToUint16 "123" |> printfn "%A"   // 123us
sToUint32 "123" |> printfn "%A"   // 123u
sToUint64 "123" |> printfn "%A"   // 123UL
sToInt16 "123" |> printfn "%A"    // 123s
sToInt32 "123" |> printfn "%A"    // 123
sToInt64 "123" |> printfn "%A"    // 123L

Note that sToNum returns a lambda, so it doesn't have to recompute ten each time. (You could even cache the value of f '0' as well, to shave off a little more time.)

Upvotes: 5

Related Questions