【问题标题】:Method call with option value or default parameter in ScalaScala中带有选项值或默认参数的方法调用
【发布时间】:2016-06-10 15:12:22
【问题描述】:

我对 Scala 还很陌生,偶然发现了一个一直困扰我的小问题。假设有一些带有默认参数的方法

def foo(v: Any = "default"): String = s"called with parameter '$v'"

和一个选项val opt: Option[String]。 如何使用选项值(如果已定义)或默认参数调用此方法? 我的意思是尽管有明显的解决方案

val result = if (opt.isDefined)
               from.here.to.foo(opt.get)
             else
               from.here.to.foo()

并且必须用(可能很长的)对象链键入方法两次?更不用说拥有多个可选/默认参数...


我能想到的只是无用的帮手

def definedOrDefault[A, B](opt: Option[A], f0: => B, f1: A => B): B =
  if (opt.isDefined) f1(opt.get) else f0

但是当不能在高阶函数中提及默认参数时……就是这样。让我想起了 Java 的糟糕时光,其中方法重载会产生同样的问题。

【问题讨论】:

    标签: scala default higher-order-functions scala-option


    【解决方案1】:

    您似乎在一个地方使用了两个可选值的概念,即可选参数和Option。他们不喜欢一起玩,也许只用一个更好。

    如果您总是只将 Option 的值传递给方法或不传递任何内容以获取默认值,则可以考虑更改函数以接受 Option

    def foo(v: Option[String]) = {
      val param = v getOrElse "default"
      s"called with parameter '$param'"
    }
    

    如果你仍然想要默认参数,你可以将签名更改为

    def foo(v: Option[String] = None)
    

    但是,当您进行常规调用时,这种方法将要求您将参数包装到 Some 中,例如

    foo(Some("regular param"))
    

    但是当您使用Options 时效果很好。您也可以轻松添加更多可选参数。

    这是一个例子

    def foo(v1: Option[String] = None, v2: Option[Int] = None) = {
      val param1 = v1 getOrElse "default"
      val param2 = v2 getOrElse 42
      s"'$param1', '$param2'"
    }
    
    foo() // "default", 42
    
    foo(v2 = Some(12)) // "default", 12
    
    foo(Some("asd"), Some(11)) // "asd", 11
    
    val optionFromSomewhere = Some("a")
    val anotherOptionFromSomewhere = Option.empty[Int]
    foo(optionFromSomewhere, anotherOptionFromSomewhere) // "a", 42
    

    您还可以引入从AnyOption 的隐式转换,这样您就可以省略Some,但我认为这不是一个好主意

     implicit def any2option[A](e: A): Option[A] = Some(e)
    

    【讨论】:

    • 在这种情况下,您甚至可以将“默认”参数设置为None,以保留一些相同的语义。
    • 是的,我也这么认为。答案正在进行中:p
    • 我希望有一个解决方案,该方法可以保持不变,它可能是图书馆的一部分,我无法触及。并且检查参数首先打破了功能风格。不过还是不错的!
    【解决方案2】:

    对@Łukasz 的回答稍加扩展,太大而无法放入评论:

    通过创建一个特殊用途的类型,您可以避免将当前参数包装在 Some 中而没有危险的 any2option

    sealed trait OptParam[+A] { def toOption: Option[A] }
    case class Param[+A](value: A) extends OptParam[A] { def toOption = Some(value) }
    case object NoParam extends OptParam[Nothing] { def toOption = None }
    
    object OptParam {
      implicit def any2optParam[A](x: A): OptParam[A] = Param(x)
    }
    
    def foo(v1: OptParam[String] = NoParam, v2: OptString[Int] = NoParam) = {
      val param1 = v1.toOption.getOrElse("default")
      val param2 = v2.toOption.getOrElse(42)
      s"'$param1', '$param2'"
    }
    
    foo("a") // "'a', '42'"
    

    使这更安全的是OptParam 应该只作为方法参数类型出现,因此转换不会在您不期望的地方触发。

    【讨论】:

      【解决方案3】:

      您可以使用Option 上定义的map 函数来转换包含在其中的值(如果已定义)。这看起来像这样:

      opt.map(foo)
      // same as
      opt.map(x => foo(x))
      

      这将返回一个Option[String](如果其中有某个值)或None(如果之前是None)。

      这是来自 REPL 的完整示例:

      scala> def foo(v: Any = "default"): String = s"called with parameter '$v'"
      foo: (v: Any)String
      
      scala> Some("Hello")
      res0: Some[String] = Some(Hello)
      
      scala> res0.map(foo)
      res1: Option[String] = Some(called with parameter 'Hello')
      
      scala> val x: Option[String] = None
      x: Option[String] = None
      
      scala> x.map(foo)
      res2: Option[String] = None
      

      编辑:

      要使用默认值调用它,您应该在调用之前匹配您的 Option,因为该方法需要一个非 Option 参数。

      val optOrDefault = opt getOrElse "default"
      foo(optOrDefault)
      

      【讨论】:

      • 我认为当Option 为空时,OP 希望看到"called with parameter default"。这将需要opt map foo getOrElse foo(),这稍微好一些,但仍然需要输入两次foo,并且不能扩展到更多可选参数。
      • 嗯,你可能是对的。我会扩展我的答案,谢谢。
      猜你喜欢
      • 2014-12-04
      • 2020-04-14
      • 2016-06-15
      • 1970-01-01
      • 2012-05-26
      • 2011-08-04
      • 1970-01-01
      • 2014-02-27
      • 1970-01-01
      相关资源
      最近更新 更多