Інтерфейс без побічних ефектів на вершині значної бібліотеки


16

В інтерв’ю Джона Х'юза, де він розповідає про Ерланга та Хаскелла, він має сказати про використання значних бібліотек в Ерланге:

Якщо я хочу використовувати бібліотеку, що склалася, я зазвичай будую поверх неї інтерфейс без побічних ефектів, щоб потім міг безпечно використовувати її в решті свого коду.

Що він розуміє під цим? Я намагаюся придумати приклад того, як це виглядатиме, але моя фантазія та / або знання мене не вдається.


Ну, якщо держава існує, вона не піде. Хитрість полягає в тому, щоб створити щось, що буде відслідковувати залежність. Стандартна відповідь Haskell - це "монади" або більш досконалі "стрілки" . Їм трохи важко обернути голову, і я ніколи насправді цього не робив, тому комусь іншому доведеться спробувати пояснити їх.
Ян Худек

Відповіді:


12

(Я не знаю Ерланга, і я не можу написати Haskell, але я думаю, що все-таки можу відповісти)

Ну, в цьому інтерв'ю наведено приклад бібліотеки генерації випадкових чисел Ось можливий інтерпретаційний стан:

# 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

З таким інтерфейсом ми завжди можемо повернутися до попереднього стану. Якщо дві частини вашого коду стосуються одного і того ж RNG, вони фактично отримають однакову послідовність чисел. Для функціонального мислення це дуже бажано.

Реалізовувати це на державній мові не так вже й складно. Наприклад:

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.
  • якщо функцію, яку я пишу, неможливо запам'ятати, це залежить від зовнішнього стану, що змінюється. (Запам'ятовування може бути не дуже хорошою ідеєю через тиск пам’яті, але воно все-таки має бути можливим)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.