当我将其作为本地值传递但不作为参数传递时,我是否可以使用具有不同类型参数的泛型函数? 例如:
let f = id
let g (x,y) = (f x, f y)
g ( 1, '2')
工作正常,但如果我尝试将该函数作为参数传递
let g f (x,y) = (f x, f y)
g id ( 1, '2')
它失败了,因为它需要版本f< int>它试图将它应用两次。
我找到了一个解决方法,但它迫使我写两次我正在传递的函数:
let g f1 f2 (x,y) = (f1 x, f2 y)
g id id ( 1, '2')
这是我第二次遇到这个问题。
为什么它以这种方式运行,如果函数是本地值或者它作为参数传递,它不应该是相同的?
有没有办法在不重复功能的情况下执行此操作?
黑客,可能使用显式类型约束,内联魔法,引用?
答案 0 :(得分:7)
这是内联魔术。
让我们采用kvb的代码并定义一个处理所有情况的gmap
函数:
let inline gmap f (x, y) = f $ x, f $ y
type One = One with static member ($) (One, x) = 1 // Example1 ConvertAll
type Id = Id with static member ($) (Id , x) = x // Example2 PassThrough
type SeqSingleton = SeqSingleton with static member ($) (SeqSingleton , x) = seq [x]
type ListSingleton = ListSingleton with static member ($) (ListSingleton, x) = [x]
type ListHead = ListHead with static member ($) (ListHead, x) = List.head x
// Usage
let pair1 = gmap One ("test", true)
let pair2 = gmap Id ("test", true)
let pair3 = gmap SeqSingleton ("test", true)
let pair4 = gmap ListSingleton ("test", true)
let pair5 = gmap ListHead (["test";"test2"], [true;false])
let pair6 = ("test", true) |> gmap ListSingleton |> gmap ListHead
(* results
val pair1 : int * int = (1, 1)
val pair2 : string * bool = ("test", true)
val pair3 : seq<string> * seq<bool> = (["test"], [true])
val pair4 : string list * bool list = (["test"], [true])
val pair5 : string * bool = ("test", true)
val pair6 : string * bool = ("test", true)
*)
更新
也可以使用更通用的gmap
函数定义here,然后它也可以使用n-uples(n <9)。
答案 1 :(得分:6)
正如rkhayrov在评论中提到的,当你可以拥有更高排名的类型时,类型推断是不可能的。在您的示例中,您有
let g f (x,y) = (f x, f y)
以下是g
的两种可能不兼容的类型(用一种混合的F#/ Haskell语法编写):
鉴于第一种类型,我们可以致电g (fun x -> 1) ("test", true)
并获取(1,1)
。鉴于第二种类型,我们可以调用g id ("test", true)
并获取("test", true)
。两种类型都不比另一种更通用。
如果你想在F#中使用排名较高的类型,你可以,但你必须明确并使用中间名义类型。以下是对上述每种可能性进行编码的一种方法:
module Example1 =
type ConvertAll<'b> =
abstract Invoke<'a> : 'a -> 'b
let g (f:ConvertAll<'b>) (x,y) = (f.Invoke x, f.Invoke y)
//usage
let pair = g { new ConvertAll<int> with member __.Invoke(x) = 1 } ("test", true)
module Example2 =
type PassThrough =
abstract Invoke<'a> : 'a -> 'a
let g (f:PassThrough) (x,y) = (f.Invoke x, f.Invoke y)
//usage
let pair = g { new PassThrough with member __.Invoke(x) = x } ("test", true)
答案 2 :(得分:2)
我猜这是预期的行为:
在第一种情况下,您调用两个不同版本的f
(一个使用int
,一个使用char
),在第二种情况下,您对两者都使用相同的编译器推断它(上下,左右 - 记得吗?)是int->int
问题在于通用版本将由编译器以具体的版本进行翻译。我认为没有解决方法 - 甚至没有使用inline
,但也许有人可以在这里工作一些魔法;)
答案 3 :(得分:0)
我认为即使内联魔法也不会在这里工作,例如尝试
let inline g f (x:^a,y:^b) = (f x,f y);;
FSI中的失败,因为编译器推断^b
必须与^a
相同
考虑到这一点,失败变得更加明显,因为我们使用函数f并将其应用于x,它必须具有类似^a -> 'c
的签名,如果我们尝试将函数应用于类型为^b
的元素。
如果您考虑这个问题,我无法定义一个函数,该函数需要int->string
并期望它在没有重载的情况下适用于char->string
,并且您不能将重载作为参数传递
答案 4 :(得分:0)
原因是当f作为参数传递给g时,编译器将尝试推断f的类型。基于函数g的主体中f的使用,编译器推断该函数应用于x和y,这意味着x和y参数必须是相同类型并且是f参数的类型。在您的代码示例中,您将x作为整数传递,这使编译器推断y也将是整数类型。
<强>更新强>
您可以执行以下操作:
let g (f:obj -> obj) (x : 'a,y : 'b) = (f x :?> 'a ,f y :?> 'b)
g id (1,"1") |> printfn "%A"
函数f需要确保它返回与obj类型
相同的主类型