Причина, по которой Set
не является функтором, указана здесь. Похоже, все сводится к тому, что a == b && f a /= f b
возможно. Итак, почему в Haskell нет стандартной альтернативы Eq, что-то вроде
class Eq a => StrongEq a where
(===) :: a -> a -> Bool
(/==) :: a -> a -> Bool
x /== y = not (x === y)
x === y = not (x /== y)
В каких случаях предполагается подчиняться законам
∀a,b,f. not (a === b) || (f a === f b)
∀a. a === a
∀a,b. (a === b) == (b === a)
А может еще какие-то? Тогда мы могли бы иметь:
instance StrongEq a => Functor (Set a) where
-- ...
Или я что-то упускаю?
Изменить : моя проблема не в том, «Почему существуют типы без экземпляра Eq
?», как некоторые из вас, кажется, ответили. Все наоборот: «Почему существуют экземпляры Eq
, которые не равны по расширению? Почему существует слишком много экземпляров Eq
? »В сочетании со словами« Если a == b
действительно подразумевает экстенсиональное равенство, почему Set
не является экземпляром Functor
? ».
Кроме того, объявление моего экземпляра - мусор (спасибо @ n.m.). Я должен был сказать:
newtype StrongSet a = StrongSet (Set a)
instance Functor StrongSet where
fmap :: (StrongEq a, StrongEq b) => (a -> b) -> StrongSet a -> StrongSet b
fmap (StrongSet s) = StrongSet (map s)
4 ответа
Ваш второй экземпляр Functor
тоже не имеет смысла. Самая большая причина, по которой Set
не может быть Functor
в Haskell, - это то, что fmap
не может иметь ограничений. Изобретение различных понятий равенства как StrongEq
не меняет того факта, что вы не можете записать эти ограничения на fmap
в своем экземпляре Set.
fmap
вообще не должен иметь необходимых вам ограничений. Имеет смысл, например, иметь функторы функций (без него не работает вся идея использования Applicative для применения функций внутри функтора), а функции не могут быть членами Eq или вашего StrongEq в целом.
fmap
не может иметь дополнительных ограничений только для некоторых экземпляров из-за такого кода:
fmapBoth :: (Functor f, Functor g) => (a -> b, c -> d) -> (f a, g c) -> (f b, g d)
fmapBoth (h, j) (x, y) = (fmap h x, fmap j y)
Этот код утверждает, что работает независимо от функторов f
и g
и независимо от функций h
и j
. У него нет способа проверить, является ли один из функторов специальным, имеющим дополнительные ограничения на fmap
, или нет способа проверить, нарушит ли одна из функций, которые он применяет, эти ограничения.
Сказать, что Set является функтором в Haskell, означает, что существует (законная) операция fmap :: (a -> b) -> Set a -> Set b
с этим точным типом. Это именно то, что означает Functor
. fmap :: (Eq a -> Eq b) => (a -> b) -> Set a -> Set b
не является примером такой операции.
Насколько я понимаю, можно использовать ConstraintKinds Расширение GHC для написания другого класса Functor, допускающего ограничения на значения, которые зависят от Functor (на самом деле вам нужно ограничение Ord
, а не просто Eq
). В этом сообщении блога рассказывается о том, как сделать новую монаду класс, который может иметь экземпляр для Set. Я никогда не играл с подобным кодом, поэтому я знаю только то, что этот метод существует. Это не поможет вам передать наборы существующему коду, который нуждается в функторах, но вы должны иметь возможность использовать его вместо функтора в своем собственном коде, если хотите.
instance StrongEq a => Functor (Set a) where
Это не имеет смысла ни в Haskell, ни в большой математической / категориальной схеме вещей, независимо от того, что означает StrongEq
.
В Haskell для Functor
требуется конструктор типа типа * -> *
. Стрелка отражает тот факт, что в теории категорий функтор - это своего рода отображение. []
и (гипотетический) Set
являются такими конструкторами типов. [a]
и Set a
имеют вид *
и не могут быть функторами.
В Haskell трудно определить Set
так, чтобы его можно было превратить в Functor
, потому что равенство не может быть разумно определено для некоторых типов, несмотря ни на что. Например, нельзя сравнивать две вещи типа Integer->Integer
.
Предположим, есть функция
goedel :: Integer -> Integer -> Integer
goedel x y = -- compute the result of a function with
-- Goedel number x, applied to y
Предположим, у вас есть значение s :: Set Integer
. Как должен выглядеть fmap goedel s
? Как убрать дубликаты?
В вашей типичной теории множеств равенство магическим образом определяется для всего, включая функции, поэтому Set
(или Powerset
, если быть точным) является функтором, с этим нет проблем.
newtype StrongEq a => StrongSet a = StrongSet (Set a)
, затем instance Functor StrongSet where ---
. Но разве ваш goedel
в любом случае не вызовет ошибку типа? fmap :: (a -> b) -> f a -> f b
, но там, где (a -> b) = (Integer -> (Integer -> Integer))
и f = Set
, f b
, а именно Set (Integer -> Integer)
не существует (или не существует функция, которая его создает). Это по дизайну.
goedel
не вызывает ошибки типа. Это совершенно нормальная функция. У вас будет ошибка типа, если вы попытаетесь определить fmap
для instance Functor StrongSet
.
Поскольку я не теоретик категорий, я постараюсь написать более конкретное / практическое объяснение (то есть такое, которое я могу понять):
Ключевым моментом является тот, который @leftaroundabout сделал в комментарии:
==
должен свидетель "эквивалентен всеми наблюдаемыми средствами" (что не обязательно requirea == b
должен выполняться только для идентичных реализаций; но все, что вы можете "официально" сделать с a и b, должно снова дать эквивалентные результаты. Так чтоunAlwaysEq
никогда не следует раскрывать в первом место). Если вы не можете гарантировать это для какого-то типа, вы не должны его экземплярEq
.
То есть в вашем StrongEq
не должно быть необходимости, потому что это то, чем уже должен быть Eq
.
Значения Haskell часто предназначены для представления некоторого математического или «реального» значения. Часто это представление однозначно. Например, рассмотрим тип
data PlatonicSolid = Tetrahedron | Cube |
Octahedron | Dodecahedron | Icosahedron
Этот тип содержит ровно одно представление каждого Платонового тела. Мы можем воспользоваться этим, добавив deriving Eq
в объявление, и оно создаст правильный экземпляр.
Однако во многих случаях одно и то же абстрактное значение может быть представлено более чем одним значением Haskell. Например, красно-черные деревья Node B (Node R Leaf 1 Leaf) 2 Leaf
и Node B Leaf 1 (Node R Leaf 2 Leaf)
могут представлять набор {1,2}. Если мы добавим deriving Eq
в наше объявление, мы получим экземпляр Eq
, который отличает вещи, которые мы хотим считать одинаковыми (вне реализации установленных операций).
Важно убедиться, что типы являются экземплярами Eq
(и Ord
) только тогда, когда это необходимо! Очень заманчиво сделать что-то экземпляром Ord
, чтобы вы могли вставить его в структуру данных, требующую упорядочивания, но если этот порядок не является на самом деле полным упорядочением абстрактных значений, возможны любые поломки. Если, например, документация не гарантирует это абсолютно, функция с именем sort :: Ord a => [a] -> [a]
может не только быть нестабильной сортировкой, но даже не может создавать список, содержащий все входящие в нее значения Haskell. sort [Bad 1 "Bob", Bad 1 "James"]
может разумно производить [Bad 1 "Bob", Bad 1 "James"]
, [Bad 1 "James", Bad 1 "Bob"]
, [Bad 1 "James", Bad 1 "James"]
или [Bad 1 "Bob", Bad 1 "Bob"]
. Все это совершенно законно. Функция, которая использует unsafePerformIO
в подсобке для реализации рандомизированного алгоритма в стиле Лас-Вегаса или для конкуренции потоков друг с другом, чтобы получить ответ от самого быстрого, может даже давать разные результаты в разное время, если они ==
друг к другу.
Tl; dr: Создание чего-либо экземпляра Eq
- это способ сделать очень сильное заявление миру; не делайте этого заявления, если вы этого не имеете в виду.
Это понятие StrongEq
сложно. В общем, равенство - это место, где информатика становится значительно более строгой, чем обычная математика, что усложняет задачу.
В частности, типичная математика любит говорить об объектах так, как будто они существуют в наборе и могут быть однозначно идентифицированы. Компьютерные программы обычно имеют дело с типами, которые не всегда вычислимы (в качестве простого контрпримера скажите мне, что такое набор, соответствующий типу data U = U (U -> U)
). Это означает, что может быть непонятно, можно ли идентифицировать два значения.
Это становится огромной темой для языков с зависимой типизацией, так как проверка типов требует идентификации похожих типов, а языки с зависимой типизацией могут иметь произвольные значения в своих типах и, следовательно, нуждаются в способе проецирования равенства.
Итак, StrongEq
может быть определен в ограниченной части Haskell, содержащей только типы, которые могут быть решаемо сравнены на равенство. Мы можем рассматривать эту категорию со стрелками как вычислимые функции, а затем рассматривать Set
как эндофунктор от типов к типу наборов значений этого типа. К сожалению, эти ограничения уводят нас далеко от стандартного Haskell и делают определение StrongEq
или Functor (Set a)
немного менее практичным.
Похожие вопросы
Связанные вопросы
Новые вопросы
haskell
Haskell — это чисто функциональный язык программирования со строгой статической типизацией, отложенными вычислениями, обширной поддержкой параллелизма и параллелизма, а также уникальными возможностями абстракции.
==
должно свидетельствовать о "эквивалентности всеми наблюдаемыми средствами" (это не обязательно требуетa == b
должно выполняться только для идентичных реализаций; но все, что вы можете «официально» сделать сa
иb
, снова должно давать эквивалентные результаты. Таким образом,unAlwaysEq
никогда не следует раскрывать в первую очередь). Если вы не можете гарантировать это для какого-либо типа, вам не следует давать ему экземплярEq
.Set
не является функтором, потому что выходной типSet.map
требует ограниченияOrd
. Экземплярinstance Functor (Set a) where ...
даже недействителен.fmap
дляSet
, и это много раз кусало меня (и коллег).Int
определенно не может быть экземпляромStrongEq
, потому что существуетf :: Int -> Int
, гдеa === b && not (f a === f b)
.Set
таким образом, что два разных элемента отображались как равные; но намерение состояло в том, чтобы сохранить дубликаты. Например. у вас есть набор объектов и вы хотите просуммировать их стоимость. Если вы сопоставляете объекты с их стоимостью, а затем суммируете, если вы получаете набор затрат между ними, вы подсчитываете объекты с одинаковой стоимостью только один раз.