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

Наборы, функторы и путаница Eq

Недавно появилось обсуждение о Sets, которое в Scala поддерживает метод zip и как это может привести к ошибкам, например.

scala> val words = Set("one", "two", "three")
scala> words zip (words map (_.length))
res1: Set[(java.lang.String, Int)] = Set((one,3), (two,5))

Я думаю, что довольно ясно, что Set не должен поддерживать операцию zip, так как элементы не упорядочены. Однако было высказано предположение, что проблема заключается в том, что Set на самом деле не является функтором и не должен иметь метод map. Конечно, вы можете столкнуться с проблемами, сопоставив набор. Теперь переключитесь на Haskell,

data AlwaysEqual a = Wrap { unWrap :: a }

instance Eq (AlwaysEqual a) where
    _ == _ = True

instance Ord (AlwaysEqual a) where
    compare _ _ = EQ

и теперь в ghci

ghci> import Data.Set as Set
ghci> let nums = Set.fromList [1, 2, 3]
ghci> Set.map unWrap $ Set.map Wrap $ nums
fromList [3]
ghci> Set.map (unWrap . Wrap) nums
fromList [1, 2, 3]

So Set не удовлетворяет закону функтора

    fmap f . fmap g = fmap (f . g)

Можно утверждать, что это не является провалом операции map на Set s, а ошибкой экземпляра Eq, который мы определили, поскольку он не соблюдает закон замены, а именно: для двух экземпляров Eq на A и B и отображения f : A -> B, тогда

    if x == y (on A) then f x == f y (on B)

который не выполняется для AlwaysEqual (например, рассмотрим f = unWrap).

Является ли закон подзарядки разумным законом для типа Eq, который мы должны стараться уважать? Разумеется, другие законы равенства соблюдаются нашим типом AlwaysEqual (симметрия, транзитивность и рефлексивность тривиально выполнены), поэтому замена - это единственное место, в которое мы можем попасть.

Для меня подстановка кажется очень желательным свойством для класса Eq. С другой стороны, некоторые комментарии к недавнему обсуждению Reddit включают

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

- godofpumpkins

"Я также действительно не хочу подстановки/конгруэнтности, так как существует много законных применений для значений, которые мы хотим приравнивать, но как-то различимы".

- sclv

"Замещение выполняется только для структурного равенства, но ничто не настаивает на том, что Eq является структурным".

- edwardkmett

Эти три очень хорошо известны в сообществе Haskell, поэтому я не решаюсь пойти против них и настаивать на подстановке для моих типов Eq!

Другим аргументом против Set является Functor - широко признано, что если Functor позволяет вам преобразовывать "элементы" "коллекции" при сохранении формы. Например, эта цитата в вики Haskell (обратите внимание, что Traversable является обобщением Functor)

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

"Traversable заключается в сохранении структуры точно как-есть."

и в реальном мире Haskell

"... Функтор [A] должен сохранять форму. На структуру коллекции не должен влиять функтор, а только значения, которые он содержит, должны меняться."

Очевидно, что любой экземпляр функтора для Set имеет возможность изменить форму, уменьшив количество элементов в наборе.

Но похоже, что Set действительно должны быть функторами (игнорируя требование Ord на данный момент), я вижу это как искусственное ограничение, налагаемое нашим желанием эффективно работать с множествами, а не абсолютное требование для любого набора Например, множество функций - это совершенно разумная вещь. В любом случае Олег показал, как писать эффективные экземпляры Functor и Monad для Set, для которого не требуется ограничение Ord). Слишком много приятных применений для них (то же самое верно и для несуществующего экземпляра Monad).

Может ли кто-нибудь прояснить этот беспорядок? Должен ли Set быть Functor? Если да, то что вы делаете о возможности нарушения законов Functor? Какими должны быть законы для Eq и как они взаимодействуют с законами для Functor и экземпляра Set в частности?

4b9b3361

Ответ 1

Другим аргументом против Set является Functor - широко признано, что если Functor позволяет вам преобразовать "элементы" "коллекции" при сохранении формы. [...] Очевидно, что любой экземпляр functor для Set имеет возможность изменять форму, уменьшая количество элементов в наборе.

Я боюсь, что это случай, когда аналог "формы" является определяющим, когда это не так. Математически говоря, существует такая функция, как функтор множества мощности. Из Википедии:

Power sets: Функтор набора мощности P: Set → Set отображает каждый набор в свой набор мощности и каждую функцию f: X → Y на карту, которая посылает U ⊆ X к его изображению f (U) ⊆ Y.

Функция P (f) (fmap f в функторе набора мощности) не сохраняет размер его аргумента, но тем не менее это функтор.

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

(Хорошо, я закрою сейчас...)


EDIT: Я усекал следующие биты, когда я процитировал вас в верхней части моего ответа:

Например, эта цитата на вики Haskell (обратите внимание, что Traversable является обобщением Functor)

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

     

"Traversable заключается в сохранении структуры точно как-есть."

Здесь я бы заметил, что Traversable является своего рода специализированным Functor, а не "обобщением" его. Один из ключевых фактов о любом Traversable (или, фактически, о Foldable, который продолжается Traversable), состоит в том, что он требует, чтобы элементы любой структуры имели линейный порядок - вы можете превратить любой Traversable в список его элементов (с Foldable.toList).

Другой, менее очевидный факт о Traversable заключается в том, что существуют следующие функции (адаптированные из Гиббонс и Оливейра, "Сущность шаблона итератора" :

-- | A "shape" is a Traversable structure with "no content," 
-- i.e., () at all locations.
type Shape t = t ()

-- | "Contents" without a shape are lists of elements.
type Contents a = [a]

shape :: Traversable t => t a -> Shape t
shape = fmap (const ())

contents :: Traversable t => t a -> Contents a
contents = Foldable.toList

-- | This function reconstructs any Traversable from its Shape and
-- Contents.  Law:
--
-- > reassemble (shape xs) (contents xs) == Just xs
--
-- See Gibbons & Oliveira for implementation.  Or do it as an exercise.
-- Hint: use the State monad...
--
reassemble :: Traversable t => Shape t -> Contents a -> Maybe (t a)

A Traversable экземпляр для множеств нарушил бы предложенный закон, так как все непустые множества имели бы тот же Shape - набор, Contents которого [()]. Из этого должно быть легко доказать, что всякий раз, когда вы пытаетесь выполнить reassemble набор, вы только когда-либо получите пустой набор или один сингл обратно.

Урок? Traversable "сохраняет форму" в очень специфическом, более сильном смысле, чем Functor.

Ответ 2

Set является "просто" функтором (а не Functor) из подкатегории Hask, где Eq "хорошо" (т.е. подкатегория, где выполняется сравнение, подстановка, выполняется). Если бы виды ограничений были со временем назад, возможно, это был бы Functor.

Ответ 3

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

Относительно Set.zip - это вздор. Как и Set.head(у вас есть это в Scala). Он не должен существовать.