如何从obj向下转换为选项<obj>?</obj>

时间:2011-06-09 07:58:17

标签: f# casting

我有一个带有object类型参数的函数,需要将它向下转换为option<obj>

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

如果我将Option<string>作为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<obj>

为了使这项工作,我当前必须指定选项包含的类型(例如,如果函数传递option<string>,并且我将参数向下转换为{而不是option<obj>,功能有效。

有没有办法我可以将参数转发到option<obj>而不指定选项包含的类型?我尝试了option<_>option<#obj>option<'a>,结果相同。

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

更新:我成功解决了这个选项的内容,直到它成为这个函数的参数,但我仍然很想知道我是否可以进行类型测试或转换选项的对象(或通用参数),而不必担心选项包含的类型(当然假设转换是有效的,即对象确实是一个选项)。

3 个答案:

答案 0 :(得分:7)

目前没有任何好方法可以解决这个问题。

问题是你需要在模式匹配中引入一个新的泛型类型参数(当与option<'a>匹配时),但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))

答案 1 :(得分:2)

我不确定为什么你需要把你的输入作为obj,但如果你的输入是一个选项&lt; _&gt;,那么很容易:

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

答案 2 :(得分:1)

回答你的上一个问题:如果你需要一种通用的方法来检查没有装箱值的选项,你可以使用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")