是选项,并命名默认参数如油和水在斯卡拉API?

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

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

这里感兴趣的参数是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) 

据我所知,有两种select(不是双关)来解决这个问题。 要么使API客户端导入隐式转换为范围:

 implicit def string2Option(s: String) : Option[String] = Some(s) 

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

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

我的问题如下:

  1. 有没有更好的方法来解决我的具体情况?
  2. 更一般地说:命名参数是一种新的语言特征(2.8)。 可能事实certificate,选项和名为默认参数就像油和水? 🙂
  3. 在这种情况下可能使用空默认值是最好的select?

这是另外一个解决scheme,部分由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()) 

为了解决更大的devise问题,我不认为选项和默认参数之间的交互作用与第一眼看上去一样多。 可选字段和缺省值之间有明确的区别。 一个可选的字段(即一个Option[T] )可能永远不会有一个值。 另一方面,具有默认值的字段不需要将其值作为参数提供给构造函数。 这两个概念因此是正交的,并且字段可以是可选的并且具有默认值并不奇怪。

也就是说,我认为可以使用Opt而不是Option来理解这些领域,除了保存客户端的一些input。 这样做可以使API更加灵活,因为您可以在不破坏构造函数[1]的调用者的情况下用Opt[T]参数replaceOpt[T]参数(反之亦然)。

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

[1]假如你删除了option2opt转换,那么只要需要Opt[T] ,调用者就必须通过T

不要将任何东西自动转换为选项。 在这里用我的答案 ,我认为你可以做到这一点很好,但以一种安全的方式。

 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 

然后你可以按照你的意愿调用它 – 显然把你的行为方法添加到FallbackUrlNumDigits中。 这里的主要负面是它是一吨样板

 Gather(numDigits = 4, fallbackUrl = "http://wibble.org") 

就个人而言,我认为在这里使用'null'作为默认值是完全可以的。 使用选项而不是null是当你想传达给你的客户,可能没有定义的东西。 所以返回值可能被声明为Option […]或抽象方法的方法参数。 这样可以节省客户阅读文档或者更可能得到NPE,因为没有意识到可能是空的。

在你的情况下,你意识到null可能在那里。 如果你喜欢Option的方法,只需要在方法开始时执行val optionalFallbackUrl = Option(fallbackUrl)

但是,这种方法只适用于AnyReftypes。 如果你想对任何一种参数使用相同的技术(不会导致Integer.MAX_VALUE作为null的替代),那么我想你应该去与其他答案之一

我认为只要在Scala中没有语言支持来实现一种真正的void(下面的解释)'type',使用Option就可能是一个更清晰的解决scheme。 甚至可能是所有的默认参数。

问题是,使用你的API的人知道你的一些参数是默认的,不如把它们当作可选的。 所以,他们宣称他们是

 var url: Option[String] = None 

这一切都很好,干净,他们可以等待,看看他们是否得到任何信息来填补这个选项。

当用默认的参数调用你的API时,他们会面临一个问题。

 // Your API case class Gather(url: String) { def this() = { ... } ... } // Their code val gather = url match { case Some(u) => Gather(u) case _ => Gather() } 

我认为这样做会容易得多

 val gather = Gather(url.openOrVoid) 

None情况下*openOrVoid将被忽略。 但这是不可能的。

所以你真的应该考虑谁会使用你的API以及他们如何使用它。 这可能是你的用户已经使用Option来存储所有的variables,因为他们知道他们是可选的最后…

默认参数很好,但也使事情复杂化; 特别是当已经有一个Optiontypes的时候。 我想你的第二个问题是有道理的。

可能我只是主张你现有的方法, Some("callbackUrl") ? 这是API用户input的6个字符以上的字符,向他们显示该参数是可选的,并且可能使您的实现更容易。

我认为你应该咬紧牙关,然后Option 。 之前我遇到过这个问题,经过一些重构之后通常就会消失。 有时候它没有,我也和它一起生活。 但事实是,默认参数不是“可选”参数 – 它只是一个具有默认值的参数。

我非常赞成Debilski的 回答 。

我也对此感到惊讶。 为什么不能概括为:

 implicit def any2Option[T](x: T): Option[T] = Some(x) 

有什么理由不能成为Predef的一部分?