【问题标题】:How to downcast from obj to option<obj>?如何从 obj 向下转换为 option<obj>?
【发布时间】:2011-06-09 07:58:16
【问题描述】:

我有一个接受对象类型参数的函数,需要将其向下转换为option&lt;obj&gt;

member s.Bind(x : obj, rest) =
    let x = x :?> Option<obj>

如果我将(例如)Option&lt;string&gt; 作为x 传递,则最后一行将引发异常:无法转换类型为 'Microsoft.FSharp.Core.FSharpOption'1[System.String] 的对象' 输入 'Microsoft.FSharp.Core.FSharpOption'1[System.Object]'。

或者,如果我尝试类型测试:

member s.Bind(x : obj, rest) =
   match x with
    | :? option<obj> as x1 -> ... // Do stuff with x1
    | _ -> failwith "Invalid type"

那么x 永远不会匹配option&lt;obj&gt;

为了完成这项工作,我目前必须指定选项包含的类型(例如,如果函数传递了 option&lt;string&gt;,并且我将参数向下转换为该参数而不是 option&lt;obj&gt;,则该函数有效。

有没有一种方法可以将参数向下转换为 option&lt;obj&gt; 而无需指定选项包含的类型?我尝试了option&lt;_&gt;option&lt;#obj&gt;option&lt;'a&gt;,结果相同。

作为背景,参数需要是obj类型,因为我正在为一个monad写一个接口,所以Bind需要根据实现该接口的monad来绑定不同类型的值。这个特定的 monad 是一个 continuation monad,所以它只是想确保参数是 Some(x) 而不是 None,然后将 x 传递给 rest。 (我需要接口的原因是因为我正在编写一个 monad 转换器,我需要一种方法来告诉它它的参数 monad 实现了绑定和返回。)

更新:我设法通过在选项成为此函数的参数之前向上转换选项的内容来解决这个问题,但我仍然很想知道我是否可以进行类型测试或转换一个对象(或通用参数)到一个选项,而不用担心选项包含什么类型(当然假设强制转换是有效的,即对象确实是一个选项)。

【问题讨论】:

  • 我对你的问题做了一些额外的格式化,你可能想查看源(编辑)按钮,看看一些降价技巧。
  • 谢谢kvb,这些看起来确实很有帮助。在发布这个问题之前,我花了一段时间挖掘这些信息,但当然不得不使用“选项”这个词出现了很多不相关的结果。

标签: f# casting


【解决方案1】:

目前没有什么好的方法可以解决这个问题。

问题是您需要在模式匹配中引入一个新的泛型类型参数(当匹配 option&lt;'a&gt; 时),但 F# 只允许您在函数声明中定义泛型类型参数。因此,您唯一的解决方案是使用一些反射技巧。例如,您可以定义一个隐藏此的活动模式:

let (|SomeObj|_|) =
  let ty = typedefof<option<_>>
  fun (a:obj) ->
    let aty = a.GetType()
    let v = aty.GetProperty("Value")
    if aty.IsGenericType && aty.GetGenericTypeDefinition() = ty then
      if a = null then None
      else Some(v.GetValue(a, [| |]))
    else None

这将为您提供NoneSome,其中包含任何选项类型的obj

let bind (x : obj) rest =   
    match x with    
    | SomeObj(x1) -> rest x1
    | _ -> failwith "Invalid type"

bind(Some 1) (fun n -> 10 * (n :?> int))

【讨论】:

  • 非常感谢托马斯。这对我有用,很高兴知道我没有遗漏一些明显的东西。感谢大家的快速回复。
  • 可以嵌套模式匹配以测试特定类型的options。例如,可以将第 3 行更改为 | SomeObj(:? string as x1) -&gt; rest x1 以测试 option&lt;string&gt;
  • 试试match None with SomeObj x -&gt; printfn "%A" x | _ -&gt; printfn "Not an option"。它不处理None
【解决方案2】:

我不确定你为什么需要将输入作为 obj,但如果你的输入是 Option<_>,那么很容易:

member t.Bind (x : 'a option, rest : obj option -> 'b) =
    let x = // val x : obj option
        x
        |> Option.bind (box >> Some)
    rest x

【讨论】:

  • 感谢您的回答拉蒙。我的输入必须是 obj 类型,因为我需要接受例如字符串或选项。如果我只是使用 'a 作为输入类型,那么 F# 会在传入任何类型的选项时抱怨(即它说它期待一个 'a 但得到一个 'a 选项)。使用 obj 作为参数类型是我发现解决此问题的唯一方法。我当然愿意接受更好的方法。
  • (在最后一个回复中空间不足。)由于在 Bind 的接口定义中将 x 指定为 obj,因此我无法使用您提到的代码,如 Option.bind(box >> Some) 导致错误:类型不匹配。期待一个 obj -> 'a 但给出了一个 'b 选项 -> 'c 选项。 “obj”类型与“a option”类型不匹配。
  • 我看到了你的问题。如果您以前不喜欢向上转换内容,或者不喜欢 Tomas 的反射(这可能是正确的方法),您可以使用 F# 编译器的自定义版本,它支持更多的方法,而不仅仅是 monads 中的 Bind。例如,这是一种选择:ramon.org.il/wp/2011/04/…
  • 非常感谢 Ramon 的链接,这看起来很有趣,我很期待,尤其是您对编译器扩展的实现。
  • 这一切都是因为我想找到一种“堆叠”多个 monad 的方法,即从同一段代码中抽象出多个正交关注点(例如延续和日志记录)。据我了解,您可以在 Haskell (blog.sigfpe.com/2006/05/grok-haskell-monad-transformers.html) 中相当容易地做到这一点,但在 F# 中却不那么容易。但我认为我目前有一些基本的工作,并且可能会在进行更多测试后尝试将其作为回复发布在这里。再次感谢您。
【解决方案3】:

回答你的最后一个问题:如果你需要一种通用的方法来检查选项而不预先装箱值,你可以使用 Tomas 代码的轻微变化:

let (|Option|_|) value = 
  if obj.ReferenceEquals(value, null) then None
  else
    let typ = value.GetType()
    if typ.IsGenericType && typ.GetGenericTypeDefinition() = typedefof<option<_>> then
      let opt : option<_> = (box >> unbox) value
      Some opt.Value
    else None
//val ( |Option|_| ) : 'a -> 'b option    

let getValue = function
  | Option x ->  x
  | _ -> failwith "Not an option"

let a1 : int = getValue (Some 42)
let a2 : string = getValue (Some "foo")
let a3 : string = getValue (Some 42) //InvalidCastException
let a4 : int = getValue 42 //Failure("Not an option")

【讨论】:

  • 谢谢 Daniel,正如 Tomas 所说,很高兴有代码显示如何处理其中一些模式匹配项,否则只能在函数声明级别工作。
  • 很遗憾,上述代码在 F# 3.0 中不再编译:Active pattern '|Option|_|'具有包含不由输入确定的类型变量的结果类型。常见原因是未提及结果案例时,例如'让 (|A|B|) (x:int) = A x'。这可以通过类型约束来修复,例如'让 (|A|B|) (x:int) : 选择 = A x'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多