如何在F#中编写一个能够返回真正不同类型的函数?

4
假设有一个用FSharp编写的第三方库,它包含几个通用类,例如以下内容:
- 类型 FirstType<'a> 有一个方法 DoWork,接受: - 第一个参数是类型为 FirstType<'a> 的对象, - 第二个参数是一个函数,其类型为 ('a -> 'b)。 - DoWork 方法的返回类型是 FirstType<'b>。
- 类型 SecondType<'a> 有一个方法 DoWork,接受: - 第一个参数是类型为 SecondType<'a> 的对象, - 第二个参数是一个函数,其类型为 ('a -> 'b)。 - DoWork 方法的返回类型是 SecondType<'b>。
- 类型 ThirdType<'a> 有一个方法 DoWork,接受: - 第一个参数是类型为 ThirdType<'a> 的对象, - 第二个参数是一个函数,其类型为 ('a -> 'b)。 - DoWork 方法的返回类型是 ThirdType<'b>。
这些类没有共同的接口或父类,Object 是它们唯一的共同父类。
下面的每种类型都有一个名为 DoWork 的方法。它接受两个参数:
- 第一个参数是包含 DoWork 方法的类的类型。 - 第二个参数是一个函数,它接受与类的泛型参数相等的参数,并返回任何类型的元素 'b。
然后,该 DoWork 函数应返回具有与其所在类相同的类型的对象,但泛型类型参数等于 'b。
目前,这些类的用法示例如下:
let first = new FirstType<int>()
...
// here result is of type FirstType<string>
let result = FirstType.DoWork first (fun x -> "hello" + x.toString())

let second = new SecondType<int>()
...
// here result is of type SecondType<bool>
let result = SecondType.DoWork second (fun x -> 2 = 4)

let third = new ThirdType<string>()
...
// here result is of type ThirdType<int>
let result = ThirdType.DoWork third (fun x -> x.Length())

问题:

需要实现一个名为Do的函数,它接受两个参数,第一个是一个对象,类型可以是FirstType<'a>、SecondType<'a>或ThirdType<'a>中的一种,第二个是一个类型为('a -> 'b)的函数,其中'b'可以是'a'之外的另一种类型。所以,这个函数应该确定它的第一个输入参数的类型,并基于此返回适当的类型。

Do函数的返回类型:

  • 如果Do函数的第一个参数的类型是FirstType<'a>,则返回类型应该是FirstType<'b>的对象
  • 如果Do函数的第一个参数的类型是SecondType<'a>,则返回类型应该是SecondType<'b>的对象
  • 如果Do函数的第一个参数的类型是ThirdType<'a>,则返回类型应该是ThirdType<'b>的对象

期望的,这些类的使用示例:

let first = new FirstType<int>()
let second = new SecondType<int>()
let third = new ThirdType<string>()
...
// here result1 should be of type FirstType<string>
let result1 = Do first (fun x -> "hello" + x.toString())

// here result2 should be of type SecondType<bool>
let result2 = Do second (fun x -> 2 = 4)

// here result3 should be of type ThirdType<int>
let result3 = Do third (fun x -> x.Length())

我曾考虑过函数重载,但在F#中不允许使用。现在我在思考如何使函数返回真正不同的类型,而不是辨别联合类型,因为在调用函数时它期望一个特定的类型。
更新:我已经看了John Palmer的评论并尝试了它。
type Ops =
    static member Do (f: 'a -> 'b) (x:FirstType<'a>) = ...
    static member Do (f: 'a -> 'b) (x:SecondType<'a>) = ...
    static member Do (f: 'a -> 'b) (x:ThirdType<'a>) = ...

尝试创建函数时:

let Do f x = Ops.Do f x

出现以下错误:

错误:此方法的一个或多个重载已使用柯里化参数。请考虑重新设计这些成员,以采用元组形式的参数。

尝试将此 Do 函数作为Ops类的成员使用时会出现相同的错误:

let result1 = first |> Ops.Do(fun x -> x + 2)
let result2 = second |> Ops.Do(fun x -> "hello" + x.ToString())
let result3 = third |> Ops.Do(fun x -> x = 1) sq

重新设计具有元组形式参数的Ops类型中的Do方法,并创建如下的Do函数时:
let Do f x = Ops.Do (f, x)

在错误列表中出现以下错误:

错误:在此程序点之前,无法根据类型信息确定方法'Do'的唯一重载。可能需要类型注释。候选项:静态成员Ops.Do:f:('a ->'b)* x:FirstType<'a> -> FirstType<'b>,静态成员Ops.Do:f:('a ->'b)* x:SecondType<'a> -> SecondType<'b>,静态成员Ops.Do:f:('a ->'b)* x:ThirdType<'a> -> ThirdType<'b>

因此,我只能使用指定类名(Ops)和元组形式来使用Do.. 我已经通过错误消息理解了这一点。

有没有办法使用重载的成员方法进行柯里化?

我想要的用法是这样的:

let result = input |> Do someFunction

如果有人有任何想法或建议,我会很高兴。也许我在某些方面是错误的。


3
F#支持成员函数的函数重载 - 我认为这就是答案。 - John Palmer
@JohnPalmer 谢谢您的评论,我已经尝试使用重载的成员函数并更新了我的问题。是否可以在这些方法中使用柯里化?或者有什么建议欢迎提出。 - experimenter
@JohnPalmer 我仔细查看了我的代码,并通过内联函数成功实现了我想要的功能。谢谢!!! - experimenter
1个回答

2
我认为这就是你想要做的事情:
type FirstType<'a>  = FirstType  of 'a
type SecondType<'a> = SecondType of 'a
type ThirdType<'a>  = ThirdType  of 'a   

type Ops = Ops with
    static member ($) (Ops, FirstType  a) = fun f -> FirstType  (f a)
    static member ($) (Ops, SecondType a) = fun f -> SecondType (f a)
    static member ($) (Ops, ThirdType  a) = fun f -> ThirdType  (f a)

let inline Do f x = (Ops $ f) x

let first  = FirstType  10
let second = SecondType 12
let third  = ThirdType  "Hello"

let result1 = Do first  (fun x -> "hello" + x.ToString())
let result2 = Do second (fun x -> 2 = 4)
let result3 = Do third  (fun x -> x.Length)

这些帖子中了解更多关于内联和重载的信息。看起来你正在尝试在包装类上实现一个通用的映射函数,这对应于Haskell中的Functor Typeclass,它有一个名为fmap的函数,与您的Do函数具有相同的签名,但参数顺序相反。请注意保留HTML标记。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接