scala - 建模 optional 参数的最佳方法

标签 scala design-patterns optional

正如标题所说,在 中建模 optional 参数的最佳方法是什么?斯卡拉 ?
对于 optional 参数,我的意思是执行函数体不需要的值。
要么因为该参数存在默认值,要么根本不需要该参数本身(例如配置或调试标志);请注意,在 Java 我可能会通过 null对这些论点。

这是 上的常见问题解答斯卡拉 社区,尤其是新人制作的。
例如:

  • 在这里 SO :Implicit conversion from A to Some(a)
  • 关于用户的话语 :https://users.scala-lang.org/t/passing-true-optional-arguments-to-functions/6087
  • git Scala channel :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 参数(当然,如果它的名称不同)并且这种转换可以应用于代码的许多其他(非预期​​)部分;这就是为什么隐式转换 ,一般来说,是不鼓励的。
    一个次要的选择是使用 扩展方法 而不是 隐式转换 ,类似 optional = "foo".opt .然而,事实是扩展方法要添加更多代码,并且该站点调用仍然有一些样板文件,这使得它像一个平庸的中间点。
    (免责声明,如果您正在使用 ,您已经拥有这样一个 扩展方法 在范围 .some 中,因此您可能想要使用它)。
    默认参数
    该语言支持为函数的参数提供默认值,这样如果未通过,编译器将插入默认值。
    有人可能认为这应该是建模 optional 参数的最佳方式;然而,他们遇到了三个问题。
  • 您并不总是有默认值,有时您只想知道该值是否被传递。例如,一面旗帜。
  • 如果它在自己的参数组上,您仍然需要添加可能看起来很难看的空括号(这当然是主观意见)。

  • 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 = "")
    
    这个的优点是它封装了样板定义站点而不是调用站点;还使代码更易于阅读,并且得到了工具的良好支持。
    然而,最大的缺点是,如果您有多个 optional 参数,这将无法很好地扩展;例如,对于三个参数,您需要七个 ( 7 ) 重载。
    但是,如果您有许多 optional 参数,也许最好只要求一个 Config/Context参数并使用 build 师 .
    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
    也许是的介绍联合类型 可以打开一种更简单的方法来编码 optional 参数的可能性吗?

    关于scala - 建模 optional 参数的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65256690/

    相关文章:

    Scala 编译器抛出注释参数需要是常量参数上的常量错误

    java - ListBuffer[BaseType] 中的协方差/子类型

    Java理解

    swift3 - Swift 委托(delegate) : unexpectedly found nil while unwrapping an Optional value

    java - 如何设计一个可能使用skip的返回流

    c++ - C++ 中的链 optional

    java - Jvm 需要很长时间才能解析 localhost 的 ip 地址

    scala - Scala文件

    c# - C# 是否包括有限状态机?

    design-patterns - 用 Dart 模式代替静态继承