Подтвердить что ты не робот

Почему длина возвращает 1 для кортежа с 2 элементами и дает ошибку для кортежа с большим количеством элементов?

Я изучаю Haskell, используя книгу " Haskell Programming from First Principles", и в конце главы 4, "Основные типы данных", Я столкнулся с чем-то, что смутило меня. В книге упоминается функция length и говорится, что она работает на Lists s. Все в порядке с этим, но когда я пытаюсь использовать эту функцию length с различными Tuple s, то, что я вижу, смущает меня:

Сначала посмотрим тип length:

:t length
length :: Foldable t => t a -> Int

ОК, поэтому я прочитал выше как "берет Складную, которая, по моему мнению, является списком для удобства, и возвращает Int, то есть количество элементов в списке". Отсюда моя первая путаница: зачем вообще работает следующая работа:

length (1, 1)
1

Потому что мне кажется, что я только что передал кортеж с двумя элементами в length, и он вернул 1. Является ли кортеж списком? Является ли кортеж Складным? И, конечно, почему 1?

Теперь я делаю еще один шаг:

length (1, 1, 1)

<interactive>:6:1:
    No instance for (Foldable ((,,) t0 t1))
      arising from a use of ‘length’
    In the expression: length (1, 1, 1)
    In an equation for ‘it’: it = length (1, 1, 1)

<interactive>:6:9:
    No instance for (Num t0) arising from the literal ‘1’
    The type variable ‘t0’ is ambiguous
    Note: there are several potential instances:
      instance Num Integer -- Defined in ‘GHC.Num’
      instance Num Double -- Defined in ‘GHC.Float’
      instance Num Float -- Defined in ‘GHC.Float’
      ...plus two others
    In the expression: 1
    In the first argument of ‘length’, namely ‘(1, 1, 1)’
    In the expression: length (1, 1, 1)

<interactive>:6:12:
    No instance for (Num t1) arising from the literal ‘1’
    The type variable ‘t1’ is ambiguous
    Note: there are several potential instances:
      instance Num Integer -- Defined in ‘GHC.Num’
      instance Num Double -- Defined in ‘GHC.Float’
      instance Num Float -- Defined in ‘GHC.Float’
      ...plus two others
    In the expression: 1
    In the first argument of ‘length’, namely ‘(1, 1, 1)’
    In the expression: length (1, 1, 1)

Еще одна попытка:

length (1::Int, 1::Int, 1::Int)

<interactive>:7:1:
    No instance for (Foldable ((,,) Int Int))
      arising from a use of ‘length’
    In the expression: length (1 :: Int, 1 :: Int, 1 :: Int)
    In an equation for ‘it’: it = length (1 :: Int, 1 :: Int, 1 :: Int)

Но следующие работы:

length (1::Int, 1::Int)
1

Есть ли хорошее объяснение поведения, которое я наблюдаю выше? Я неправильно читаю тип length? Или что-то происходит за кулисами?

4b9b3361

Ответ 1

Вы столкнулись с причиной Haskell célèbre, которая вызвала много дискуссий и скрежетов зубов.

В принципе, для Foldable (класс стилей, который предоставляет length), 2-кортежи не считаются контейнером из двух элементов, но контейнер одного элемента, сопровождаемый некоторым контекстом.

Вы можете извлечь список элементов типа a из любого Foldable a. Обратите внимание, что для 2-х кортежей переменная типа Foldable является переменной второго элемента кортежа и может отличаться от типа первого элемента.

Если у вас был кортеж ('c',2) :: (Char,Int), было бы не секрет, что вы не смогли бы извлечь два Int в этом случае! Но когда типы равны, он становится запутанным.

Что касается того, почему length (1::Int, 1::Int, 1::Int) не работает, 3-кортежи не имеют определенного экземпляра Foldable, но, возможно, они должны иметь один для согласованности. 3-кортежи также имели бы длину 1.

Кстати, функтор Identity, который можно было считать одним из 1-кортежей, также имеет значение Foldable и, конечно, имеет длину 1.

Если экземпляр Foldable для кортежей существует вообще? Я думаю, что основополагающая философия в пользу "да" - это одна из, мы будем называть ее "полнотой". Если тип может быть создан экземпляром класса в корректном, законном виде, он должен иметь этот экземпляр. Даже если это не очень полезно и в некоторых случаях может быть запутанным.

Ответ 2

Мне нравится ответ danidiaz, потому что он обеспечивает интуицию высокого уровня о том, как работает экземпляр Foldable для кортежей и что он интуитивно означает. Однако, похоже, все еще есть путаница в механизме его; поэтому в этом ответе я сосредоточусь на "закулисных" битах. Полный текст рассматриваемого экземпляра Foldable доступен в Интернете и выглядит следующим образом:

instance Foldable ((,) a) where
    foldMap f (_, y) = f y
    foldr f z (_, y) = f y z

Вы уже можете видеть из этого экземпляра, что первая часть каждого кортежа полностью игнорируется во всех методах Foldable. Однако, чтобы завершить изображение, нам нужно посмотреть определения для minimum и length. Поскольку этот экземпляр не включает определения для minimum и length, мы должны посмотреть на определения по умолчанию для них. Объявление класса для Foldable выглядит так (с несоответствующими битами):

class Foldable t where
    length :: t a -> Int
    length = foldl' (\c _ -> c+1) 0

    foldl' :: (b -> a -> b) -> b -> t a -> b
    foldl' f z0 xs = foldr f' id xs z0
      where f' x k z = k $! f z x

    minimum :: forall a . Ord a => t a -> a
    minimum = fromMaybe (error "minimum: empty structure") .
       getMin . foldMap (Min #. (Just :: a -> Maybe a))

Итак, давайте разложим эти определения и посмотрим, откуда они.

length (a, b)
= { definition of length }
foldl' (\c _ -> c+1) 0 (a, b)
= { definition of foldl' }
foldr (\x k z -> k $! (\c _ -> c+1) z x) id (a, b) 0
= { definition of foldr }
(\x k z -> k $! (\c _ -> c+1) z x) b id 0
= { beta reduction }
id $! (\c _ -> c+1) 0 b
= { id $! e = e }
(\c _ -> c+1) 0 b
= { beta reduction }
1

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

minimum (a, b)
= { definition of minimum }
( fromMaybe (error "minimum: empty structure")
. getMin
. foldMap (Min . Just)
) (a, b)
= { definition of (.) }
( fromMaybe (error "minimum: empty structure")
. getMin
) (foldMap (Min . Just) (a, b))
= { definition of foldMap }
( fromMaybe (error "minimum: empty structure")
. getMin
) ((Min . Just) b)
= { definition of (.) }
fromMaybe (error "minimum: empty structure")
(getMin (Min (Just b)))
= { definition of getMin }
fromMaybe (error "minimum: empty structure") (Just b)
= { definition of fromMaybe }
b