scala - scala函数-案例类内部或外部的方法/函数?

标签 scala functional-programming purely-functional

作为Scala的初学者-函数式方法,我是否应该将case类的函数/方法放在此类中(然后使用诸如方法链接,IDE提示之类的东西)还是它是一种更具功能性的方法,对此我有点困惑在案例类之外定义函数。让我们考虑两种非常简单的环形缓冲区实现方法:

1 /案例类中的方法

case class RingBuffer[T](index: Int, data: Seq[T]) {
  def shiftLeft: RingBuffer[T] = RingBuffer((index + 1) % data.size, data)
  def shiftRight: RingBuffer[T] = RingBuffer((index + data.size - 1) % data.size, data)
  def update(value: T) = RingBuffer(index, data.updated(index, value))
  def head: T = data(index)
  def length: Int = data.length
}

使用这种方法,您可以进行方法链接之类的工作,IDE在这种情况下将能够提示方法:
val buffer = RingBuffer(0, Seq(1,2,3,4,5))  // 1,2,3,4,5
buffer.head   // 1
val buffer2 = buffer.shiftLeft.shiftLeft  // 3,4,5,1,2
buffer2.head // 3

2 /案例类之外的函数
case class RingBuffer[T](index: Int, data: Seq[T])

def shiftLeft[T](rb: RingBuffer[T]): RingBuffer[T] = RingBuffer((rb.index + 1) % rb.data.size, rb.data)
def shiftRight[T](rb: RingBuffer[T]): RingBuffer[T] = RingBuffer((rb.index + rb.data.size - 1) % rb.data.size, rb.data)
def update[T](value: T)(rb: RingBuffer[T]) = RingBuffer(rb.index, rb.data.updated(rb.index, value))
def head[T](rb: RingBuffer[T]): T = rb.data(rb.index)
def length[T](rb: RingBuffer[T]): Int = rb.data.length

这种方法对我来说似乎更实用,但是我不确定它的实用性,因为例如IDE将无法像在先前示例中使用方法链接那样提示您所有可能的方法调用。
val buffer = RingBuffer(0, Seq(1,2,3,4,5))  // 1,2,3,4,5
head(buffer)  // 1
val buffer2 = shiftLeft(shiftLeft(buffer))  // 3,4,5,1,2
head(buffer2) // 3

使用这种方法,管道运算符(operator)功能可以使上述第三行更具可读性:
implicit class Piped[A](private val a: A) extends AnyVal {
  def |>[B](f: A => B) = f( a )
}

val buffer2 = buffer |> shiftLeft |> shiftLeft

能否请您总结一下自己对特定方法进阶/后退的看法,以及何时使用哪种方法(如果有)的通用规则是什么?

非常感谢。

最佳答案

在此特定示例中,第一种方法比第二种方法具有更多的好处。我会在case类中添加所有方法。

这是ADT上的示例,其中将逻辑与数据解耦有一些好处:

sealed trait T
case class X(i: Int) extends T
case class Y(y: Boolean) extends T

现在,您可以继续添加逻辑而无需更改数据。
def foo(t: T) = t match {
   case X(a) => 1
   case Y(b) => 2 
}

另外,foo()的所有逻辑都集中在一个块中,这使它很容易看出它如何在X和Y上运行(与X和Y具有自己的foo版本相比)。

在大多数程序中,逻辑更改比数据更改多得多,因此这种方法允许您添加额外的逻辑,而无需更改/修改现有代码(错误更少,破坏现有代码的机会更少)。

将代码添加到伴随对象中

在如何使用隐式转换和类型类的概念向类中添加逻辑时,Scala具有很大的灵活性。这是从ScalaZ借来的一些基本思想。在此示例中,数据(案例类)仅保留数据,并将所有逻辑添加到伴随对象中。
// A generic behavior (combining things together)
trait Monoid[A] {
  def zero: A
  def append(a: A, b: A): A
}

// Cool implicit operators of the generic behavior
trait MonoidOps[A] {
    def self: A
    implicit def M: Monoid[A]
    final def ap(other: A) = M.append(self,other)
    final def |+|(other: A) = ap(other)
}

object MonoidOps {
     implicit def toMonoidOps[A](v: A)(implicit ev: Monoid[A]) = new MonoidOps[A] {
       def self = v
       implicit def M: Monoid[A] = ev
    }
}


// A class we want to add the generic behavior 
case class Bar(i: Int)

object Bar {
  implicit val barMonoid = new Monoid[Bar] {
     def zero: Bar = Bar(0)
     def append(a: Bar, b: Bar): Bar = Bar(a.i + b.i)
  }
}

然后,您可以使用以下隐式运算符:
import MonoidOps._
Bar(2) |+| Bar(4)  // or Bar(2).ap(Bar(4))
res: Bar = Bar(6)

或者在围绕Monooid Type类构建的泛型函数中使用Bar。
def merge[A](l: List[A])(implicit m: Monoid[A]) = l.foldLeft(m.zero)(m.append)

merge(List(Bar(2), Bar(4), Bar(2)))
res: Bar = Bar(10)

关于scala - scala函数-案例类内部或外部的方法/函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38025936/

相关文章:

java - 将参数从流重复应用到流畅的构建器方法

scala - 包含保留关键字的 JSON 反序列化

Scala:替换字符串中的换行符、制表符和返回序列

java - 卡夫卡 : What is new API for ConsumerConnector in version 0. 11

Haskell:QuickCheck 属性无法使用含义进行测试

java - RxJava : how to compose multiple Observables with dependencies and collect all results at the end?

haskell - 差异列表的显式纯函数数据结构

scala - 使用空参数从Scala调用Java API

java - 在 Java8 中收集统计信息

.net - F# 函数耦合迭代 : performance issue and preferred functional style