Ответ 1
Я беру небольшую проблему с рассмотренным ответом на изоморфизм, поскольку определение теории изоморфизма категорически ничего не говорит об объектах. Чтобы понять, почему, просмотрите определение.
Определение
Изоморфизм представляет собой пару морфизмов (т.е. функций), f
и g
, таких, что:
f . g = id
g . f = id
Эти морфизмы тогда называются "изо морфизмами". Многие люди не понимают, что "морфизм" в изоморфизме относится к функции, а не к объекту. Однако вы могли бы сказать, что объекты, которые они связывают, являются "изоморфными", что и описывает другой ответ.
Обратите внимание, что определение изоморфизма не говорит, что (.
), id
, или =
должно быть. Единственное требование - то, что бы они ни были, они также удовлетворяют законам категорий:
f . id = f
id . f = f
(f . g) . h = f . (g . h)
Композиция (т.е. (.
)) соединяет два морфизма в один морфизм, а id
обозначает какой-то "тождественный" переход. Это означает, что если наши изоморфизмы скомпенсируются тождественному морфизму id
, то вы можете думать о них как обратные друг от друга.
Для конкретного случая, когда морфизмы являются функциями, тогда id
определяется как тождественная функция:
id x = x
... и состав определяется как:
(f . g) x = f (g x)
... и две функции являются изоморфизмами, если они сводятся к тождественной функции id
, когда вы их компилируете.
Морфизмы против объектов
Однако существует несколько способов, по которым два объекта могут быть изоморфны. Например, учитывая следующие два типа:
data T1 = A | B
data T2 = C | D
Между ними существует два изоморфизма:
f1 t1 = case t1 of
A -> C
B -> D
g1 t2 = case t2 of
C -> A
D -> B
(f1 . g1) t2 = case t2 of
C -> C
D -> D
(f1 . g1) t2 = t2
f1 . g1 = id :: T2 -> T2
(g1 . f1) t1 = case t1 of
A -> A
B -> B
(g1 . f1) t1 = t1
g1 . f1 = id :: T1 -> T1
f2 t1 = case t1 of
A -> D
B -> C
g2 t2 = case t2 of
C -> B
D -> A
f2 . g2 = id :: T2 -> T2
g2 . f2 = id :: T1 -> T1
Итак, почему лучше описать изоморфизм в терминах конкретных функций, связывающих два объекта, а не двух объектов, так как необязательно может быть уникальная пара функций между двумя объектами, которые удовлетворяют законам изоморфизма.
Кроме того, обратите внимание на то, что функции не являются обратимыми. Например, следующие пары функций не являются изоморфизмами:
f1 . g2 :: T2 -> T2
f2 . g1 :: T2 -> T2
Даже если никакая информация не теряется при компоновке f1 . g2
, вы не возвращаетесь в исходное состояние, даже если конечное состояние имеет тот же тип.
Кроме того, изоморфизмы не должны быть между конкретными типами данных. Здесь пример двух канонических изоморфизмов не существует между конкретными алгебраическими типами данных и вместо этого просто связывает функции: curry
и uncurry
:
curry . uncurry = id :: (a -> b -> c) -> (a -> b -> c)
uncurry . curry = id :: ((a, b) -> c) -> ((a, b) -> c)
Используется для изоморфизмов
Кодовое кодирование
Одно использование изоморфизмов - это типы данных типа Church-encode как функции. Например, Bool
изоморфно forall a . a -> a -> a
:
f :: Bool -> (forall a . a -> a -> a)
f True = \a b -> a
f False = \a b -> b
g :: (forall a . a -> a -> a) -> Bool
g b = b True False
Убедитесь, что f . g = id
и g . f = id
.
Преимущество типов данных кодирования в Церкви заключается в том, что они иногда работают быстрее (потому что кодирование в церкви - это стиль продолжения передачи), и они могут быть реализованы на языках, на которых вообще не существует языковой поддержки для типов алгебраических данных.
Перевод реализации
Иногда пытаются сравнить одну библиотечную реализацию какой-то функции с другой реализацией библиотеки, и если вы можете доказать, что они изоморфны, то вы можете доказать, что они одинаково сильны. Кроме того, изоморфизмы описывают, как перевести одну библиотеку в другую.
Например, существуют два подхода, которые предоставляют возможность определять монаду из сигнатуры функтора. Одна из них - свободная монада, предоставляемая пакетом free
, а другая - операционная семантика, предоставляемая пакетом operational
.
Если вы посмотрите на два основных типа данных, они выглядят разными, особенно их вторыми конструкторами:
-- modified from the original to not be a monad transformer
data Program instr a where
Lift :: a -> Program instr a
Bind :: Program instr b -> (b -> Program instr a) -> Program instr a
Instr :: instr a -> Program instr a
data Free f r = Pure r | Free (f (Free f r))
... но они на самом деле изоморфны! Это означает, что оба подхода одинаково сильны, и любой код, написанный одним подходом, может быть механически переведен в другой подход с использованием изоморфизмов.
Изоморфизмы, не являющиеся функциями
Кроме того, изоморфизмы не ограничиваются функциями. Они фактически определены для любого Category
, а у Haskell множество категорий. Вот почему более полезно думать с точки зрения морфизмов, а не типов данных.
Например, тип Lens
(из data-lens
) формирует категорию, в которой вы можете создавать линзы и иметь линзу. Таким образом, используя наш выше тип данных, мы можем определить две линзы, которые являются изоморфизмами:
lens1 = iso f1 g1 :: Lens T1 T2
lens2 = iso g1 f1 :: Lens T2 T1
lens1 . lens2 = id :: Lens T1 T1
lens2 . lens1 = id :: Lens T2 T2
Заметим, что в игре есть два изоморфизма. Один из них - изоморфизм, который используется для построения каждой линзы (т.е. f1
и g1
) (а также почему эта строительная функция называется iso
), а затем сами линзы также являются изоморфизмами. Заметим, что в приведенной выше формулировке используемая композиция (.
) не является функциональным составом, а скорее композицией линзы, а id
не является тождественной функцией, а вместо этого является идентичной линзой:
id = iso id id
Это означает, что если мы составим наши две линзы, результат должен быть неотличим от этого объектива.