Причина, по которой 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)
5
mudri 13 Июн 2014 в 15:33
8
Я бы сказал, что пример Майкла недействителен. == должно свидетельствовать о "эквивалентности всеми наблюдаемыми средствами" (это не обязательно требует a == b должно выполняться только для идентичных реализаций; но все, что вы можете «официально» сделать с a и b, снова должно давать эквивалентные результаты. Таким образом, unAlwaysEq никогда не следует раскрывать в первую очередь). Если вы не можете гарантировать это для какого-либо типа, вам не следует давать ему экземпляр Eq.
 – 
leftaroundabout
13 Июн 2014 в 15:41
1
Если вы определяете экземпляр, который не имеет смысла, вы должны ожидать, что код, который вы пишете, будет иметь странное поведение. Set не является функтором, потому что выходной тип Set.map требует ограничения Ord. Экземпляр instance Functor (Set a) where ... даже недействителен.
 – 
user2407038
13 Июн 2014 в 15:53
1
В Scala существует fmap для Set, и это много раз кусало меня (и коллег).
 – 
ziggystar
13 Июн 2014 в 16:31
Как ваше первое свойство может быть истинным для любого интересного типа? Int определенно не может быть экземпляром StrongEq, потому что существует f :: Int -> Int, где a === b && not (f a === f b).
 – 
Thomas M. DuBuisson
13 Июн 2014 в 17:00
2
Проблема всегда заключалась в отображении Set таким образом, что два разных элемента отображались как равные; но намерение состояло в том, чтобы сохранить дубликаты. Например. у вас есть набор объектов и вы хотите просуммировать их стоимость. Если вы сопоставляете объекты с их стоимостью, а затем суммируете, если вы получаете набор затрат между ними, вы подсчитываете объекты с одинаковой стоимостью только один раз.
 – 
ziggystar
14 Июн 2014 в 17:11

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. Я никогда не играл с подобным кодом, поэтому я знаю только то, что этот метод существует. Это не поможет вам передать наборы существующему коду, который нуждается в функторах, но вы должны иметь возможность использовать его вместо функтора в своем собственном коде, если хотите.

5
Ben 15 Июн 2014 в 06:44
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, если быть точным) является функтором, с этим нет проблем.

15
n. m. 13 Июн 2014 в 16:08
Хорошо, я понял первую часть. Мне нужно 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) не существует (или не существует функция, которая его создает). Это по дизайну.
 – 
mudri
14 Июн 2014 в 15:20
goedel не вызывает ошибки типа. Это совершенно нормальная функция. У вас будет ошибка типа, если вы попытаетесь определить fmap для instance Functor StrongSet.
 – 
n. m.
14 Июн 2014 в 15:45
Ах, значит, функтор должен содержать любой тип. Думаю, это имеет смысл. Является ли это стандартной вещью в функторах теории категорий?
 – 
mudri
15 Июн 2014 в 00:27
@JamesWood, я не теоретик категорий, но сомневаюсь в этом. Вероятно, вы имели дело с чем-то вроде функтора, домен которого является неполной подкатегорией Hask.
 – 
dfeuer
15 Июн 2014 в 08:55

Поскольку я не теоретик категорий, я постараюсь написать более конкретное / практическое объяснение (то есть такое, которое я могу понять):

Ключевым моментом является тот, который @leftaroundabout сделал в комментарии:

== должен свидетель "эквивалентен всеми наблюдаемыми средствами" (что не обязательно require a == 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, который отличает вещи, которые мы хотим считать одинаковыми (вне реализации установленных операций).

Важно убедиться, что типы являются экземплярами EqOrd) только тогда, когда это необходимо! Очень заманчиво сделать что-то экземпляром 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 - это способ сделать очень сильное заявление миру; не делайте этого заявления, если вы этого не имеете в виду.

6
dfeuer 13 Июн 2014 в 20:04

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

В частности, типичная математика любит говорить об объектах так, как будто они существуют в наборе и могут быть однозначно идентифицированы. Компьютерные программы обычно имеют дело с типами, которые не всегда вычислимы (в качестве простого контрпримера скажите мне, что такое набор, соответствующий типу data U = U (U -> U)). Это означает, что может быть непонятно, можно ли идентифицировать два значения.

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

Итак, StrongEq может быть определен в ограниченной части Haskell, содержащей только типы, которые могут быть решаемо сравнены на равенство. Мы можем рассматривать эту категорию со стрелками как вычислимые функции, а затем рассматривать Set как эндофунктор от типов к типу наборов значений этого типа. К сожалению, эти ограничения уводят нас далеко от стандартного Haskell и делают определение StrongEq или Functor (Set a) немного менее практичным.

2
J. Abrahamson 13 Июн 2014 в 17:52