Ухватить неизменяемые структуры данных
Я изучаю 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
, мог бы взять весь фрагмент вместо двух параметров.
Все еще... неплохо, не так ли?