Есть ли способ разделить бесконечные и конечные списки?

Например, я пишу функцию для списков и хочу использовать функцию длины.

foo :: [a] -> Bool
foo xs = length xs == 100

Как кто-то может понять, можно ли использовать эту функцию с бесконечными списками или нет?

Или я всегда должен думать о бесконечных списках и использовать что-то вроде этого

foo :: [a] -> Bool
foo xs = length (take 101 xs) == 100

вместо прямого использования длины?

Что, если бы haskell имел бы тип FiniteList, поэтому длина и foo были бы

length :: FiniteList a -> Int
foo :: FiniteList a -> Bool

person ais    schedule 08.10.2015    source источник


Ответы (5)


length просматривает весь список, но чтобы определить, имеет ли список определенную длину n, вам нужно просмотреть только первые n элементы.

Ваша идея использовать take будет работать. В качестве альтернативы вы можете написать функцию lengthIs следующим образом:

-- assume n >= 0
lengthIs 0 [] = True
lengthIs 0 _  = False
lengthIs n [] = False
lengthIs n (x:xs) = lengthIs (n-1) xs

Вы можете использовать ту же идею для написания вариантов lengthIsAtLeast и lengthIsAtMost.

person ErikR    schedule 08.10.2015
comment
Это просто странно. Зачем вообще существует функция длины, если вы не можете просто использовать ее? - person ais; 08.10.2015
comment
Что бы вы хотели, чтобы length из бесконечного списка возвращал? - person ErikR; 08.10.2015
comment
Я бы предпочел, чтобы длина не принимала бесконечные списки и имела тип что-то вроде FiniteList a -> Int - person ais; 08.10.2015
comment
ais: вас может заинтересовать это обсуждение данных и кодовых данных. Используя эту терминологию, length должен принимать только данные, но списки потенциально являются кодовыми данными. - person rampion; 08.10.2015
comment
Вы можете определить тип FiniteList в Haskell — например, Vector a может хорошо подойти для ваших вариантов использования. Действительно, списки, которые вы видите в таких языках, как Python, Perl, Ruby и т. д., действительно лучше всего моделируются Vector a в Haskell. Списки могут быть бесконечными, они очень полезны в Haskell, и по мере того, как вы узнаете больше о Haskell, вы поймете, когда списки уместны, а когда вам следует использовать другую структуру данных. - person ErikR; 08.10.2015
comment
какую длину бесконечного списка вы бы хотели вернуть? — бесконечное количество. - person user3237465; 08.10.2015
comment
@ user3237465, это эффект ленивого подхода Nat. Но length производит Int, а бесконечного Int не бывает. Не бывает и бесконечного натурального числа, но лень эффективно примыкает к бесконечности, S $ S $ S $ ... к натуральным числам. - person dfeuer; 08.10.2015
comment
@dfeuer, я имел в виду длину — более общую вещь, чем length. Существует бесконечное конатуральное число, и в Haskell NatConat. Я не предлагаю использовать Nat везде, но бесконечный список, который имеет бесконечную длину, является для меня совершенно разумной конструкцией. - person user3237465; 08.10.2015
comment
@user3237465 user3237465, ну, вы могли бы сделать индуктивные натуральные, если хотите: data Nat' = Z' | S' !Nat'. - person dfeuer; 08.10.2015

О редактировании: я в первую очередь отвечаю на вопрос в вашем заголовке, а не на особенности вашего конкретного примера (для которого ответ ErikR превосходен).

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

takeWhile f [1..]

(где f — предикат целых чисел) создает конечный или бесконечный список.

person John Coleman    schedule 08.10.2015
comment
Хорошая точка зрения! Но есть способ разделить заведомо конечные и, возможно, бесконечные списки. - person rampion; 08.10.2015

Nats и снова удар лени:

import Data.List

data Nat = S Nat | Z deriving (Eq)

instance Num Nat where
    fromInteger 0 = Z
    fromInteger n = S (fromInteger (n - 1))

    Z   + m = m
    S n + m = S (n + m)

lazyLength :: [a] -> Nat
lazyLength = genericLength

main = do
    print $ lazyLength [1..]    == 100 -- False
    print $ lazyLength [1..100] == 100 -- True
person user3237465    schedule 08.10.2015
comment
насколько это эффективно? ;) - person Michael; 08.10.2015
comment
@Michael, думаю, медленнее на небольшой постоянный коэффициент, чем прямая реализация. Но вам не нужно писать lengthIsAtLeast, lengthIsAtMost и другие причудливые функции — модуль Nat даст их бесплатно. Но, насколько мне известно, такого модуля нет, поэтому, наверное, лучше просто использовать lengthIs. - person user3237465; 08.10.2015
comment
ну, используя 5000000 вместо 100, у меня есть 1,291 с для реализации Nat и 0,185 с для наивной реализации isLength на моем компьютере. Оба скомпилированы с ghc -O2. Это коэффициент 6,9... коэффициент останется прежним, если я увеличу максимальное значение до 10000000. - person Michael; 08.10.2015
comment
@ Майкл, определяя lazyLength напрямую как lazyLength = foldr (\_ -> S) Z, дает мне 4.96s вместо lengthIs 100000000 и 5.74 вместо lazyLength [1..100000000] == 100000000. - person user3237465; 08.10.2015
comment
genericLength реализовано строго или что-то? или почему это медленнее, чем эквивалентная реализация foldr? - person Erik Kaplun; 09.10.2015
comment
Для меня имеет смысл, что реализация genericLength намного медленнее, поскольку она включает преобразование между цифрами и Nat. Кроме того, оценка 1 + x сводится к S (Z + x). Исключение Z в этом выражении — это еще один шаг. - person is7s; 09.10.2015

ErikR и John Coleman уже ответили на основные части вашего вопроса, однако я хотел бы отметить еще кое-что:

Лучше всего писать свои функции таким образом, чтобы они просто не зависели от конечности или бесконечности своих входных данных — иногда это невозможно, но в большинстве случаев это просто вопрос редизайна. Например, вместо вычисления среднего значения всего списка вы можете вычислить скользящее среднее значение, которое само по себе является списком; и сам этот список будет бесконечным, если входной список бесконечен, и конечным в противном случае.

avg :: [Double] -> [Double]
avg = drop 1 . scanl f 0.0 . zip [0..]
  where f avg (n, i) = avg * (dbl n / dbl n') +
                       i            / dbl n'      where n'  = n+1
                                                        dbl = fromInteger

в этом случае вы можете усреднять бесконечный список, не беря его length:

*Main> take 10 $ avg [1..]
[1.0,1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0]

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

Таким образом, они также будут более пригодными для повторного использования и компоновки — все, что имеет меньше или более общих предположений о входных данных, имеет тенденцию быть более компонуемым; и наоборот, что-либо с более или более конкретными предположениями, как правило, менее компонуемо и, следовательно, менее пригодно для повторного использования.

person Erik Kaplun    schedule 08.10.2015
comment
Если ваши функции не зависят от конечности, то такие функции, как длина, бесполезны. Вы можете использовать их, но потом понимаете, что вам нужно переписать весь код. - person ais; 08.10.2015
comment
Я имел в виду, что вы можете реконструировать свой код таким образом, чтобы он не зависел от length — конечно, до некоторой степени, но если вы сможете это сделать, это сделает вашу код более гибкий, а функции более независимы от некоторых аспектов их ввода. - person Erik Kaplun; 08.10.2015
comment
Мой код прост, я хочу знать, равна ли длина списка 100. Поэтому я написал length xs == 100, а затем понял, что не могу этого сделать из-за бесконечных списков. - person ais; 08.10.2015
comment
Мой совет был общим. Возможно, ваш текущий проект — просто игрушка для обучения, а length xs == 100 — ваша конечная цель, но в будущем это может измениться. - person Erik Kaplun; 08.10.2015

Существует несколько различных способов создания конечного списка. Первый — просто сделать списки строгими в своих корешках:

data FList a = Nil | Cons a !(FList a)

К сожалению, это сводит на нет все преимущества эффективности лени. Некоторые из них можно восстановить, используя вместо этого списки с индексом длины:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}

data Nat = Z | S Nat deriving (Show, Read, Eq, Ord)

data Vec :: Nat -> * -> * where
  Nil :: Vec 'Z a
  Cons :: a -> Vec n a -> Vec ('S n) a

instance Functor (Vec n) where
  fmap _f Nil = Nil
  fmap f (Cons x xs) = Cons (f x) (fmap f xs)

data FList :: * -> * where
  FList :: Vec n a -> FList a

instance Functor FList where
  fmap f (FList xs) = FList (fmap f xs)

fcons :: a -> FList a -> FList a
fcons x (FList xs) = FList (Cons x xs)

funcons :: FList a -> Maybe (a, FList a)
funcons (FList Nil) = Nothing
funcons (FList (Cons x xs)) = Just (x, FList xs)

-- Foldable and Traversable instances are straightforward
-- as well, and in recent GHC versions, Foldable brings
-- along a definition of length.

GHC не допускает бесконечных типов, поэтому невозможно построить бесконечный Vec и, следовательно, невозможно создать бесконечный FList (1). Однако FList можно преобразовывать и потреблять несколько лениво, что влечет за собой преимущества кэширования и сборки мусора.

(1) Обратите внимание, что система типов заставляет fcons быть строгим в аргументе FList, поэтому любая попытка завязать узел с FList обречена на провал.

person dfeuer    schedule 09.10.2015