2

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

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”中的一些东西。

4

1 回答 1

3

我觉得你现在的设计其实挺好的。通过明确列出支持的类型,您可以确保人们不会尝试调用该函数来生成无意义类型的数据(例如 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<bool>代码,代码要么崩溃要么产生废话,所以这就是为什么我认为你的原始方法也有它的好处。

或者,您可以参数化该函数以将转换器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 的评论,我尝试使用重载运算符技巧,结果你也可以这样做(这个博客在以下奇怪的片段中对到底发生了什么有最好的解释!):

// 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

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

于 2013-06-08T20:33:36.113 回答