【发布时间】:2019-01-22 22:42:06
【问题描述】:
对于上下文,我正在使用https://fsharpforfunandprofit.com/posts/dependency-injection-1/ 中概述的部分依赖注入模式。
如果我想将一个函数传递给另一个函数(比如在 DI 的组合根中),能够为 FSharp 类型引擎提供一些关于我正在尝试做什么的提示会很有用,否则它有点爆炸成无用的混乱,直到一切正常。 为此,我想参考“现有函数的部分应用版本的类型”
例如,假设我有设置
let _getUser logger db userId =
logger.log("getting user")
User.fromDB (db.get userId)
let _emailUserId sendEmail getUser emailContents userId =
let user = getUser userId
do sendEmail emailContents user.email
// composition root
let emailUserId =
let db = Db()
let logger = Logger()
let sendEmail = EmailService.sendEmail
let getUser = _getUser logger db
_emailUserId sendEmail getUser
我想为 _emailUserId 提供类型提示
let _emailUserId
// fake syntax. is this possible?
(sendEmail: typeof<EmailService.sendEmail>)
// obviously fake syntax, but do I have any way of defining
// `type partially_applied = ???` ?
(getUser: partially_applied<2, typeof<_getUser>>)
emailContents userId
=
...
因为否则在编写 _emailUserId 时我的 IDE 提供的帮助很少。
问题
(显式添加是因为将其隐藏在代码块中有点误导)。
F# 的类型系统是否允许以任何方式引用或构建现有的推断类型?
如type t = typeof<some inferred function without a manually written type signature>
F# 的类型系统可以让我表达一个部分应用的函数,而无需手动编写它的参数类型吗?
例如type partial1<'a when 'a is 'x -> 'y -> 'z> = 'y -> 'z>,也许像partial1<typeof<some ineferred function without a manually written signature>一样使用?
虽然我仍然很感谢对我使用的模式的反馈,但这不是核心问题,只是上下文。在我看来,这个问题非常普遍地适用于 F# 开发。
我发现得到我想要的东西的唯一方法是硬编码完整的函数类型:
let _emailUserId
(sendEmail: string -> string -> Result)
(getUser: string -> User)
emailContents userId
=
...
这会导致重复签名,很大程度上抵消了 F# 强大推理系统的优势,并且在将此模式扩展到玩具 StackOverflow 示例之外时很难维护。
令我惊讶的是,这是不明显或不受支持的——我使用丰富类型系统的另一个经验是 Typescript,在许多情况下,使用内置语法很容易做到这一点。
【问题讨论】:
-
您不需要对所有类型进行硬编码。您也可以将单个类型“打开”,例如
(sendEmail : string -> 'TContent -> Result)。TContent现在是泛型类型参数,您可以再次将其用于函数内部的类型注释以指定该类型。您可以根据需要将所涉及的类型设为泛型;如果你违反了任何类型系统规则,编译器会告诉你。但是,为了能够做到这一点,您必须至少写出相关函数签名的结构,以便您可以放置和命名通用参数。 -
如果你想保留类型通用/推断但不想命名它们,你也可以使用通配符
_:(sendEmail: _ -> 'TContent -> _)
标签: types f# type-inference