F# 签名文件和对应的实现文件中的通用函数定义

Posted

技术标签:

【中文标题】F# 签名文件和对应的实现文件中的通用函数定义【英文标题】:Generic function definitions in F# signature files and corresponding implementation file 【发布时间】:2013-04-18 05:22:25 【问题描述】:

我正在尝试使用 F# 签名文件为轻量级数据存储模块创建抽象。这是我的签名文件代码,假设它被称为repository.fsi

namespace DataStorage

/// <summary>Lightweight Repository Abstraction</summary>
module Repository = 
   /// <summary> Insert data into the repository </summary>
   val put: 'a -> unit
   /// <summary> Fetch data from the repository </summary>
   val fetch: 'a -> 'b
   /// <summary> Remove data from the repository </summary>
   val remove: 'a -> unit

这里是对应的实现,我们称之为repository.fs

namespace DataStorage

module Repository = 

    (* Put a document into a database collection *)
    let put entity = ()

    (* Select a document from a database collection *)
    let fetch key = ("key",5)

    (* Remove a document from a database collection *)
    let remove entity = ()

在我的 Visual Studio 项目文件中,我有上面的签名文件 (repository.fsi) 我的实现文件(repository.fs)。 putremove 函数正在被正确解析和验证,没有错误(在实现文件中),但 fetch 函数一直给我红色在 Visual Studio 中波浪形地显示以下错误消息:

模块“DataStorage.Repository”包含

val fetch: s:string -> string * int

但它的签名指定

val fetch<'a,'b> : 'a -> 'b

各自的类型参数计数不同

谁能告诉我我做错了什么?我的 fetch 函数值是否定义错误 我的签名文件?我只是想在我的签名文件中创建一个通用函数('a -> 'b),并让实现将一种类型作为输入并返回另一种类型作为输出。

【问题讨论】:

fetch 的签名是通用的,但实现不是。如果实现只是一个存根,请尝试将其替换为 let fetch key = Unchecked.defaultof&lt;_&gt; 以使其能够编译。 感谢@Daniel 的回答,我试试看。 【参考方案1】:

我在 F# 方面还不是很强,但我认为您在这里以错误的方式使用签名文件。

首先。以下是修复编译错误的方法:

替换:

let fetch key = ("key",5)

与:

let fetch key = ("key",5) :> obj :?> 'b

而且您不会收到编译错误。 但这个修复在很多情况下实际上没有意义。例如,如果下一个工作:

let a = Repository.fetch(56)

如果您明确指定类型,它将崩溃(在大多数情况下):

let b = Repository.fetch<int, string>(56)

情况是泛型实现应该使用泛型类型。如果我正确理解了您要做什么,那么当使用签名文件隐藏实现方面时,您应该使用 OOP 和多态性。例如:

namespace DataStorage

[<AbstractClass>]
type Repository<'TKey,'T>() =     
    abstract member put: 'T -> unit
    abstract member fetch: 'TKey -> 'T  
    abstract member remove: 'T -> unit

type IntRepository() =
    inherit Repository<int, int>()
    override self.put item = ()
    override self.fetch index = 5
    override self.remove item = ()

type StringRepository() =
    inherit Repository<int, string>()
    override self.put item = ()
    override self.fetch index = "Hello"
    override self.remove item = ()

【讨论】:

这是有道理的。我正在尝试以与在 OCaml 中相同的方式使用签名文件。我想这是在 .NET 世界中做事的标准方式。感谢@petro.silovskyy 的时间、想法和解释。【参考方案2】:

我最近尝试过的一个(一些限制性的)替代方案与泛型相差无几,但似乎适用于我的场景。基本上 fetch 函数的签名文件现在看起来像这样。

'a -> RepositoryRecord

RepositoryRecord 的实现是代数数据类型。

type RepositoryRecord = | SomeVal1 of int * string | SomeVal2 of string

【讨论】:

我认为这根本没有限制。我一直使用这种方法。事实上,我什至会说这是惯用的。 @Shredderroy 感谢您的评论。很高兴知道我的想法并非完全错误。 ;) 确定。也许更有知识的人会权衡,但关于你的评论...one step away from generics,我只想说我不相信你最初的方法涉及generics。毕竟,genericscasting 之间是有区别的,我相信这是你最终不得不做的事情。

以上是关于F# 签名文件和对应的实现文件中的通用函数定义的主要内容,如果未能解决你的问题,请参考以下文章

保留装饰函数的签名

无法在 F# 签名文件 (.fsi) 中使用文字属性

Gradle实现自动打包,签名,自定义apk文件名

Typescript - 具有通用类型函数的索引签名

在 F# 中输入通用鸭子?

使用不同签名的通用回调函数