在 Scala 中使用选项的惯用方法

发布于 2024-10-14 23:09:26 字数 1889 浏览 2 评论 0原文

我正在将一些 Java 代码转换为 Scala,试图使代码尽可能地惯用。

所以,我现在有一些使用选项而不是可为空值的代码,我想知道事情是否是 Scalaiish,或者我是否错了。那么,你们能批评一下下面的代码片段吗?

我特别寻求反馈的领域是:

  • 使用伴生对象作为工厂,根据我们是否要传递选项或字符串提供 2 个选项: String 构造函数好吗,或者我们应该始终暴露这样的事实:它是一个选项吗?
  • 前提条件的使用:是否有更好的方法来断言 alpha3Code 和 name 是强制性的,并且必须为 alpha2Code 传递非空选项? (我求助于 Guava 来获取字符串实用程序,因为我还没有找到Scala API 中的任何内容)
  • hashCode、equals 和 toString 的实现。 equals 和 toString 再次委托给 Guava,而 equals 使用模式匹配。有没有更 Scala 风格的方式?
  • 我知道我可以使用案例类,这会创建默认实现,但我最感兴趣的是学习如何在无法使用案例类的情况下实现这些实现。
    package com.sirika.openplacesearch.api.language
    
    import com.google.common.base.Objects
    import com.google.common.base.Strings
    
    object Language {
        def apply(name : String, alpha3Code : String, alpha2Code : Option[String]) = new Language(name, alpha3Code, alpha2Code)
        def apply(name : String, alpha3Code : String, alpha2Code : String = null) = new Language(name, alpha3Code, Option(alpha2Code))
        def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
    }
    
    
    class Language(val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
        require(!Strings.isNullOrEmpty(alpha3Code))
        require(!Strings.isNullOrEmpty(name))
        require(alpha2Code != null)
        
        override def hashCode(): Int = Objects.hashCode(alpha3Code)
        
                override def equals(other: Any): Boolean = other match {
            case that: Language => this.alpha3Code == that.alpha3Code
            case _ => false
        }
    
        override def toString() : String = Objects.toStringHelper(this)
            .add("name", name)    
            .add("alpha3", alpha3Code)
            .add("alpha2", alpha2Code)
            .toString()
    }

I am converting some Java code to Scala, trying to make the code as idiomatic as possible.

So, I now have some code using Options instead of nullable values, and I wonder whether things are Scalaiish, or whether I'm wrong. So, could you guys please criticize the following snippet of code?

The areas in which I am specifically looking for feedback are:

  • The use of a companion object as a factory, giving 2 options depending on whether we want to pass Options or Strings: is the String constructor fine, or should we always expose the fact that it is an Option?
  • The use of preconditions: are there better ways to assert the fact that alpha3Code and name are mandatory, and a non-null option mustbe passed for alpha2Code? (I am resorting to Guava for the string utils, as I haven't found anything in the Scala API)
  • The implementation of hashCode, equals and toString. equals and toString delegate to Guava again, whereas equals uses pattern matching. Is there a more Scala-ish way?
  • I know I could have used Case classes, which would have created default implementations, but I am mostly interested in learning how I should implement those for the cases where case classes cannot be used.
    package com.sirika.openplacesearch.api.language
    
    import com.google.common.base.Objects
    import com.google.common.base.Strings
    
    object Language {
        def apply(name : String, alpha3Code : String, alpha2Code : Option[String]) = new Language(name, alpha3Code, alpha2Code)
        def apply(name : String, alpha3Code : String, alpha2Code : String = null) = new Language(name, alpha3Code, Option(alpha2Code))
        def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
    }
    
    
    class Language(val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
        require(!Strings.isNullOrEmpty(alpha3Code))
        require(!Strings.isNullOrEmpty(name))
        require(alpha2Code != null)
        
        override def hashCode(): Int = Objects.hashCode(alpha3Code)
        
                override def equals(other: Any): Boolean = other match {
            case that: Language => this.alpha3Code == that.alpha3Code
            case _ => false
        }
    
        override def toString() : String = Objects.toStringHelper(this)
            .add("name", name)    
            .add("alpha3", alpha3Code)
            .add("alpha2", alpha2Code)
            .toString()
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

嗫嚅 2024-10-21 23:09:26

我认为你应该在工厂方法中只公开 Option[String] 。例如,作为您库的用户,我也会问自己应该使用哪种工厂方法的问题。我很可能会使用 Option。

Scala 为我们提供了足够的工具来让我们的生活更轻松。例如,您可以使用默认选项,如下所示:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)

如果我再次作为库的用户,只想传递字符串而不每次将其包装在 Some 中,我可以像这样编写自己的隐式转换:

implicit def anyToOption[T](t: T): Option[T] = Some(t)

甚至(如果我个人使用 nulls):

implicit def anyToOption[T](t: T): Option[T] = 
 if (t == null) None else Some(t)

但我相信,如果你强制执行选项,它会让你的 API 更加坚实和清晰。

I think you should expose only Option[String] in factory method. For example I, as a user of your library, will also ask myself question which factory method I should use. And most probably I will use Option.

Scala gives us enough tools to make our lifes easier. For example you can use default for option like this:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)

If I, again as user of your library, want to pass just string without wrapping it in Some each time, I can write my own implicit conversion like this:

implicit def anyToOption[T](t: T): Option[T] = Some(t)

or even (if I personally use nulls):

implicit def anyToOption[T](t: T): Option[T] = 
 if (t == null) None else Some(t)

But I believe, if you enforce option, it will make your API more solid and clear.

叫思念不要吵 2024-10-21 23:09:26

您应该避免使用 null ,除非有充分的理由不这样做。事实上,你可以这样写:

def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, Option(alpha2Code))
def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code, None)

前提条件很好。你可以这样写:

require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))

不过,不一定是改进。

StringhashCode,所以我不明白为什么你要调用另一个方法来生成哈希码,而不是仅仅调用 alpha3Code.hashCode 。不过,我确实认为 Scala API 中有一些东西。没有把握。

equals 代码应该有一个 canEqual 方法,除非您将类设为 sealedfinal。模式匹配几乎就是实现它的方法,尽管考虑到提取器的存在,您可以像这样编写它:

case Language(_, `alpha3Code`, _) => true

但是您编写它的方式几乎是通常编写的方式。

You should avoid null unless there's a very good reason not to. As it is, you could have just written this:

def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, Option(alpha2Code))
def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code, None)

The preconditions are fine. You could write it like this:

require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))

Not necessarily an improvement, though.

A String has hashCode, so I don't understand why you are calling another method to generate a hash code instead of just calling alpha3Code.hashCode. I do think there's something in the Scala API, though. Not sure.

The equals code should have a canEqual method, unless you make your class sealed or final. Pattern match is pretty much the way to do it, though you could have written it like this given the presence of an extractor:

case Language(_, `alpha3Code`, _) => true

But the way you wrote it is pretty much the way it is usually written.

一笑百媚生 2024-10-21 23:09:26

我不喜欢选项——它们增加了一定程度的间接性,在许多情况下这是不必要的和令人困惑的。我更不喜欢空值,所以我知道使用选项通常是合理的。但是,您应该始终查看是否有更自然的方法来消除界面中 Option 的使用。

默认参数或单独的重载通常是更好的选择。所以我会像这样重写你的代码:

package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}

番石榴文档

I dislike Options -- they add a level of indirection that's unnecessary and confusing in many cases. I dislike nulls even more, so I understand that often the use of Options is justified. However, you should always see if there is a more natural way to eliminate the the use of Option in an interface.

Default parameters or separate overloads are often a better option. So I'd rewrite your code like this:

package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}

Guava docs

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文