F# Generic Map.count

狂风中的少年 提交于 2019-12-24 05:22:11

问题


This is a follow-up on this previous question, but with a different twist.

I would like to write a function which, given an object oMap, returns its count if oMap happens to be of type Map<'k,'v>, and -1 otherwise. My constraint : oMap type can only be 'discovered' at runtime.

As apparently "there is no built-in way to pattern match on a generic Map." (see link to previous question), I am using reflection for this.

namespace genericDco

module Test1 =
    let gencount (oMap : obj) : int =
        let otype   = oMap.GetType()
        let otypenm = otype.Name

        if otypenm = "FSharpMap`2" then
            // should work, as oMap of type Map<'a,'b>, but does not. *How to fix this?*
            Map.count (unbox<Map<_,_>> oMap)
        else
            // fails, as oMap is not of any type Map<'a,'b>.
            -1

    let testfailObj : int = gencount ("foo")

    // FAILS
    let testsuccessObj : int = 
        let oMap = [| ("k1", "v1");  ("k1", "v1") |] |> Map.ofArray
        gencount (box oMap)

The error being :

System.InvalidCastException: Unable to cast object of type 'Microsoft.FSharp.Collections.FSharpMap`2[System.String,System.String]' to type 'Microsoft.FSharp.Collections.FSharpMap`2[System.IComparable,System.Object]'. at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.UnboxGeneric[T](Object source)

My question: How should I rewrite the above to get this to work?

PS : I am not looking for solutions where we know at compile time that oMap is of type Map<'k,'v>, e.g. :

module Test2 =
    let gencount2<'k,'v when 'k : comparison> (gMap : Map<'k,'v>) : int =
        Map.count gMap

    let testsuccessStr : int = 
        let gMap = [| ("k1", "v1");  ("k2", "v2") |] |> Map.ofArray
        gencount2<string,string> gMap

    let testsuccessDbl : int = 
        let gMap = [| ("k1", 1.0);  ("k2", 2.0);  ("k3", 3.0) |] |> Map.ofArray
        gencount2<string,double> gMap

== EDIT ==

Thanks to Asti's suggestion, that's the solution that worked for me :

let gencount (oMap : obj) : int =
    let otype   = oMap.GetType()        
    let propt = otype.GetProperty("Count")

    try 
        propt.GetValue(oMap) :?> int
    with
    | _ -> -1

回答1:


Since Map.count is just defined as let count m = m.Count, we can just go for the Count property.

let gencount<'k,'v when 'k : comparison> map =       
   let mtype = typeof<Map<'k, 'v>>
   let propt = mtype.GetProperty("Count")

   if map.GetType() = mtype then        
       propt.GetValue(map) :?> int
   else
       -1 

Test:

[<EntryPoint>]
let main argv =
 let m = Map.ofSeq [ ("a", 1); ("b", 2)]
 printfn "%d" (gencount<string, int> m)
 printfn "%d" (gencount<string, string> m)
 Console.ReadKey() |> ignore
 0 // return exit code 0

Using _ in place of a type will simply end up as object if no additional constraint information is available. You use unbox when you strongly know what type your value is, except that the value is boxed in.



来源:https://stackoverflow.com/questions/59242217/f-generic-map-count

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!