F# simplest way to split string to tuple

Viewed 400

tl;dr

Should I implement each helper function like below?

let splitToTuple2 str =
    match str.Split() with
    | [| a; b |] -> a, b
    | _ -> failwith "size must be 2"

let splitToTuple3 str =
    match str.Split() with
    | [| a; b; c |] -> a, b, c
    | _ -> failwith "size must be 3"

let splitToTuple4 str =
    match str.Split() with
    | [| a; b; c; d |] -> a, b, c, d
    | _ -> failwith "size must be 4"

motivation

The number of words is known in advance.

let str = "first second third" // each word is separeted by space

This below is enough to do things I need.

let arr = str.Split()
let a = arr.[0] // "first"
let b = arr.[1] // "second"
let c = arr.[2] // "third"

However, I'd like to do those by one-liner.

let [| a; b; c |] = str.Split() // WARNING: Incomplete pattern matches on this expression.

#nowarn "25" is unfavorable because it affects whole file.

In this case, I need to split a string to string * string * string to avoid warning.

let a, b, c = str |> splitToTuple3

Should I implement each helper function as mentioned at the beginning?

1 Answers

Generally speaking yes, you have to create three separate functions in your case, because each function returns a different data type - tuple of two, three or four elements. You might consider creating a discriminated union to cover all your cases that would require only one function, like for example

module Split = 
    
    let split (str:string) = str.Split() 

    type SplitterTuple<'t> =
    | Two of 't * 't
    | Three of 't * 't * 't
    | Four of 't * 't * 't * 't

    let getTuple<'t> (l : 't array) : SplitterTuple<'t> = 
        match l with
        | [|a; b; c; d|] -> Four (a, b, c, d)
        | [|a; b; c|]  -> Three (a, b, c)
        | [|a; b|] -> Two (a, b)
        | _ -> failwith "Incompatible list"

This allows you to create a consumer function that uses the result of getTuple. Please note that this function returns unit for all cases.

let displayResult = function
    | Two(a, b) -> printfn "two elements: %A" (a, b)
    | Three(a, b, c) -> printfn "three elements: %A" (a, b, c)
    | Four(a, b, c, d) -> printfn "four elements: %A" (a, b, c, d)

You might want to consider using a record type instead of tuple, as it might be easier to handle.

//test code
    let t1 = "first" |> split |> getTuple |> displayResult //fails
    let t2 = "first second" |> split |> getTuple |> displayResult
    let t3 = "first second third" |> split |> getTuple |> displayResult
    let t4 = "first second third fourth" |> split |> getTuple |> displayResult
    let t5 = "first second third fourth fifth" |> split |> getTuple  |> displayResult //fails
Related