xwi*_*nus 16 functional-programming scala purely-functional
作为Scala的初学者 - 功能方式,我有点困惑于是否应该将我的case类的函数/方法放在这样的类中(然后使用类似方法链接,IDE提示)或者它是否是更实用的方法定义case类之外的函数.让我们考虑两种非常简单的环形缓冲区实现的方法:
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
}
Run Code Online (Sandbox Code Playgroud)
使用这种方法,您可以执行类似链接的方法,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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
这种方法对我来说似乎更具功能性,但我不确定它有多实用,因为例如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
Run Code Online (Sandbox Code Playgroud)
使用这种方法,管道操作员功能可以使上面的第3行更具可读性:
implicit class Piped[A](private val a: A) extends AnyVal {
def |>[B](f: A => B) = f( a )
}
val buffer2 = buffer |> shiftLeft |> shiftLeft
Run Code Online (Sandbox Code Playgroud)
您能否总结一下您对特定方法的进展/不满意的看法以及何时使用哪种方法(如果有的话)的常见规则?
非常感谢.
在此特定示例中,第一种方法比第二种方法具有更多的好处。我会在case类中添加所有方法。
这是一个有关ADT的示例,其中将逻辑与数据解耦有一些好处:
sealed trait T
case class X(i: Int) extends T
case class Y(y: Boolean) extends T
Run Code Online (Sandbox Code Playgroud)
现在,您可以继续添加逻辑而无需更改数据。
def foo(t: T) = t match {
case X(a) => 1
case Y(b) => 2
}
Run Code Online (Sandbox Code Playgroud)
另外,的所有逻辑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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用以下隐式运算符:
import MonoidOps._
Bar(2) |+| Bar(4) // or Bar(2).ap(Bar(4))
res: Bar = Bar(6)
Run Code Online (Sandbox Code Playgroud)
或者在围绕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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7348 次 |
| 最近记录: |