我正在开发一个 Scala API(顺便说一下,适用于 Twilio),其中的操作具有相当大量的参数,并且其中许多参数都有合理的默认值。为了减少输入并提高可用性,我决定使用带有命名参数和默认参数的案例类。例如,对于 TwiML Gather 动词:
case class Gather(finishOnKey: Char = '#',
numDigits: Int = Integer.MAX_VALUE, // Infinite
callbackUrl: Option[String] = None,
timeout: Int = 5
) extends Verb
这里感兴趣的参数是callbackUrl。它是唯一真正可选的参数,因为如果没有提供任何值,则不会应用任何值(这是完全合法的)。
我已将其声明为一个选项,以便在 API 的实现端使用它执行单子(monad)映射例程,但这给 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)
据我所知,有两个选项(没有双关语)可以解决这个问题。要么让 API 客户端将隐式转换导入作用域:
implicit def string2Option(s: String) : Option[String] = Some(s)
或者我可以使用 null 默认值重新声明案例类,并将其转换为实现端的选项:
case class Gather(finishOnKey: Char = '#',
numDigits: Int = Integer.MAX_VALUE,
callbackUrl: String = null,
timeout: Int = 5
) extends Verb
我的问题如下:
- 有没有更优雅的方法来解决我的特殊情况?
- 更一般地说:命名参数是一项新的语言功能 (2.8)。难道选项和命名默认参数就像油和水一样吗? :)
- 在这种情况下,使用 null 默认值可能是最佳选择吗?
最佳答案
这是另一个解决方案,部分灵感来自 Chris' answer 。它还涉及到一个包装器,但包装器是透明的,您只需定义一次,并且 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())
<小时/>
为了解决更大的设计问题,我认为选项和命名默认参数之间的交互并不像乍一看那么复杂。可选字段和具有默认值的字段之间有明确的区别。可选字段(即 Option[T]
类型之一)可能永远没有值。另一方面,具有默认值的字段不需要将其值作为参数提供给构造函数。因此,这两个概念是正交的,并且字段可能是可选的并且具有默认值也就不足为奇了。
也就是说,我认为对于此类字段使用 Opt
而不是 Option
是合理的,而不仅仅是节省客户端的一些输入。这样做可以使 API 更加灵活,因为您可以将 T
参数替换为 Opt[T]
参数(反之亦然),而不会破坏该函数的调用者构造函数[1]。
至于对公共(public)字段使用 null
默认值,我认为这是一个坏主意。 “您”可能知道您期望一个 null
,但访问该字段的客户端可能不知道。即使该字段是私有(private)的,当其他开发人员必须维护您的代码时,使用 null
也会带来麻烦。所有关于 null
值的常见争论都在这里发挥作用 - 我不认为这个用例有任何特殊的异常(exception)。
[1] 前提是您删除了 option2opt 转换,以便每当需要 Opt[T]
时调用者都必须传递 T
。
关于scala - 选项和命名默认参数就像 Scala API 中的油和水吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4199393/