通用高阶函数

时间:2011-08-27 09:16:25

标签: generics f# inline

当我将其作为本地值传递但不作为参数传递时,我是否可以使用具有不同类型参数的泛型函数? 例如:

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')

这是我第二次遇到这个问题。

为什么它以这种方式运行,如果函数是本地值或者它作为参数传递,它不应该是相同的?

有没有办法在不重复功能的情况下执行此操作?

黑客,可能使用显式类型约束,内联魔法,引用?

5 个答案:

答案 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语法编写):

  1. forall'b,'c,'d。 ((forall'a。'a - &gt;'b) - &gt;'c *'d - &gt;'b *'b)
  2. forall'c,'d。 (forall'a。'a - &gt;'a) - &gt; 'c *'d - &gt; 'c *'d)
  3. 鉴于第一种类型,我们可以致电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类型

相同的主类型