是Scala API中的选项和命名的默认参数,如石油和水吗?

DaG*_*RRz 41 scala optional-parameters named-parameters

我正在研究Scala API(顺便说一下,对于Twilio),其中操作具有相当多的参数,其中许多具有合理的默认值.为了减少输入并增加可用性,我决定使用带有命名和默认参数的case类.例如对于TwiML Gather动词:

case class Gather(finishOnKey: Char = '#', 
                  numDigits: Int = Integer.MAX_VALUE, // Infinite
                  callbackUrl: Option[String] = None, 
                  timeout: Int = 5
                  ) extends Verb
Run Code Online (Sandbox Code Playgroud)

这里感兴趣的参数是callbackUrl.它是唯一真正可选的参数,如果没有提供任何值,则不会应用任何值(这是完全合法的).

我已经宣布它是一个选项,以便在API的实现方面使用它来执行monadic map例程,但这给API用户带来了额外的负担:

Gather(numDigits = 4, callbackUrl = Some("http://xxx"))
// Should have been
Gather(numDigits = 4, callbackUrl = "http://xxx")

// Without the optional url, both cases are similar
Gather(numDigits = 4)
Run Code Online (Sandbox Code Playgroud)

据我所知,有两种选择(没有双关语)来解决这个问题.要么让API客户端导入隐式转换到范围:

implicit def string2Option(s: String) : Option[String] = Some(s)
Run Code Online (Sandbox Code Playgroud)

或者我可以使用null默认值重新声明case类,并将其转换为实现端的选项:

case class Gather(finishOnKey: Char = '#', 
                  numDigits: Int = Integer.MAX_VALUE, 
                  callbackUrl: String = null, 
                  timeout: Int = 5
                  ) extends Verb
Run Code Online (Sandbox Code Playgroud)

我的问题如下:

  1. 有没有更优雅的方法来解决我的特定情况?
  2. 更一般地说:命名参数是一种新的语言特性(2.8).难道结果是选项和命名的默认参数就像石油和水一样?:)
  3. 在这种情况下,使用null默认值可能是最佳选择吗?

Aar*_*rup 24

这是另一个解决方案,部分灵感来自Chris的回答.它还涉及一个包装器,但包装器是透明的,您只需要定义一次,并且API的用户不需要导入任何转换:

class Opt[T] private (val option: Option[T])
object Opt {
   implicit def any2opt[T](t: T): Opt[T] = new Opt(Option(t)) // NOT Some(t)
   implicit def option2opt[T](o: Option[T]): Opt[T] = new Opt(o)
   implicit def opt2option[T](o: Opt[T]): Option[T] = o.option
}

case class Gather(finishOnKey: Char = '#', 
                  numDigits: Opt[Int] = None, // Infinite
                  callbackUrl: Opt[String] = None, 
                  timeout: Int = 5
                 ) extends Verb

// this works with no import
Gather(numDigits = 4, callbackUrl = "http://xxx")
// this works too
Gather(numDigits = 4, callbackUrl = Some("http://xxx"))
// you can even safely pass the return value of an unsafe Java method
Gather(callbackUrl = maybeNullString())
Run Code Online (Sandbox Code Playgroud)

为了解决更大的设计问题,我认为选项和命名默认参数之间的相互作用与初看起来的油和水一样多.可选字段与具有默认值的字段之间存在明确区别.可选字段(即类型之一Option[T])可能永远不会有值.另一方面,具有默认值的字段不需要将其值作为构造函数的参数提供.因此,这两个概念是正交的,并且毫不奇怪,字段可以是可选的并且具有默认值.

也就是说,我认为可以使用Opt而不是仅仅Option为这些字段进行合理的论证,而不仅仅是为客户节省一些打字.这样做会使API更加灵活,因为您可以使用T参数替换Opt[T]参数(反之亦然),而不会破坏构造函数[1]的调用者.

至于使用null公共字段的默认值,我认为这是一个坏主意."您"可能知道您期望a null,但访问该字段的客户可能不会.即使该字段是私有的,null当其他开发人员必须维护您的代码时,使用a 也会在路上遇到麻烦.关于null值的所有常见论点都在这里发挥作用 - 我不认为这个用例是任何特殊的例外.

[1]前提是您删除了option2opt转换,以便调用者必须TOpt[T]需要时传递a .


oxb*_*kes 9

不要将任何内容自动转换为选项.在这里使用我的答案,我认为你可以很好地做到这一点,但是以一种类型安全的方式.

sealed trait NumDigits { /* behaviour interface */ }
sealed trait FallbackUrl { /* behaviour interface */ }
case object NoNumDigits extends NumDigits { /* behaviour impl */ }
case object NofallbackUrl extends FallbackUrl { /* behaviour impl */ }

implicit def int2numd(i : Int) = new NumDigits { /* behaviour impl */ }
implicit def str2fallback(s : String) = new FallbackUrl { /* behaviour impl */ }

class Gather(finishOnKey: Char = '#', 
              numDigits: NumDigits = NoNumDigits, // Infinite
              fallbackUrl: FallbackUrl = NoFallbackUrl, 
              timeout: Int = 5
Run Code Online (Sandbox Code Playgroud)

然后,你可以把它作为你想-很明显将您的行为方法FallbackUrlNumDigits适当的.这里的主要负面因素是它是一大堆样板

Gather(numDigits = 4, fallbackUrl = "http://wibble.org")
Run Code Online (Sandbox Code Playgroud)

  • 澄清我的其他评论.你的方法很好,因为它是类型安全的,如果我在传递的参数中需要更多的行为,它会非常强大.但我没有.:)为所有参数类型添加包装类和隐式转换是IMO太笨重了.此外,它使API文档更不清晰.只有整数和字符串可以使用时,签名将指示WrapperX类型参数... (2认同)

Itt*_*ayD 5

就个人而言,我认为使用'null'作为默认值是完全可以的.如果您想向客户传达可能未定义的内容,则使用Option而不是null.因此,返回值可以声明为Option [...],也可以是抽象方法的方法参数.这样可以避免客户端阅读文档,或者更有可能获得NPE,因为没有意识到某些东西可能是空的.

在您的情况下,您知道可能存在空值.如果你喜欢Option的方法就val optionalFallbackUrl = Option(fallbackUrl)在方法的开头做.

但是,此方法仅适用于AnyRef类型.如果你想对任何类型的参数使用相同的技术(没有导致Integer.MAX_VALUE替换为null),那么我想你应该选择其中一个其他答案

  • 实际上,我强烈反对在案例类字段中使用“空”默认值。由于该字段是自动公开的,因此避免“ null”的所有常见原因均适用。即使在类定义中,当考虑长期可维护性时,具有潜在的“ null”值也很容易出错。 (2认同)