Почему Haskell Data.Set не поддерживает бесконечные множества?

В следующем фрагменте:

import qualified Data.Set as Set

data Nat = Zero | Succ Nat deriving (Eq, Show, Ord)

instance Enum Nat where
  pred (Succ x)     = x
  succ x            = Succ x
  toEnum 0          = Zero
  toEnum x          = Succ (toEnum (x-1))
  fromEnum Zero     = 0
  fromEnum (Succ x) = 1 + (fromEnum x)

nats :: [Nat]
nats = [Zero ..]

natSet :: Set.Set Nat
natSet = Set.fromList nats

Почему:

  • elem (toEnum 100) nats == True

но

  • Set.member (toEnum 100) natSet никогда не заканчивается?

Ответ 1

Существующих ответов достаточно, но я хочу немного рассказать о поведении Set s.

Похоже, вы надеетесь на ленивый набор всех Nat s; вы берете бесконечный список всех Nat и используете Set.toList на нем. Это было бы чудесно; математики часто говорят в терминах "совокупности всех натуральных чисел". Проблема заключается в том, что реализация Set не соответствует ожиданиям лени, поскольку списки есть.

Реализация Set основана на двоичных деревьях, сбалансированных по размеру (или деревья ограниченного баланса)

Документы для Data.Set

Предположим, вы хотите лениво построить двоичное дерево из списка. Элементы из списка будут вставлены в дерево только тогда, когда потребуется более глубокий обход дерева. Итак, вы спрашиваете, есть ли 100 в дереве. Он продолжал бы добавлять числа 1-99 к дереву, по одному за раз. Затем он, наконец, добавит 100 к дереву и обнаружит, что 100 действительно является элементом в дереве. Но заметьте, что мы сделали. Мы просто выполнили обход ленивого списка в порядке! Итак, первый раз наш мнимый LazyTree.contains имел бы примерно такую ​​же сложность, как List.find (предполагая, что вложенная вставка O (1), которая является плохим предположением для простого двоичного дерева, которая имела бы сложность O (log n)). И без балансировки наше дерево было бы очень однобоким (мы добавили цифры от 1 до 100 по порядку, так что это будет просто связанный список с правильным дочерним элементом каждой ветки). Но с балансировкой дерева во время обхода, было бы трудно узнать, с чего начать обход снова; по крайней мере, это, конечно, не будет сразу интуитивным.

tl; dr: nobody (afaik) сделал хороший ленивый Set еще. Таким образом, бесконечные наборы более легко представлены как бесконечные списки.

Ответ 2

Set.fromList не ленив, поэтому он не закончится, если вы передадите ему бесконечный список. Но natSet не создается до тех пор, пока он не понадобится, поэтому вы заметите это только при запуске Set.member на нем.

Например, даже Set.null $ Set.fromList [0..] не заканчивается.

Ответ 3

У вас не может быть бесконечных множеств. Это не влияет только на Set.member, когда вы делаете что-либо, что приведет к оценке natSet даже одного шага (даже Set.null), он перейдет в бесконечный цикл.

Ответ 4

Посмотрим, что произойдет, когда мы адаптируем GHC Set code для размещения бесконечных множеств:

module InfSet where

data InfSet a = Bin a (InfSet a) (InfSet a) 

-- create an infinite set by unfolding a value
ofUnfold :: (x -> (x, a, x)) -> x -> InfSet a
ofUnfold f x = 
  let (lx,a,rx) = f x
      l = ofUnfold f lx
      r = ofUnfold f rx
  in Bin a l r

-- check for membership in the infinite set
member :: Ord a => a -> InfSet a -> Bool
member x (Bin y l r) = case compare x y of
                         LT -> member x l
                         GT -> member x r
                         EQ -> True       

-- construct an infinite set representing a range of numbers
range :: Fractional a => (a, a) -> InfSet a
range = ofUnfold $ \(lo,hi) -> 
          let mid = (hi+lo) / 2
          in ( (lo, mid), mid, (mid, hi) )

Обратите внимание, что вместо построения бесконечного множества из бесконечного списка, Вместо этого я определяю функцию ofUnfold, чтобы развернуть одно значение в бесконечный список. Это позволяет нам лениво строить параллельные ветки (нам не нужно заканчивать одна ветвь перед построением другой).

Дайте ему вихрь:

ghci> :l InfSet
[1 of 1] Compiling InfSet           ( InfSet.hs, interpreted )
Ok, modules loaded: InfSet.
ghci> let r = range (0,128)
ghci> member 64 r
True
ghci> member 63 r
True
ghci> member 62 r
True
ghci> member (1/2) r
True
ghci> member (3/4) r
True

Хорошо, это работает. Что делать, если мы попробуем значение вне Set?

ghci> member 129 r
^CInterrupted.

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

ghci> let ex = ofUnfold (\f -> ( f . (LT:), f [EQ], f . (GT:) )) id
ghci> :t ex
ex :: InfSet [Ordering]
ghci> member [EQ] ex
True
ghci> member [LT,EQ] ex
True
ghci> member [EQ,LT] ex
^CInterrupted.

Таким образом, возможны бесконечные множества, но я не уверен, что они полезны.

Ответ 5

Я чувствовал то же самое, поэтому добавил набор, который работает с бесконечными списками. Однако их нужно сортировать, поэтому мой алгоритм знает, когда перестать искать больше.

Prelude> import Data.Set.Lazy
Prelude Data.Set.Lazy> natset = fromList [1..]
Prelude Data.Set.Lazy> 100 `member` natset
True
Prelude Data.Set.Lazy> (-10) `member` natset
False

Его на взломе. http://hackage.haskell.org/package/lazyset-0.1.0.0/docs/Data-Set-Lazy.html