【问题标题】:FSharp: Type inference with genericFSharp:使用泛型进行类型推断
【发布时间】:2015-05-22 16:15:57
【问题描述】:

我不明白为什么下面的代码不能编译

module GenericsTest = 
    open System

    type Dog = {
        name:string
    }

    type Apple = {
        size:int
    }

    let get<'a> (id:string) =
        Activator.CreateInstance<'a>()

    let creatorInferred getAsParam = 
        let apple = {
            name = getAsParam "some-apple"
        }

        let dog = {
            size = getAsParam "some-dog"
        }
        (apple, dog)

    let creatorWithTypeAnnotation (getAsParam:string->'a) = 
        let apple = {
            name = getAsParam "some-apple"
        }

        let dog = {
            size = getAsParam "some-dog"
        }
        (apple, dog)

如果您查看 2“创建者...”函数 - 它们都给出编译错误..

这个表达式应该有 int 类型...但是这里有 输入字符串

我可以看到 F# 将 getAsParam 方法的返回类型推断为 int,因为它是它遇到的第一个。但是,为什么它不决定使用泛型返回类型呢?

如您所见,我已尝试在 creatorWithTypeAnnotation 方法中进行函数签名 - 但这没有任何影响。

我被难住了!我如何强制它识别 getAsParam 函数应该返回一个泛型?

【问题讨论】:

  • 基本上当它看到第一个(Apple)时,类型推断会将getAsParam 专门化为string -&gt; Apple,所以当然你的第二次使用它(对于Dog)必须失败 - 如果你删除有错误的部分,你甚至可以看到它 - 编译器应该警告你它将 'a 限制为 Apple - 试着想想你将如何编写一个返回真正 generic 'a 没有作弊(反射,默认值,...)
  • 但是.. 我如何强制 getAsParam 具有通用返回类型?注意 - 如果我对 getAsParams 的论点之一是 'a ...,我可以做到这一点,但我不想这样做
  • 如果你使用接口而不是getAsParam的函数,你可以欺骗进入这个过程@
  • 只需将getAsParam 中的'a 视为createorWithAnnotation 的通用参数 - 看看为什么这里不能是两个不同的东西? - 所以如果你需要两个不同的结果,你也必须使用两个不同的 getAsParam1getAsParam2 参数

标签: generics f# type-inference


【解决方案1】:

这里是一个快速的 F# 交互式会话,我的意思是 使用接口作弊(它必须是某种 member - 所以它可以是类上的一个方法当然也是):

> type IParam = abstract getParam : string -> 'a;;                              

type IParam =
  interface
    abstract member getParam : string -> 'a
  end

> let createWith (p : IParam) : int*bool = (p.getParam "a", p.getParam "b");;            

val createWith : p:IParam -> int * bool

> let test = { new IParam with member __.getParam s = Unchecked.defaultof<_> };;

val test : IParam

> createWith test;;                                                             
val it : int * bool = (0, false)

您可能会发现实现IParam 的一些更明智 实例并不容易;)

【讨论】:

  • 谢谢...我了解解决方案。但对我来说,为什么它应该作为成员而不是作为模块根目录中的 func 声明工作没有意义......我意识到我的例子没有这样做。为什么会员功能不同?
  • @MikeS :这与您拥有的实际的类型有关,即getParam - 它需要是一个单个的具体类型每次调用给定的creator 函数。在您的代码中,它必须是 either string-&gt;int or string-&gt;string 在运行时,它不能在函数中途更改类型。对于一个接口,getParam总是类型为IParam,并且它的getParam 方法可以在creator 的单个实例化期间以任意数量的不同类型调用。
【解决方案2】:

正如 Carsten 所暗示的,类型变量都是在编译时解析的。如果将getAsParam 定义为string -&gt; 'a,则不会阻止'a 在编译时被解析。因为它不能在编译时解析为两种不同的类型,所以编译失败。

考虑你的例子:

let creatorWithTypeAnnotation (getAsParam:string->'a) = 
    let apple = {
        name = getAsParam "some-apple"
    }

    let dog = {
        size = getAsParam "some-dog"
    }

这也可以这样声明(我将使用'A 而不是'a,因为预期的约定是小写类型变量用于编译器推断的类型):

let creatorWithTypeAnnotation<'A> (getAsParam:string->'A) = 
    let apple = {
        name = getAsParam "some-apple"
    }

    let dog = {
        size = getAsParam "some-dog"
    }

现在应该清楚为什么代码无法编译了。

【讨论】:

  • 所以,我现在明白为什么它不能编译了。但是我不能只做 getAsParam "some-apple" ... 之类的事情来强制该方法是通用的吗?
  • @MikeS 不,因为getAsParam 不是一种方法。它是一个参数——一个变量——恰好有一个函数类型。参数本身不能有类型参数;也就是说,它不能是独立通用的。相反,类型参数与正在定义的函数有关,即creatorWithTypeAnnotation。这很像在 C# 中,不能将泛型委托作为参数传递给方法。您必须在编译时提供类型参数。该类型可以是被调用方法的泛型参数,类似于getAsParam 是泛型的要求。
猜你喜欢
  • 2022-11-07
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多