带有逻辑的案例类什么是惯用方式

时间:2019-01-30 17:57:10

标签: scala functional-programming

FP惯用的方式是什么:假设我有

trait Name

object Name{
 def apply(name: String): Name = {
     if (name.trim.isEmpty || name.trim.length < 3)
       InvalidName
     else 
       ValidName(name.trim)
    }
 }

case object InvalidName extends Name
case class ValidName(name:String) extends AnyVal with Name

现在我有一些辅助功能,例如

def split = name.splitAt(" ")
//some more functions 

哪种方式更惯用:

  1. 将它们自己放置在case类中,但是这可以使case类包含一些逻辑,但是我可以这样做:

    val n = ValidName("john smith")

    val (first, last) = n.split

  2. 将它们放入专用对象中,然后方法看起来像

    def split(n: ValidName) = n.name.splitAt(" ")

  3. 使用隐式类创建一个对象,该对象将接受Name并将调用方法

您怎么看?

3 个答案:

答案 0 :(得分:2)

有与添加逻辑以没有重大问题一个case class,特别是当它以不同格式只是提取数据。 (将数据成员添加到case class时会出现问题。)

所以我会做选项1,不用担心!


作为对评论的回应,case class实际上只是创建带有一堆有用的预实现方法的类的快捷方式。特别是,unapply方法允许将case class用于模式匹配,而equals对两个实例的字段进行逐元素比较。

还有许多其他方法以不同的方式从case class中提取数据。最明显的是toStringcopy,但也有其他类似hashCode,并从继承了所有东西Product,例如productIterator

由于case class已经具有以有用方式提取数据的方法,因此我不反对添加您的split方法作为从case class提取数据的另一种方法。

答案 1 :(得分:2)

更多惯用语:

case class Name private (name: String) {
  lazy val first :: last :: Nil = name.split(" ").toList
}
object Name {
  def fromString (name: String): Either[String, Name] = {
    if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
    else Right(new Name(name.trim))
  }
}

或者也许是这样

case class Name (first: String, last: String) {
  lazy val fullName = s"$first $last"
}
object Name {
  def fromString (name: String): Either[String, Name] = {
    if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
    else {
      val first :: last :: Nil = name.split(" ").toList
      Right(new Name(first, last))
    }
  }
}

在scala中,通过使用Either来表示失败案例比通过继承来表示失败案例更为常见。如果您有N的实例,则无法在其上调用任何函数,则可能必须对其进行模式匹配。但是类似Either的类型已经带有mapfold等功能,使其更易于使用。

拥有私有的构造函数有助于确保您只能创建有效的Name,因为创建一个唯一的方法是通过fromString方法。

请勿为此使用隐式。不需要,只会使代码混乱。隐含的含义不是真的。

答案 2 :(得分:1)

我认为这取决于上下文。在这种情况下,如果您使用的大多数方法只是对String方法进行了细微调整,则可能需要考虑第四个选项:

case class Name(name: String)
implicit def NameToString(n: Name) = n.name

Name("Iron Man").split(" ") // Array(Iron, Man)