在 F# 中命名本身就是参数的函数的参数
Naming the arguments of functions that are themselves arguments in F#
给函数参数命名是记录我们的代码的一种方式;如果没有这样的需要,那么我们可以给它们起诸如“arg1”和“arg2”之类的名字,而且不会更糟——但实际上这当然会使我们的代码更难理解。在 F# 中,我们经常将函数作为参数传递给其他函数,在这种情况下,它们的参数不再命名:
let foo<'T> (bar : 'T -> 'T -> float -> 'T) =
// do something
这个函数 foo 以一个“bar”作为参数,它本身有两个 'T 和一个浮点数以及 returns 一个 'T,但很难说出它应该做什么。如果我们可以命名“bar”的参数,那就更清楚了:
let foo<'T> (bar : (startVal : 'T) -> (endVal : 'T) -> (interpolationAmt : float) -> 'T) =
// do something
现在很明显“bar”的用途是什么:它在两个类型为“T”的值之间进行插值。此外,'T 参数的顺序以及应用它们的顺序也很重要。但不幸的是,这段代码无法编译。
所以我的问题是是否有一种我错过的方法可以做到这一点,如果没有,人们通常如何处理这个问题?
如果bar
插值,你可以称它为interpolate
。
您也可以使用简单的类型别名作为标记来帮助交流。但是,从 foo
中看不出 StartValue<'T>
只是一个 'T
:
type StartValue<'T> = 'T
type EndValue<'T> = 'T
type InterpolationAmount<'T> = 'T
let foo<'T> (interpolate : StartValue<'T> -> EndValue<'T> -> InterpolationAmount<float> -> 'T) =
()
另一种使用更面向对象风格的方法是使用接口。
type IInterpolate<'T> =
abstract member Interpolate : startVal:'T -> endVal:'T -> interpolationAmount:float -> 'T
let foo<'T> (interpolate : IInterpolate<'T>) =
()
编辑:另一种选择是使用匿名记录类型:
let foo<'T> (interpolate : {| StartVal:'T; EndVal:'T; InterpolationAmount:float |} -> 'T) =
()
遗憾的是,无法将参数名称添加到函数签名中。使函数签名易于理解的最佳方法是自定义类型。但我建议只为真正的域类型创建自定义类型,它们本身具有一定的意义。通常,此类类型有一些限制 - 非空列表、最大长度为 42 的字符串、负整数等。因此,对于你的函数,我会为插值量创建一个类型:
type InterpolationAmount = private InterpolationAmount of float
module InterpolationAmount =
let create (amount: float) : InterpolationAmount =
if amount < 0. then failwith "blah-blah"
else InterpolationAmount amount
至少现在清楚了float
参数是什么,可以控制插值量值了。
现在关于插值开始和结束。为确保用户将正确的参数传递给此函数,您可以将其设为具有单个参数的函数。你当然会失去部分应用程序选项,但所有参数都会有名称并且很难在这里搞砸:
type InterpolationArgs<'T> = {
startValue: 'T
endValue: 'T
amount: InterpolationAmount
}
当然,使用bar
以外的名称也会给用户提示
let foo<'T> (interpolate : InterpolationArgs<'T> -> 'T) =
interpolate {
startValue = a
endValue = b
amount = InterpolationAmount.create 1.2
}
给函数参数命名是记录我们的代码的一种方式;如果没有这样的需要,那么我们可以给它们起诸如“arg1”和“arg2”之类的名字,而且不会更糟——但实际上这当然会使我们的代码更难理解。在 F# 中,我们经常将函数作为参数传递给其他函数,在这种情况下,它们的参数不再命名:
let foo<'T> (bar : 'T -> 'T -> float -> 'T) =
// do something
这个函数 foo 以一个“bar”作为参数,它本身有两个 'T 和一个浮点数以及 returns 一个 'T,但很难说出它应该做什么。如果我们可以命名“bar”的参数,那就更清楚了:
let foo<'T> (bar : (startVal : 'T) -> (endVal : 'T) -> (interpolationAmt : float) -> 'T) =
// do something
现在很明显“bar”的用途是什么:它在两个类型为“T”的值之间进行插值。此外,'T 参数的顺序以及应用它们的顺序也很重要。但不幸的是,这段代码无法编译。
所以我的问题是是否有一种我错过的方法可以做到这一点,如果没有,人们通常如何处理这个问题?
如果bar
插值,你可以称它为interpolate
。
您也可以使用简单的类型别名作为标记来帮助交流。但是,从 foo
中看不出 StartValue<'T>
只是一个 'T
:
type StartValue<'T> = 'T
type EndValue<'T> = 'T
type InterpolationAmount<'T> = 'T
let foo<'T> (interpolate : StartValue<'T> -> EndValue<'T> -> InterpolationAmount<float> -> 'T) =
()
另一种使用更面向对象风格的方法是使用接口。
type IInterpolate<'T> =
abstract member Interpolate : startVal:'T -> endVal:'T -> interpolationAmount:float -> 'T
let foo<'T> (interpolate : IInterpolate<'T>) =
()
编辑:另一种选择是使用匿名记录类型:
let foo<'T> (interpolate : {| StartVal:'T; EndVal:'T; InterpolationAmount:float |} -> 'T) =
()
遗憾的是,无法将参数名称添加到函数签名中。使函数签名易于理解的最佳方法是自定义类型。但我建议只为真正的域类型创建自定义类型,它们本身具有一定的意义。通常,此类类型有一些限制 - 非空列表、最大长度为 42 的字符串、负整数等。因此,对于你的函数,我会为插值量创建一个类型:
type InterpolationAmount = private InterpolationAmount of float
module InterpolationAmount =
let create (amount: float) : InterpolationAmount =
if amount < 0. then failwith "blah-blah"
else InterpolationAmount amount
至少现在清楚了float
参数是什么,可以控制插值量值了。
现在关于插值开始和结束。为确保用户将正确的参数传递给此函数,您可以将其设为具有单个参数的函数。你当然会失去部分应用程序选项,但所有参数都会有名称并且很难在这里搞砸:
type InterpolationArgs<'T> = {
startValue: 'T
endValue: 'T
amount: InterpolationAmount
}
当然,使用bar
以外的名称也会给用户提示
let foo<'T> (interpolate : InterpolationArgs<'T> -> 'T) =
interpolate {
startValue = a
endValue = b
amount = InterpolationAmount.create 1.2
}