Я беру невеличку проблему з високо оціненою відповіддю на ізоморфізм, оскільки визначення теорії категорій ізоморфізму нічого не говорить про об'єкти. Щоб зрозуміти чому, давайте переглянемо визначення.
Визначення
Ізоморфізм - це пара морфізмів (тобто функцій) 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)
Використання для ізоморфізмів
Церковне кодування
Одним із варіантів використання ізоморфізмів є кодування Церквою типів даних як функцій. Наприклад, 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
Це означає, що якщо ми складемо наші дві лінзи, результат повинен бути невідмінним від цієї ідентичної лінзи.