掌握不可变数据结构

per*_*i4n 19 functional-programming scala immutability data-structures

我正在学习斯卡拉,作为一名优秀的学生,我试图遵守我发现的所有规则.

一条规则是:不可靠性!

所以我试图使用不可变数据结构和val来编写所有代码,有时候这很难.

但今天我想到了自己:唯一重要的是对象/类应该没有可变状态.我不是被迫以不可变的样式编写所有方法,因为这些方法不会相互影响.

我的问题:我是否正确或有任何问题/缺点我看不到

编辑:

aishwarya的代码示例:

def logLikelihood(seq: Iterator[T]): Double = {
  val sequence = seq.toList
  val stateSequence = (0 to order).toList.padTo(sequence.length,order)
  val seqPos = sequence.zipWithIndex

  def probOfSymbAtPos(symb: T, pos: Int) : Double = {
    val state = states(stateSequence(pos))
    M.log(state( seqPos.map( _._1 ).slice(0, pos).takeRight(order), symb))
  }

  val probs = seqPos.map( i => probOfSymbAtPos(i._1,i._2) )

  probs.sum
}  
Run Code Online (Sandbox Code Playgroud)

说明:这是一种计算变量阶齐次马尔可夫模型的对数似然的方法.state的apply方法获取所有先前的符号和即将到来的符号,并返回这样做的概率.

正如您所看到的:整个方法只是增加了一些使用变量更容易的概率.

Apo*_*isp 47

规则不是真正的不变性,而是参考透明度.使用本地声明的可变变量和数组是完全可以的,因为没有任何效果可以观察到整个程序的任何其他部分.

引用透明度(RT)的原则是这样的:

如果对于所有程序,每个出现的in 都可以用求值结果替换,而不影响可观察的结果,则表达式e引用透明的.pepep

请注意,如果e创建并改变某些本地状态,则不会违反RT,因为没有人能够观察到这种情况.

也就是说,我非常怀疑你的实现对于vars来说更直接.

  • 是.您正在寻找的是一些原则,如果遵循,会给您带来一些好处.你有"不变性",这是非常接近,但限制性太强."参考透明度"是更一般的原则,我相信这是你正在寻找的原则. (3认同)
  • @Ben:如果并发性会影响结果,那么表达式最重要的是*不是*引用透明的. (3认同)
  • 另请注意,您正在寻找的原则不是*审核*.也就是说"一点点"可变状态是可以的. (2认同)
  • @Apocalisp我同意你所说的原则,但我觉得它不完全准确.使用内部突变仍然意味着您在本地实现*中违反了引用透明度*.如果局部范围很大且足够复杂(但通常不应该是这样),那么出于同样的原因,这可能是一个问题.只是通过使用引用透明的**接口进行编程,您可以获得大部分好处**.您似乎暗示引用透明性是一种只能应用于接口的属性,这是不正确的. (2认同)
  • @Apocalisp啊,那我们同意.我读了你的帖子,建议如果你去"各地的参考透明度"而不是"到处都是不变的",那么你自然会得到纯粹的接口,并且可能会暗示本地实现.虽然我相信一个函数可以在其接口处引用透明但在内部使用可变性在您具有并发性时会失败.如果这是正确的,那么并非所有使用"本地可变性"的方式都是不可观察的. (2认同)
  • @ShelbyMooreIII:元组被排序的事实(例如,"Set`s不是")与RT无关.您更新的解释仍然过于模糊 - 您甚至没有定义可交换(不正确).我认为RT也意味着我看到的唯一(明智的)幂等定义(评估表达式一次或两次不会影响语义).而且,你的解释写得过于抽象:写得不好http://amzn.to/zF3K6v,你甚至没有引用awelonblue对'declarative'的定义,你显然很困惑.最后,我同意@Apocalisp. (2认同)
  • @ShelbyMooreIII:注意:原帖(http://awelonblue.wordpress.com/2012/01/12/defining-declarative/)有点清楚 - 但它也清楚地表明作者正在谈论声明,甚至纯粹函数式编程不是声明性的,也没有交换性和幂等性的特性.我购买它是为了声明和他做的例子,但这些概念对函数式编程本身没有意义.错误再一次是你的. (2认同)

Nei*_*ssy 7

函数式编程的一个例子就是在代码中简洁并引入更多数学方法.它可以减少错误的可能性,使您的代码更小,更易读.至于更容易与否,它确实需要您以不同的方式思考您的问题.但是一旦你习惯于使用功能模式进行思考,那么更具势在意的风格可能会变得更容易.

它很难完美地运行并且具有零可变状态但是对于具有最小可变状态非常有益.要记住的是,一切都需要平衡而不是极端.通过减少可变状态的数量,您最终会更难以编写具有意外后果的代码.一个常见的模式是拥有一个可变变量,其值是不可变的.这种方式标识(命名变量)和值(可以分配变量的不可变对象)是分开的.

var acc: List[Int] = Nil
// lots of complex stuff that adds values
acc ::= 1
acc ::= 2
acc ::= 3
// do loop current list
acc foreach { i => /* do stuff that mutates acc */ acc ::= i * 10 }
println( acc ) // List( 1, 2, 3, 10, 20, 30 )
Run Code Online (Sandbox Code Playgroud)

在我们开始foreach的时候,foreach正在循环acc的值.任何acc的突变都不会影响循环.这比java中的典型迭代器更安全,其中列表可以在迭代中更改.

还存在并发问题.不可变对象很有用,因为JSR-133内存模型规范声称对象最终成员的初始化将在任何线程可以看到这些成员之前发生,期间!如果它们不是最终的那么它们是"可变的"并且不能保证正确的初始化.

演员是放置可变状态的理想场所.表示数据的对象应该是不可变的.以下面的例子为例.

object MyActor extends Actor {
  var acc: List[Int] = Nil
  def act() {
    loop {
      react {
        case i: Int => acc ::= i
        case "what is your current value" => reply( acc )
        case _ => // ignore all other messages
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们可以发送acc的值(这是一个List)而不用担心同步,因为List是不可变的,也就是List对象的所有成员都是final.同样由于不变性,我们知道没有其他演员可以改变发送的底层数据结构,因此没有其他演员可以改变这个演员的可变状态.