【问题标题】:How to deal with option values generically in F#如何在 F# 中一般处理选项值
【发布时间】:2014-07-19 14:01:30
【问题描述】:

我正在编写一个适配器类来将 IEnumerable 映射到 IDataReader,完整的源代码位于 https://gist.github.com/jsnape/56f1fb4876974de94238 以供参考,但我想询问编写其中一部分的最佳方法。即两个函数:

member this.GetValue(ordinal) =
    let value = match enumerator with
        | Some e -> getters.[ordinal](e.Current)
        | None -> raise (new ObjectDisposedException("EnumerableReader"))

    match value with
        | :? Option<string> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
        | :? Option<int> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
        | :? Option<decimal> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj
        | :? Option<obj> as x -> if x.IsNone then DBNull.Value :> obj else x.Value
        | _ -> value

此函数必须返回一个对象,但由于传递的值可以是下游函数(如 SqlBulkCopy)无法理解的任何 F# 选项类型,因此我需要解压缩选项并将其转换为 null/DBNull。

上面的代码有效,但我觉得它有点笨拙,因为我必须为不同的类型(浮点数等)添加新的特化。我确实尝试过使用通配符 | :? Option <_> as x -> 在匹配中,但编译器给了我一个'不太通用的警告,代码只会匹配 Option。

如何写得更惯用?我怀疑活动模式可能会起作用,但我从未使用过它们。

其他功能也类似:

member this.IsDBNull(ordinal) =
    match (this :> IDataReader).GetValue(ordinal) with
        | null -> true
        | :? DBNull -> true
        | :? Option<string> as x -> x.IsNone
        | :? Option<int> as x -> x.IsNone
        | :? Option<decimal> as x -> x.IsNone
        | :? Option<obj> as x -> x.IsNone
        | _ -> false

我不在乎它是哪种 Option 类型,我只想检查 IsNone

【问题讨论】:

  • 我只想在您的IsDBNull 成员中补充一点,您不必检查IsNone,因为None 在内部表示为null,并且将与第一种情况匹配. None :&gt; obj |&gt; function null -&gt; printfn "null" | _ -&gt; printfn "something else" 将打印“null”。

标签: f# option idatareader


【解决方案1】:

我认为你应该使用一些像这样的反射技术:

open System

let f (x:obj) =
    let tOption = typeof<option<obj>>.GetGenericTypeDefinition()
    match x with
    | null -> printfn "null"; true
    | :? DBNull -> printfn "dbnull"; true
    | _ when x.GetType().IsGenericType && x.GetType().GetGenericTypeDefinition() = tOption ->
        match x.GetType().GenericTypeArguments with
        | [|t|] when t = typeof<int> -> printfn "option int"; true
        | [|t|] when t = typeof<obj> -> printfn "option obj"; true
        | _                          -> printfn "option 't" ; true

    | _ -> printfn "default"; false


let x = 4 :> obj
let x' = f x  //default

let y = Some 4 :> obj
let y' = f y  // option int

let z = Some 0.3 :> obj
let z' = f z  // option 't

更新

事实上,如果您只想检查所有选项类型的 IsNone 情况,并且不想使用反射,则不需要其他情况,它们将属于 null 情况,因为 None 被编译为 null。例如使用前面的函数试试这个:

let y1 = (None: int option)  :> obj
let y1' = f y1  // null

let z1 = (None: float option)  :> obj
let z1' = f z1  // null

它正在与第一个案例(空案例)一起处理

对于 GetValue 成员,我查看了您的要点,因为您已经在包含该成员的类型中定义了通用 'T,您可以编写:

match value with
| :? Option<'T> as x -> if x.IsNone then DBNull.Value :> obj else x.Value :> obj

适用于所有选项类型。

【讨论】:

  • 出于性能原因,我试图远离反射。这将用于将数千万行数据批量加载到 Sql Server 中,因此可以多次调用此代码。在完整的类中,我为每个属性获取器编译特定的代码,以免产生反射开销。
  • 是的,我的问题的 cmets 中提到了这一点。我仍然对实现 GetValue 的答案感兴趣。
  • @James 是的,它在 cmets 中被提及。我刚刚在更新中添加了 GetValue 的提案。
  • 不幸的是,类中的 'T 不是选项类型,所以我认为这不起作用。
  • 请注意,您可以使用let tOption = typedefof&lt;option&lt;_&gt;&gt; 节省一些击键次数。
【解决方案2】:

正如 Gustavo 的回答所暗示的,您应该为此使用反射。如果参数'a 在编译时未知,则没有其他方法可以将obj 转换为option&lt;'a&gt; 类型。相反,您必须将参数检查为 System.Type 对象,然后决定下一步做什么。

这样做的一般方法是设置一个函数,该函数可以将任何选项类型作为参数,并返回其类型不依赖于选项类型的参数的东西。在确定参数类型后,可以通过反射调用此函数。

要定义可以将任何选项类型作为参数的函数,辅助接口很有用,因为我们可以在该接口内定义通用方法:

type IGenericOptionHandler<'result> =
    abstract Handle<'a> : 'a option -> 'result

注意接口整体比Handle方法的return类型'result是泛型的,但是内部'a参数只在方法本身的定义中提到.

我们现在可以定义一个函数来调用这个接口:

let handleGeneric
        (handle : IGenericOptionHandler<'result>)
        (x : obj)  // something that might be an option type
        (defaultValue : 'result) // used if x is not an option type
      : 'result =

    let t = x.GetType()
    if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option>
        then
            match t.GetGenericArguments() with
            | [|tArg|] ->
                handle
                  .GetType()
                  .GetMethod("Handle")
                  .MakeGenericMethod([|tArg|])
                  .Invoke(handle, [|x|])
                 :?> 'result
            | args -> failwith "Unexpected type arguments to option: %A" args
        else defaultValue

最后我们可以用对象表达式方便地调用它,例如以下将充当类似于上述IsDBNull 的通用选项类型检测器 - 您需要在defaultValue 参数中添加DBNull 的特殊情况才能准确复制它。

Option.handleGeneric
    { new IGenericOptionHandler<bool> with member this.Handle _ = true }
    (Some 5)
    false

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    • 2013-08-30
    • 1970-01-01
    • 1970-01-01
    • 2022-10-14
    • 1970-01-01
    相关资源
    最近更新 更多