Понимание неизменных структур данных
Я изучаю scala и как хороший студент я стараюсь соблюдать все правила, которые я нашел.
одно правило: неизменность!!!
поэтому я попытался закодировать все с неизменяемыми структурами данных и 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
}
объяснение: это метод вычисления логарифмического правдоподобия однородной марковской модели переменного порядка. Метод apply состояния принимает все предыдущие символы и предстоящий символ и возвращает вероятность этого.
Как вы можете видеть: весь метод просто умножает некоторые вероятности, которые было бы намного проще использовать vars.
3 ответов
правила не непреложность, но референциальной прозрачности. Совершенно нормально использовать локально объявленные изменяемые переменные и массивы, потому что ни один из эффектов не наблюдаем для любых других частей общей программы.
принцип ссылочной прозрачности (RT) заключается в следующем:
выражение e
is совершенно прозрачна если для всех программ p
каждое вхождение e
на p
можно заменить с результатом оценки e
, не влияя на наблюдаемый результат p
.
обратите внимание, что если e
создает и мутирует некоторое локальное состояние, оно не нарушает RT, так как никто не может наблюдать это.
тем не менее, я очень сомневаюсь, что ваша реализация более проста с vars.
случай для функционального программирования один из быть кратким в вашем коде и привносить более математический подход. Это может уменьшить вероятность ошибок и сделать ваш код меньше и более читаемым. Что касается того, легче или нет, это требует, чтобы вы думали о своих проблемах по-другому. Но как только вы привыкнете думать с функциональными шаблонами, вероятно, что функциональный станет легче, чем более императивный стиль.
Это действительно трудно быть идеально функциональный и имеют нулевое изменяемое состояние, но очень полезно иметь минимальный изменяемое состояние. Нужно помнить, что все необходимо сделать в равновесии и не до крайности. Уменьшая количество изменяемого состояния, вы в конечном итоге затрудняете написание кода с непреднамеренными последствиями. Общий шаблон должен иметь изменяемую переменную, значение которой является неизменяемым. Таким образом, identity (именованная переменная ) и value (неизменяемый объект, переменная может быть назначенным ) являются отдельными.
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 зацикливается на значении acc в то время, когда мы начали foreach. Любые мутации в 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
}
}
}
}
в этом случае мы можем отправить значение acc (которое является списком ) и не беспокойтесь о синхронизации, потому что список неизменяем aka все члены объекта списка являются окончательными. Также из-за неизменности мы знаем, что никакой другой актер не может изменить базовую структуру данных, которая была отправлена и, таким образом никакой другой актер не может изменить изменчивое состояние этого актера.
С 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
может взять весь срез вместо двух параметров.
по-прежнему... неплохо, правда?