Важность изоморфных функций
Короткий Вопрос: какова важность изоморфных функций в программировании (а именно в функциональном программировании)?
Вопрос: Я пытаюсь провести некоторые аналогии между функциональным программированием и концепциями в теории категорий, основанной на некоторых из жаргона, который я слышу время от времени. По сути, я пытаюсь "распаковать" этот жаргон во что-то конкретное, что я могу затем расширить. Тогда я смогу использовать жаргон с пониманием просто-какого-черта-я-говорю. Что всегда приятно.
один из этих терминов, которые я слышу все время, это изоморфизм, Я понимаю, что речь идет о рассуждениях об эквивалентности между функциями или композициями функций. Мне было интересно, может ли кто-нибудь дать некоторое представление о некоторых общих шаблонах, где свойство изоморфизма пригодится (в функциональном программировании), и любые побочные продукты, такие как оптимизация компилятора из рассуждений об изоморфных функции.
3 ответов
я беру небольшую проблему с ответом upvoted для изоморфизма, поскольку определение теории категорий изоморфизма ничего не говорит об объектах. Чтобы понять почему, давайте рассмотрим определение.
определение
изоморфизм-это пара морфизмов (т. е. функции), 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
- это не функция Identity, но вместо этого объектив тож:
id = iso id id
это означает, что если мы составим наши две линзы, результат должен быть неотличим от этой линзы идентичности.
An изоморфизм u :: a -> b
- это функция, которая имеет инверсия, т. е. другая функция v :: b -> a
такие, что отношения
u . v = id
v . u = id
удовлетворены. Вы говорите, что два типа изоморфна если между ними есть изоморфизм. Это, по сути, означает, что вы можете считать их одним и тем же типом - все, что вы можете сделать с одним, Вы можете сделать с другим.
изоморфизм функции
два типа функции
(a,b) -> c
a -> b -> c
изоморфны, так как мы можем писать
u :: ((a,b) -> c) -> a -> b -> c
u f = \x y -> f (x,y)
v :: (a -> b -> c) -> (a,b) -> c
v g = \(x,y) -> g x y
вы можете проверить это u . v
и v . u
как id
. На самом деле, функции u
и v
более известны по именам curry
и uncurry
.
изоморфизм и Newtypes
мы используем изоморфизм всякий раз, когда мы используем объявление newtype. Например, базовый тип монады состояния s -> (a,s)
, который может быть немного запутанным, чтобы думать. Используя объявление newtype:
newtype State s a = State { runState :: s -> (a,s) }
мы создаем новый тип State s a
, которая изоморфна s -> (a,s)
и это дает понять, когда мы используем его, мы думаем о функциях, которые имеют изменяемое состояние. Мы также получаем удобный конструктор State
и геттер runState
для нового типа.
монады и Comonads
для более продвинутой точки зрения, рассмотрим изоморфизм, используя curry
и uncurry
что я использовал выше. The Reader r a
тип имеет объявление newtype
newType Reader r a = Reader { runReader :: r -> a }
в контексте монад функция f
таким образом, создание считывателя имеет подпись типа
f :: a -> Reader r b
что эквивалентно
f :: a -> r -> b
что одна половина Карри/изоморфизм uncurry. Мы также можем определить CoReader r a
тип:
newtype CoReader r a = CoReader { runCoReader :: (a,r) }
что можно сделать в comonad. Там у нас есть функция cobind, или =>>
которая принимает функцию, которая принимает coreader и производит сырье типа:
g :: CoReader r a -> b
, которая изоморфна
g :: (a,r) -> b
но мы уже видели, что a -> r -> b
и (a,r) -> b
изоморфны, что дает нам нетривиальный факт: монада читателя (с монадической связью) и комонада coreader (с комонадическим кобиндом) также изоморфны! В частности, они могут использоваться для одной и той же цели-обеспечения глобальной среды, которая проходит через каждую функцию вызов.
думать в терминах типов данных. В Haskell, например, вы можете думать о двух типах данных, которые будут изоморфными, если существует пара функций, которые преобразуют данные между ними уникальным способом. Следующие три типа изоморфны друг другу:
data Type1 a = Ax | Ay a
data Type2 a = Blah a | Blubb
data Maybe a = Just a | Nothing
вы можете думать о функциях, которые преобразуются между ними как изоморфизмы. Это соответствует категориальной идее изоморфизма. Если между Type1
и Type2
существует две функции f
и g
С f . g = g . f = id
, то две функции являются изоморфизмами между этими двумя типами (объектами).