是选项,并命名默认参数如油和水在斯卡拉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
我的问题如下:
- 有没有更好的方法来解决我的具体情况?
- 更一般地说:命名参数是一种新的语言特征(2.8)。 可能事实certificate,选项和名为默认参数就像油和水? 🙂
- 在这种情况下可能使用空默认值是最好的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
然后你可以按照你的意愿调用它 – 显然把你的行为方法添加到FallbackUrl
和NumDigits
中。 这里的主要负面是它是一吨样板
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,因为他们知道他们是可选的最后…
默认参数很好,但也使事情复杂化; 特别是当已经有一个Option
types的时候。 我想你的第二个问题是有道理的。
可能我只是主张你现有的方法, Some("callbackUrl")
? 这是API用户input的6个字符以上的字符,向他们显示该参数是可选的,并且可能使您的实现更容易。
我认为你应该咬紧牙关,然后Option
。 之前我遇到过这个问题,经过一些重构之后通常就会消失。 有时候它没有,我也和它一起生活。 但事实是,默认参数不是“可选”参数 – 它只是一个具有默认值的参数。
我非常赞成Debilski的 回答 。
我也对此感到惊讶。 为什么不能概括为:
implicit def any2Option[T](x: T): Option[T] = Some(x)
有什么理由不能成为Predef的一部分?