Ухватить неизменяемые структуры данных

Я изучаю scala и как хороший ученик, я стараюсь подчиняться всем правилам, которые я нашел.

Одно правило: IMMUTABILITY!!!

Итак, я пытался кодировать все с неизменяемыми структурами данных и vals, а иногда это очень сложно.

Но сегодня я подумал про себя: главное, что объект/класс не должен иметь изменчивого состояния. Я не вынужден кодировать все методы в неизменяемом стиле, потому что эти методы не влияют друг на друга.

Мой вопрос: Я исправляю или есть какие-либо проблемы/недостатки, которые я не вижу?

EDIT:

Пример кода для 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
}  

Объяснение: Это метод вычисления лог-правдоподобия однородной марковской модели переменного порядка. Применяемый метод состояния принимает все предыдущие символы и приближающийся символ и возвращает вероятность этого.

Как вы можете видеть: весь метод просто умножает некоторые вероятности, которые были бы намного проще с использованием vars.

Ответы

Ответ 1

Это правило не является неизменяемым, но ссылочной прозрачностью. Совершенно нормально использовать локально объявленные изменчивые переменные и массивы, потому что ни один из эффектов не наблюдается для любых других частей общей программы.

Принцип ссылочной прозрачности (RT) таков:

Выражение e является ссылочно прозрачным, если для всех программ p каждое вхождение e в p может быть заменено результатом оценки e, не затрагивая наблюдаемый результат p.

Обратите внимание, что если e создает и изменяет какое-либо локальное состояние, это не нарушает RT, так как никто не может наблюдать это.

Тем не менее, я очень сомневаюсь, что ваша реализация более прямолинейна с vars.

Ответ 2

Случай для функционального программирования является одним из , который является кратким в вашем коде и привносит более математический подход. Это может уменьшить вероятность ошибок и сделать ваш код более компактным и читаемым. Что касается того, чтобы быть проще или нет, это требует, чтобы вы думали о своих проблемах по-разному. Но как только вы начнете использовать мышление с функциональными шаблонами, вероятно, что функционал станет проще, чем более императивный стиль.

Очень сложно быть абсолютно функциональным и иметь нулевое изменяемое состояние, но очень полезно иметь минимальное изменяемое состояние. Следует помнить, что все нужно делать в балансе, а не в экстремальном. Уменьшая количество изменчивого состояния, вы в конечном итоге затрудняете писать код с непреднамеренными последствиями. Обычная модель - иметь изменяемую переменную, значение которой неизменно. Таким образом, идентичность (именованная переменная) и значение (неизменяемый объект, которому может быть назначена переменная) являются отдельными.

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 )

В течение времени, когда мы запустили foreach, foreach зацикливает значение acc. Любые мутации для acc не влияют на цикл. Это намного безопаснее, чем типичные итераторы в java, где список может изменять среднюю итерацию.

Существует также проблема concurrency. Неизменяемые объекты полезны из-за спецификации модели памяти 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
      }
    }
  }
}

В этом случае мы можем отправить значение acc (которое является List) и не беспокоиться о синхронизации, потому что List является неизменным, поскольку все члены объекта List являются окончательными. Также из-за неизменности мы знаем, что ни один другой актер не может изменить базовую структуру данных, которая была отправлена, и поэтому ни один другой актер не может изменить изменчивое состояние этого актера.

Ответ 3

Поскольку Apocalisp уже упомянул материал, который я собирался процитировать, обсудим код. Вы говорите, что это просто умножение материала, но я этого не вижу - он ссылается на, по крайней мере, три важных метода, определенных вне: order, states и M.log. Я могу заключить, что order является Int и что states возвращает функцию, которая принимает List[T] и a T и возвращает Double.

Там также есть странные вещи...

def logLikelihood(seq: Iterator[T]): Double = {
  val sequence = seq.toList

sequence никогда не используется, кроме как определить seqPos, так зачем это делать?

  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))

Собственно, вы можете использовать sequence здесь вместо seqPos.map( _._1 ), так как все, что делает, отменяет zipWithIndex. Кроме того, slice(0, pos) является просто take(pos).

  }

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

  probs.sum
}

Теперь, учитывая недостающие методы, трудно утверждать, как это действительно должно быть написано в функциональном стиле. Сохранение методов тайны даст:

def logLikelihood(seq: Iterator[T]): Double = {
  import scala.collection.immutable.Queue
  case class State(index: Int, order: Int, slice: Queue[T], result: Double)

  seq.foldLeft(State(0, 0, Queue.empty, 0.0)) {
    case (State(index, ord, slice, result), symb) =>
      val state = states(order)
      val partial = M.log(state(slice, symb))
      val newSlice = slice enqueue symb
      State(index + 1, 
            if (ord == order) ord else ord + 1, 
            if (queue.size > order) newSlice.dequeue._2 else newSlice,
            result + partial)
  }.result
}

Только я подозреваю, что материал state/M.log также может быть частью state. Теперь я замечаю другие оптимизации, которые я написал так. Скользящее окно, которое вы используете, мне, разумеется, напоминает sliding:

seq.sliding(order).zipWithIndex.map { 
  case (slice, index) => M.log(states(index + order)(slice.init, slice.last))
}.sum

Это будет начинаться только с элемента orderth, поэтому некоторые адаптации будут в порядке. Не слишком сложно. Поэтому перепишите его снова:

def logLikelihood(seq: Iterator[T]): Double = {
  val sequence = seq.toList
  val slices = (1 until order).map(sequence take) ::: sequence.sliding(order)
  slices.zipWithIndex.map { 
    case (slice, index) => M.log(states(index)(slice.init, slice.last))
  }.sum
}

Мне хотелось бы видеть M.log и states... Могу поверить, что я мог бы превратить этот map в foldLeft и покончить с этими двумя методами. И я подозреваю, что метод, возвращенный states, мог бы взять весь фрагмент вместо двух параметров.

Все еще... неплохо, не так ли?