Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Pattern-matching by type

How pattern-matching by type of argument works in F#?

For example I'm trying to write simple program which would calculate square root if number provided or return it's argument otherwise.

open System

let my_sqrt x =
  match x with
  | :? float as f -> sqrt f
  | _ -> x


printfn "Enter x"
let x = Console.ReadLine()

printfn "For x = %A result is %A" x (my_sqrt x)

Console.ReadLine()

I get this error:

error FS0008: This runtime coercion or type test from type
    'a    
 to 
    float    
involves an indeterminate type based on information prior 
to this program point. Runtime type tests are not allowed
on some types. Further type annotations are needed.

Since sqrt works with float I check for float type, but guess there could be better solution - like check if input is number (in general) and if so, cast it to float?

like image 678
Nik Avatar asked Mar 24 '23 21:03

Nik


2 Answers

The problem here is that the type of x is actually a string. Adding that it comes from Console.ReadLine, what kind of information is stored in that string is only possible to determine at runtime. This means that you can't use neither pattern matching, nor pattern matching with coercion here.

But you can use Active Patterns. As what actual data is stored in x is only known at runtime, you have to parse the string and see what is contains.

So suppose you are expecting a float, but you can't be sure since user can input whatever they want. We are going to try and parse our string:

let my_sqrt x =
    let success, v = System.Single.TryParse x // the float in F# is represented by System.Single in .NET
    if success then sqrt v
    else x

But this won't compile:

This expression was expected to have type float32 but here has type string

The problem is that the compiler inferred the function to return a float32, based on the expression sqrt (System.Single.Parse(x)). But then if the x doesn't parse to float, we intend to just return it, and as x is a string we have an inconsistency here.

To fix this, we will have to convert the result of sqrt to a string:

let my_sqrt x =
    let success, v = System.Single.TryParse x
    if success then (sqrt v).ToString()
    else x

Ok, this should work, but it doesn't use pattern matching. So let's define our "active" pattern, since we can't use regular pattern matching here:

let (|Float|_|) input =
    match System.Single.TryParse input with
    | true, v -> Some v
    | _ -> None

Basically, this pattern will match only if the input can be correctly parsed as a floating point literal. Here's how it can be used in your initial function implementation:

let my_sqrt' x =
    match x with
    | Float f -> (sqrt f).ToString()
    | _ -> x

This looks a lot like your function, but note that I still had to add the .ToString() bit.

Hope this helps.

like image 112
MisterMetaphor Avatar answered Apr 01 '23 21:04

MisterMetaphor


Just quoting the one and only Scott Wlaschin's 'F# for fun and profit' site:

Matching on subtypes You can match on subtypes, using the :? operator, which gives you a crude polymorphism:

let x = new Object()
let y = 
    match x with 
    | :? System.Int32 -> 
        printfn "matched an int"
    | :? System.DateTime -> 
        printfn "matched a datetime"
    | _ -> 
        printfn "another type"

This only works to find subclasses of a parent class (in this case, Object). The overall type of the expression has the parent class as input.

Note that in some cases, you may need to “box” the value.

let detectType v =
    match v with
        | :? int -> printfn "this is an int"
        | _ -> printfn "something else"
// error FS0008: This runtime coercion or type test from type 'a to int  
// involves an indeterminate type based on information prior to this program point. 
// Runtime type tests are not allowed on some types. Further type annotations are needed.

The message tells you the problem: “runtime type tests are not allowed on some types”. The answer is to “box” the value which forces it into a reference type, and then you can type check it:

let detectTypeBoxed v =
    match box v with      // used "box v" 
        | :? int -> printfn "this is an int"
        | _ -> printfn "something else"

//test
detectTypeBoxed 1
detectTypeBoxed 3.14

In my opinion, matching and dispatching on types is a code smell, just as it is in object-oriented programming. It is occasionally necessary, but used carelessly is an indication of poor design.

In a good object oriented design, the correct approach would be to use polymorphism to replace the subtype tests, along with techniques such as double dispatch. So if you are doing this kind of OO in F#, you should probably use those same techniques.

like image 28
jdearana Avatar answered Apr 01 '23 20:04

jdearana