【问题标题】:How to generate an array with a dynamic type in F#如何在 F# 中生成具有动态类型的数组
【发布时间】:2013-06-08 18:38:23
【问题描述】:

我正在尝试编写一个函数来返回一个动态类型的数组。这是我尝试过的一种方法的示例:

let rng = System.Random()

type ConvertType =
    | AsInts
    | AsFloat32s
    | AsFloats
    | AsInt64s

type InputType =
    | Ints of int[]
    | Float32s of float32[]
    | Floats of float[]
    | Int64s of int64[]

let genData : int -> int -> ConvertType -> InputType * int[] =
    fun (sCount:int) (rCount:int) (ct:ConvertType) ->
        let source = 
            match ct with
            | AsInts -> Array.init sCount (fun _ -> rng.Next()) |> Array.map (fun e -> int e) |> Ints
            | AsFloat32s -> Array.init sCount (fun _ -> rng.Next()) |> Array.map (fun e -> float32 e) |> Float32s
            | AsFloats -> Array.init sCount (fun _ -> rng.Next()) |> Array.map (fun e -> float e) |> Floats
            | AsInt64s -> Array.init sCount (fun _ -> rng.Next()) |> Array.map (fun e -> int64 e) |> Int64s
        let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
        source, indices

我遇到的问题是,当我使用该函数时,我需要数组是原始类型,例如float32[] 而不是“InputType”。

我也尝试过通过内联函数创建的接口并使用泛型来实现。我也无法让它按照我想要的方式工作,但我可能做错了。

编辑: 感谢您的出色回复,我今天必须尝试一下。我添加编辑是因为我解决了我的问题,尽管我没有按照我想要的方式解决它(即喜欢答案)。所以对于那些可能会看这个的人来说,我做了以下事情:

let counts = [100; 1000; 10000]
let itCounts = [ 1000; 500; 200]

let helperFunct =
    fun (count:int) (numIt:int) (genData : int -> int -> ('T[] * int[] )) ->
        let c2 = int( count / 2 )
        let source, indices = genData count c2
        ....

[<Test>]
let ``int test case`` () =
    let genData sCount rCount =
        let source = Array.init sCount (fun _ -> rng.Next())
        let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
        source, indices

    (counts, itCounts) ||> List.Iter2 (fun s i -> helperFunct s i genData)
    .....

那么每个进行中的测试用例会是这样的:

[<Test>]
let ``float test case`` () =
    let genData sCount rCount =
        let source = Array.init sCount (fun _ -> rng.Next()) |> Array.map (fun e -> float e)
        let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
        source, indices
    .....

但是,我提出这个问题的全部原因是我试图避免为每个测试用例重写 genData 函数。在我的真实代码中,这个临时解决方案让我不必分解“helperFunct”中的一些东西。

【问题讨论】:

    标签: arrays generics dynamic f# type-conversion


    【解决方案1】:

    我觉得你现在的设计其实挺好的。通过明确列出支持的类型,您可以确保人们不会尝试调用该函数来生成没有意义的类型的数据(比如byte)。

    您可以使用System.Convert 编写一个通用函数(它允许您将值转换为任意类型,但如果这没有意义可能会失败)。它也(很可能)效率会降低,但我没有测量到:

    let genDataGeneric<'T> sCount rCount : 'T[] * int[] =
      let genValue() = System.Convert.ChangeType(rng.Next(), typeof<'T>) |> unbox<'T>
      let source = Array.init sCount (fun _ -> genValue())
      let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
      source, indices
    

    然后你就可以写了

    genDataGeneric<float> 10 10
    

    但是人们也可以写genDataGeneric&lt;bool&gt;,代码要么崩溃要么产生废话,所以我认为你的原始方法也有它的好处。

    或者,您可以参数化该函数以采用一个转换器,将int(这是您从rng.Next 得到的)转换为您想要的类型:

    let inline genDataGeneric convertor sCount rCount : 'T[] * int[] =
      let genValue() = rng.Next() |> convertor
      let source = Array.init sCount (fun _ -> genValue())
      let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
      source, indices
    

    然后你可以只写getDataGeneric float 10 10,它仍然很优雅,而且效率也很高(我添加了inline,因为我认为它可以在这里有所帮助)

    编辑:根据 Leaf 的评论,我尝试使用 overloaded operator trick,结果你也可以这样做(this blog has the best explanation 在下面奇怪的 sn 中发生了什么-p!):

    // Specifies conversions that we want to allow
    type Overloads = Overloads with
        static member ($) (Overloads, fake:float) = fun (n:int) -> float n
        static member ($) (Overloads, fake:int64) = fun (n:int) -> int64 n
    
    let inline genDataGeneric sCount rCount : 'T[] * int[] =
      let convert = (Overloads $ Unchecked.defaultof<'T>)
      let genValue() = rng.Next() |> convert
      let source = Array.init sCount (fun _ -> genValue())
      let indices = Array.init rCount (fun _ -> rng.Next sCount) |> Array.sort
      source, indices
    
    let (r : float[] * _) = genDataGeneric 10 10  // This compiles & works
    let (r : byte[] * _) = genDataGeneric 10 10   // This does not compile
    

    这很有趣,但请注意,您仍然必须在某处指定类型(通过稍后使用或使用类型注释)。如果您使用类型注释,那么早期的代码可能更简单,并且提供的错误消息更少。但是这里使用的技巧肯定很有趣。

    【讨论】:

    • 我一直在使用op_Explicit 周围的通用约束来查看这是否可能(我认为不可能)并且没有考虑通过转换器。不错。
    • @LeafGarland 使用op_Explicit 的想法听起来很有趣。也许可以使用这个技巧:stackoverflow.com/questions/12971965/…
    • @LeafGarland 哇,谢谢你的建议。它实际上可以以一种更有趣的方式完成:-)
    • 令人着迷。感谢您的链接。
    • @TomasPetricek 在我的例子中,“索引”用于从源中删除元素,所以我将该行更改为code |> Seq.distinct |> Seq.toArray |> Array。排序 - 非常有用的小功能.. 谢谢大家!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    相关资源
    最近更新 更多