Важность изоморфных функций


Короткий Вопрос: какова важность изоморфных функций в программировании (а именно в функциональном программировании)?

Вопрос: Я пытаюсь провести некоторые аналоги между функциональным программированием и концепциями в теории категорий, основанной на некоторых из жаргонов, которые я слышу время от времени. По сути, я пытаюсь "распаковать" этот жаргон во что-то конкретное, что я могу затем расширить. Тогда я смогу использовать жаргон с пониманием просто-о чем-черт-я-говорю. Что всегда приятно.

один из этих терминов я слышу все время изоморфизм, Я так понимаю, это рассуждение об эквивалентности между функциями или функциональными композициями. Мне было интересно, может ли кто-нибудь дать некоторое представление о некоторых общих шаблонах, где свойство изоморфизма пригодится (в функциональном программировании), и любые побочные продукты, такие как оптимизация компилятора из рассуждений об изоморфном функции.

3 68

3 ответа:

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

определение

изоморфизм-это пара морфизмов (т. е. функции), f и g, таких, что:

f . g = id
g . f = id

эти морфизмы затем называются"iso" морфизмы. Многие люди не понимают, что "морфизм" в изоморфизме относится к функции и не объект. Однако вы бы сказали, что объекты, которые они соединяют, "изоморфны", что и описывает другой ответ.

обратите внимание, что определение изоморфизма не сказать, что (.),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

это означает, что если мы составим наши две линзы, результат должен быть неотличим от этой линзы идентичности.

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 State s a = State { runState :: s -> (a,s) }

мы создаем новый тип State s a, которая изоморфна s -> (a,s) и что дает понять, когда мы его используем, мы думаем о функциях, которые имеют изменяемое состояние. Мы также получаем удобный конструктор State и геттер runState для нового типа.

монады и Комонады

для более продвинутой точки зрения, рассмотрим изоморфизм, используя curry и uncurry что я использовал выше. Элемент 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) }

который может быть превращен в комонаду. Там у нас есть функция cobind, или =>> который принимает функцию, которая принимает coreader и создает необработанный тип:

g :: CoReader r a -> b

, которая изоморфна

g :: (a,r) -> b

но мы уже видели, что a -> r -> b и (a,r) -> b изоморфны, что дает нам нетривиальный факт: монада читателя (с монадической связью) и комонада кореадера (с комонадическим кобиндом) также изоморфны! В частности, они оба могут использоваться для одной и той же цели-обеспечения глобальной среды, которая пронизывает каждую функцию вызов.

думать в терминах типов данных. Например, в 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, то эти две функции являются изоморфизмами между этими двумя типами (объектами).