建模可选参数的最佳方法
Best way to model optional arguments
如标题所说,在 Scala 中建模可选参数的最佳方法是什么?
对于可选参数,我指的是执行函数 body 不需要的值。
因为该参数存在默认值,或者根本不需要参数本身(例如配置或调试标志);请注意,在 Java 上,我可能会将 null
传递给这些参数。
这是 Scala 社区的常见问题解答,尤其是新手制作的。
例如:
- 在 SO 中:
- 关于用户的讨论:https://users.scala-lang.org/t/passing-true-optional-arguments-to-functions/6087
- 在 Gitter Scala 频道中:https://gitter.im/scala/scala?at=5d28f90d01621760bca2eae3
简单且社区接受的答案
一般来说,社区一致认为下面列出的所有建议或备选方案都不值得权衡取舍。
因此,推荐的解决方案是仅使用 Option 数据类型并手动/显式地将值包装在 Some
中
def test(required: Int, optional: Option[String] = None): String =
optional.map(_ * required).getOrElse("")
test(required = 100) // ""
test(required = 3, optional = Some("Foo")) // "FooFooFoo"
但是,这种方法的明显缺点是需要样板待命站点。
但是,可以说它使代码更易于阅读和理解,从而更易于维护。
尽管如此,有时您可以使用其他技术(例如默认参数)或重载 (在下面讨论).
来提供更好的 API
备选方案和建议
隐式转换
由于之前解决方案的样板,使用隐式转换的常见替代方案被反复提及;例如:
implicit def a2opt[A](a: A): Option[A] = Some(a)
这样前面的函数就可以这样调用了:
test(required = 3, optional = "Foo")
这样做的缺点是 隐式转换 隐藏了 optional
是可选参数 的事实(嗯,当然,如果它将被命名为不同的),并且这种转换可以应用于代码的许多其他 (意外) 部分;这就是为什么通常不鼓励隐式转换的原因。
一个子替代方案是使用 扩展方法 而不是 隐式转换 ,例如 optional = "foo".opt
。然而,事实上 扩展方法 需要添加更多的代码,而且站点调用仍然有一些样板文件,这使得它成为一个平庸的中间点。
(免责声明,如果您正在使用 cats,您已经在范围 .some
中拥有这样的 扩展方法 ,因此您可以想用那个).
默认参数
该语言支持为函数的参数提供默认值,这样如果未通过,编译器将插入默认值。
有人可能认为这应该是对可选参数建模的最佳方式;然而,他们遇到了三个问题。
您并不总是有默认值,有时您只想知道该值是否已通过。例如,一面旗帜。
如果是自己的参数组,还是要加上空括号,这样会比较难看(当然这是个人主观看法).
def transact[A](config: Config = Config.default)(f: Transaction => A): A
transact()(tx => ???)
- 您只能有一个带默认参数的重载。
object Functions {
def run[A](query: Query[A], config: Config = Config.default): A = ???
def run[A](query: String, config: Config = Config.default): A = ???
}
error: in object Functions, multiple overloaded alternatives of method run define default arguments.
超载
另一种常见的解决方法是提供该方法的重载版本;例如:
def test(required: Int, optional: String): String =
optional * required
def test(required: Int): String =
test(required, optional = "")
这个的优点是它封装了样板定义站点而不是呼叫站点;还使代码更易于阅读,并得到工具的良好支持。
然而,最大的缺点是,如果您有多个可选参数,这将无法很好地扩展;例如,对于三个参数,您需要七个 (7
) 重载。
但是,如果你有很多可选参数,也许最好只要求一个 Config
/ Context
参数并使用 Builder.
建造者模式。
def foo(data: Dar, config: Config = Config.default)
// It probably would be better not to use a case class for binary compatibility.
// And rather define your own private copy method or something.
// But that is outside of the scope of this question / answer.
final case class Config(
flag1: Option[Int] = None,
flag2: Option[Int] = None,
flag3: Option[Int] = None
) {
def withFlag1(flag: Int): Config =
this.copy(flag1 = Some(flag))
def withFlag2(flag: Int): Config =
this.copy(flag2 = Some(flag))
def withFlag3(flag: Int): Config =
this.copy(flag3 = Some(flag))
}
object Config {
def default: Config = new Config()
}
请求本机支持
在贡献者的话语中,已经提议为该用例添加语言级别或标准库级别的支持。但是,由于上述相同的原因,所有这些都被丢弃了。
此类建议的示例:
- https://contributors.scala-lang.org/t/sip-suggestion-add-and-syntactic-sugar-for-more-convenient-option-t-usage/2413
- https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388/57
结论
一如既往,根据您的具体情况和您要提供的 API 选择要使用的技术。
斯卡拉 3
也许引入 联合类型 可以开启一种更简单的可选参数编码方式的可能性?
在我看来,只有一条经验法则:避免明确传递 None
。
任务:
- 默认参数值
Option
与默认值 None
- 过载
不该做的事:
Option
没有默认值 None
Option
默认值为 Some
如标题所说,在 Scala 中建模可选参数的最佳方法是什么?
对于可选参数,我指的是执行函数 body 不需要的值。
因为该参数存在默认值,或者根本不需要参数本身(例如配置或调试标志);请注意,在 Java 上,我可能会将 null
传递给这些参数。
这是 Scala 社区的常见问题解答,尤其是新手制作的。
例如:
- 在 SO 中:
- 关于用户的讨论:https://users.scala-lang.org/t/passing-true-optional-arguments-to-functions/6087
- 在 Gitter Scala 频道中:https://gitter.im/scala/scala?at=5d28f90d01621760bca2eae3
简单且社区接受的答案
一般来说,社区一致认为下面列出的所有建议或备选方案都不值得权衡取舍。
因此,推荐的解决方案是仅使用 Option 数据类型并手动/显式地将值包装在 Some
def test(required: Int, optional: Option[String] = None): String =
optional.map(_ * required).getOrElse("")
test(required = 100) // ""
test(required = 3, optional = Some("Foo")) // "FooFooFoo"
但是,这种方法的明显缺点是需要样板待命站点。 但是,可以说它使代码更易于阅读和理解,从而更易于维护。
尽管如此,有时您可以使用其他技术(例如默认参数)或重载 (在下面讨论).
来提供更好的 API备选方案和建议
隐式转换
由于之前解决方案的样板,使用隐式转换的常见替代方案被反复提及;例如:
implicit def a2opt[A](a: A): Option[A] = Some(a)
这样前面的函数就可以这样调用了:
test(required = 3, optional = "Foo")
这样做的缺点是 隐式转换 隐藏了 optional
是可选参数 的事实(嗯,当然,如果它将被命名为不同的),并且这种转换可以应用于代码的许多其他 (意外) 部分;这就是为什么通常不鼓励隐式转换的原因。
一个子替代方案是使用 扩展方法 而不是 隐式转换 ,例如 optional = "foo".opt
。然而,事实上 扩展方法 需要添加更多的代码,而且站点调用仍然有一些样板文件,这使得它成为一个平庸的中间点。
(免责声明,如果您正在使用 cats,您已经在范围 .some
中拥有这样的 扩展方法 ,因此您可以想用那个).
默认参数
该语言支持为函数的参数提供默认值,这样如果未通过,编译器将插入默认值。
有人可能认为这应该是对可选参数建模的最佳方式;然而,他们遇到了三个问题。
您并不总是有默认值,有时您只想知道该值是否已通过。例如,一面旗帜。
如果是自己的参数组,还是要加上空括号,这样会比较难看(当然这是个人主观看法).
def transact[A](config: Config = Config.default)(f: Transaction => A): A
transact()(tx => ???)
- 您只能有一个带默认参数的重载。
object Functions {
def run[A](query: Query[A], config: Config = Config.default): A = ???
def run[A](query: String, config: Config = Config.default): A = ???
}
error: in object Functions, multiple overloaded alternatives of method run define default arguments.
超载
另一种常见的解决方法是提供该方法的重载版本;例如:
def test(required: Int, optional: String): String =
optional * required
def test(required: Int): String =
test(required, optional = "")
这个的优点是它封装了样板定义站点而不是呼叫站点;还使代码更易于阅读,并得到工具的良好支持。
然而,最大的缺点是,如果您有多个可选参数,这将无法很好地扩展;例如,对于三个参数,您需要七个 (7
) 重载。
但是,如果你有很多可选参数,也许最好只要求一个 Config
/ Context
参数并使用 Builder.
建造者模式。
def foo(data: Dar, config: Config = Config.default)
// It probably would be better not to use a case class for binary compatibility.
// And rather define your own private copy method or something.
// But that is outside of the scope of this question / answer.
final case class Config(
flag1: Option[Int] = None,
flag2: Option[Int] = None,
flag3: Option[Int] = None
) {
def withFlag1(flag: Int): Config =
this.copy(flag1 = Some(flag))
def withFlag2(flag: Int): Config =
this.copy(flag2 = Some(flag))
def withFlag3(flag: Int): Config =
this.copy(flag3 = Some(flag))
}
object Config {
def default: Config = new Config()
}
请求本机支持
在贡献者的话语中,已经提议为该用例添加语言级别或标准库级别的支持。但是,由于上述相同的原因,所有这些都被丢弃了。
此类建议的示例:
- https://contributors.scala-lang.org/t/sip-suggestion-add-and-syntactic-sugar-for-more-convenient-option-t-usage/2413
- https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388/57
结论
一如既往,根据您的具体情况和您要提供的 API 选择要使用的技术。
斯卡拉 3
也许引入 联合类型 可以开启一种更简单的可选参数编码方式的可能性?
在我看来,只有一条经验法则:避免明确传递 None
。
任务:
- 默认参数值
Option
与默认值None
- 过载
不该做的事:
Option
没有默认值None
Option
默认值为Some