Интерфейс без побочных эффектов поверх библиотеки с сохранением состояния

16

В интервью с Джоном Хьюзом, где он говорит об Эрланге и Хаскеле, он может сказать следующее об использовании библиотек с сохранением состояния в Эрланге:

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

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

бета
источник
Ну, если государство существует, оно не исчезнет. Хитрость заключается в создании чего-то, что будет отслеживать зависимость. Стандартный ответ на Haskell - «монады» или более продвинутые «стрелки» . Им немного сложно обернуть голову, и я никогда не делал этого, поэтому кто-то другой должен попытаться объяснить их.
Ян Худек

Ответы:

12

(Я не знаю Эрланга и не могу написать на Хаскеле, но, тем не менее, могу ответить)

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

# create a new RNG
var rng = RNG(seed)

# every time we call the next(ceil) method, we get a new random number
print rng.next(10)
print rng.next(10)
print rng.next(10)

Выход может быть 5 2 7. Для того, кто любит неизменность, это совершенно неправильно! Так должно быть5 5 5 , потому что мы вызвали метод для одного и того же объекта.

Так что бы был интерфейс без состояния? Мы можем просмотреть последовательность случайных чисел в виде лениво вычисляемого списка, где nextфактически извлекается голова:

let rng = RNG(seed)
let n : rng = rng in
  print n
  let n : rng = rng in
    print n
    let n : rng in
      print n

С таким интерфейсом мы всегда можем вернуться к предыдущему состоянию. Если две части вашего кода ссылаются на один и тот же ГСЧ, они фактически получат одинаковую последовательность чисел. В функциональном мышлении это очень желательно.

Реализация этого на государственном языке не так уж сложна. Например:

import scala.util.Random
import scala.collection.immutable.LinearSeq

class StatelessRNG (private val statefulRNG: Random, bound: Int) extends LinearSeq[Int] {
  private lazy val next = (statefulRNG.nextInt(bound), new StatelessRNG(statefulRNG, bound))

  // the rest is just there to satisfy the LinearSeq trait
  override def head = next._1
  override def tail = next._2
  override def isEmpty = false
  override def apply(i: Int): Int = throw new UnsupportedOperationException()
  override def length = throw new UnsupportedOperationException()
}

// print out three nums
val rng = new StatelessRNG(new Random(), 10)
rng.take(3) foreach (n => println(n))

Когда вы добавляете немного синтаксического сахара, чтобы он выглядел как список, это на самом деле довольно приятно.

Амон
источник
1
Что касается вашего первого примера: rnd.next (10), каждый раз производящий разные значения, не имеет ничего общего с неизменяемостью, как с определением функции: функции должны быть 1-к-1. (+1, хотя, хорошие вещи)
Стивен Эверс
Благодарность! Это было действительно хорошее, ясное объяснение и пример.
бета
1

Ключевой концепцией здесь является концепция внешнего изменяемого состояния . Библиотека, которая не имеет внешнего изменяемого состояния, не содержит побочных эффектов. Каждая функция в такой библиотеке зависит только от переданных ей аргументов.

  • Если ваша функция получает доступ к какому-либо ресурсу, который ей не был создан (то есть как параметр), то это зависит от внешнего состояния .
  • Если ваша функция создает что-то, что она не передает обратно вызывающей стороне (и не уничтожает это), тогда ваша функция создает внешнее состояние.
  • Когда внешнее состояние сверху может иметь разные значения в разное время, оно становится изменчивым .

Удобные лакмусовые тесты, которые я использую:

  • если функцию A необходимо запустить перед функцией B, то A создает внешнее состояние, от которого зависит B.
  • если функция, которую я пишу, не может быть запомнена, то это зависит от внешнего изменяемого состояния. (Запоминание не может быть хорошей идеей из-за нехватки памяти, но все же должно быть возможно)
Стивен Эверс
источник