Что такое строгость позвоночника

В Haskell термин строгость шипов часто упоминается в отношении ленивой оценки. Хотя я имею смутное понимание того, что это означает, было бы неплохо получить более конкретное объяснение:

  • Что такое позвоночник структуры данных.
  • Что означает строгость позвоночника?
  • Каковы преимущества при сравнении строгих строгих структур данных с ленивыми?

Ответы

Ответ 1

Здесь пример

> length (undefined : 3 : 4 : undefined : [])
4
> length (2 : 3 : 4 : 5 : undefined)
<<loop>>

Первый список содержит элементы в виде элементов, но "форма" списка полностью определена. Грубо говоря, каждая ячейка списка имеет четко определенный "указатель" на свой следующий элемент. Эта "форма" называется позвоночником.

Второй список, для сравнения, имеет полностью определенные элементы, но его позвоночник не определен. Это связано с тем, что он не заканчивается пустым списком [], но с не заканчивающимся выражением undefined. В этом случае позвоночник не определен.

Функция length заботится о позвоночнике, а не о элементах. Таким образом, он может работать в первом случае (благодаря лени), но не второй. Мы говорим, что length является строгим в позвоночнике, но не в элементах списка.

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

В то время как некоторые функции должны быть строгими (например, длина), другие могут быть написаны как в строгом, так и в позвоночнике. Например, map в списках имеет spinal-lazy: он вернет первый элемент вывода перед доступом ко всему позвоночнику своего ввода. Более строгий вариант можно получить с помощью

map' :: (a->b) -> [a] -> [b]
map' _ []     = []
map' f (x:xs) = (f x :) $! map' f xs

Будет ли это полезно, зависит от контекста. Рассмотрим

-- apply n times function f
iter n f = foldr (.) id $ replicate n f

list1 = iter 1000 (map  succ) [1..10]
list2 = iter 1000 (map' succ) [1..10]

Если я требую head list1, я буду принуждать приложение 1000 карт только к первому элементу списка. Это означает, что после этого в памяти будет размещено 1000 выделенных колосков.

Вместо этого head list2 заставит приложение 1000 карт во всем списке. Таким образом, все 1000 thunks могут быть немедленно собраны мусором, восстанавливая память.