Scala 案例类与逻辑惯用的方法是什么
FP的惯用方式是什么:假设我有这个Scala 案例类与逻辑惯用的方法是什么,scala,functional-programming,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 V
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
哪种方式更惯用:
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))
}
}
val n=ValidName(“约翰·史密斯”)
val(第一个,最后一个)=n.split
def split(n:ValidName)=n.name.splitAt(“”)
您认为呢?向
案例类添加逻辑没有什么大问题,尤其是当它只是以不同的格式提取数据时。(将数据成员添加到案例类时会出现问题)
所以我会做选择1,不用担心
作为对这些评论的回应,case class
实际上只是一个快捷方式,用于创建一个包含大量有用的预实现方法的类。特别是,unapply
方法允许在模式匹配中使用case类
,并且equals
对两个实例的字段进行元素级比较
还有许多其他方法可以以不同的方式从案例类
中提取数据。最明显的是toString
和copy
,但也有其他类似的hashCode
以及从Product
继承的所有内容,例如productIterator
由于case类
已经有了以有用的方式提取数据的方法,我认为添加split
方法作为从case类
提取数据的另一种方法是没有异议的,我认为这取决于上下文。在这种情况下,如果您使用的大多数方法只是轻微修改到<代码> String 方法,您可能需要考虑第四个选项:
case class Name(name: String)
implicit def NameToString(n: Name) = n.name
Name("Iron Man").split(" ") // Array(Iron, Man)
更地道的说法是:
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类名称private(名称:String){
lazy val first::last::Nil=name.split(“”).toList
}
对象名{
def fromString(名称:String):或者[String,name]={
如果(name.trim.isEmpty | | name.trim.length<3)左侧(“无效名称”)
else Right(新名称(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))
}
}
}
案例类名称(第一个:字符串,最后一个:字符串){
lazy val fullName=s“$first$last”
}
对象名{
def fromString(名称:String):或者[String,name]={
如果(name.trim.isEmpty | | name.trim.length<3)左侧(“无效名称”)
否则{
val first::last::Nil=name.split(“”).toList
右(新名称(第一个、最后一个))
}
}
}
在scala中,通过使用或来表示失败案例比通过继承来表示更为惯用。如果您有一个N
的实例,则无法对其调用任何函数,您可能必须对其进行模式匹配。但是像或者这样的类型已经有了映射
,折叠
等功能,这使得它更容易使用
拥有私有构造函数有助于确保只能创建有效的名称
,因为创建名称的唯一方法是通过fromString
方法
不要为此使用隐式。这是没有必要的,只会使代码混乱。实际上,隐式函数并不是用来做什么的。Case类不应该为用于定义代数结构和主要用于模式匹配的数据保存函数。实际上,这些预先实现的方法都是由trait product继承的,它没有这些。这就是我所说的,数据上的函数应该保持在其他实体的外部,如特征或对象等。谢谢Tim,我不知怎么地介于你的建议和我的观点之间,这与@RamanMishra所说的关于案例类是代数数据类型的观点一致。我通常倾向于将业务逻辑放在案例类之外。我当然可以创建一个包含此逻辑的对象作为选项3,但它似乎是冗余开销wdyt?@igx我同意业务逻辑不应该在案例类
中,但我认为简单的操作是可以的,只要它们直接与类本身中的值相关。ADT可以使用其他方法来取消选中它们,以及模式匹配。请注意,在对象的apply
方法中使用Name(…)
将调用Name。apply(…)
(调用自身)并溢出堆栈(我想,还没有测试过它)。。。您可能应该将其替换为新名称(…)
,以确保它调用类构造函数directly@AlvaroCarrasco很好,我过度简化了这个例子。正如你所说,我编辑了这个问题以避免出现这种情况,但模式匹配是一种特定的解决方案。这里的问题是,我们在Classis的一个成员上应用了一些商业逻辑,我确实看到了您的建议的好处,但是对于设计决策:您不想用隐式填充代码。您希望使用implicits(IMHO)来扩展外部lib以适合您的dsl或方便。或进行必要的转换。在您的示例中,您只需将名称
转换为字符串
,这在某种程度上甚至是危险的。例如,您可以执行诸如``val Name=Name(“John Doe”)Name+“something”``之类的操作,这是意外的行为