我有一个带有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。)
更新:我成功解决了这个选项的内容,直到它成为这个函数的参数,但我仍然很想知道我是否可以进行类型测试或转换选项的对象(或通用参数),而不必担心选项包含的类型(当然假设转换是有效的,即对象确实是一个选项)。
答案 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
对于任何选项类型,这将为您None
或Some
提供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")