Есть ли такая вещь, как бидистрибутив? Какая функция мне нужна здесь?

У меня есть код (на самом деле в C#, но этот вопрос не имеет ничего общего с C#, поэтому я буду говорить обо всех моих типах на языке Haskell), где я работаю внутри Either a b, После, я bind функция с сигнатурой, которая в Haskell-говорят b -> (c, d)после чего хочу потянуть c наружу и по умолчанию это в левом случае, т.е. я хочу (c, Either a d), Теперь этот шаблон встречался много раз с одним конкретным сервисом, который я писал, поэтому я выбрал метод для этого. Однако меня беспокоит всякий раз, когда я просто "придумываю" такой метод, не понимая правильных теоретических основ. Другими словами, с какой абстракцией мы имеем здесь дело?

У меня была похожая ситуация в каком-то F# -коде, где моя пара и моя были поменяны местами (a, b) -> (b -> Either c d) -> Either c (a, d), Я спросил друга, что это было, и он заставил меня пройтись, что сделало меня очень счастливым, даже несмотря на то, что я вынужден делать ужасно мономорфные реализации в F# из-за отсутствия классов типов. (Я бы хотел переназначить свой F1 в Visual Studio на Hackage; это один из моих основных ресурсов для написания кода.NET). Проблема в том, что траверс:

class (Functor t, Foldable t) => Traversable t where
    traverse :: Applicative f => (a -> f b) -> t a -> f (t b)

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

Тем не менее, я подумал о своем первом случае больше, тот, который не traverseи понимаю, что "дефолт c в левом случае "может быть сделано с отображением в левом случае, что меняет проблему на наличие этой формы: Either (c, a) (c, d) -> (c, Either a d) который я распознаю как образец, который мы видим в арифметике с умножением и сложением: a(b + c) = ab + ac, Я также вспомнил, что такая же картина существует в булевой алгебре и в теории множеств (если память служит, A intersect (B union C) = (A intersect B) union (A intersect C)). Очевидно, здесь есть некоторая абстрактная алгебраическая структура. Однако память не служит, и я не мог вспомнить, как она называлась. Немного покопаться в Википедии быстро решили это: вот законы распределения. И радость, о радость, Кметт дал нам распределить:

class Functor g => Distributive g where
    distribute :: Functor f => f (g a) -> g (f a)

Это даже имеет cotraverse потому что это двойственно Travsersable! Прекрасный!! Однако я заметил, что нет (,) пример. Ооо Потому что, да, при чем здесь "дефолт" c Значение "входит во все это? Тогда я понял, о, мне, может быть, мне нужно что-то вроде двунаправленного, основанного на бифункторе, возможно, двойного к битрейбируемому? Концептуально:

class Bifunctor g => Bidistributive g where
    bidistribute :: Bifunctor f => f (g a b) (g a c) -> g a (f b c)

Кажется, это структура распределительного закона, о котором я говорю. Я не могу найти в Хаскеле такую ​​вещь, которая сама по себе не имеет значения, поскольку я на самом деле пишу на C#. Тем не менее, для меня важно не придумывать фиктивных абстракций и все же признать в моем коде как можно больше законных абстракций, независимо от того, выражены они как таковые или нет, для моего собственного понимания.

У меня сейчас есть .InsideOut(<default>) функция (метод расширения) в моем коде C# (что за хак, верно!). Буду ли я полностью вне базы, чтобы создать (да, к сожалению, мономорфный) .Bidistribute(...) функция (метод расширения), чтобы заменить его и отобразить "по умолчанию" для левого регистра в левый регистр перед его вызовом (или просто распознать "бидистрибутивный" символ "наизнанку")?

2 ответа

Решение

bidistribute не может быть реализовано как таковое. Рассмотрим тривиальный пример

data Biconst c a b = Biconst c

instance Bifunctor (Biconst c) where
  bimap _ _ (Biconst c) = Biconst c

Тогда у нас будет специализация

bidistribute :: Biconst () (Void, ()) (Void, ()) -> (Void, Biconst () () ())
bidistribute (Biconst ()) = ( ????, Biconst () )

Там явно нет способа заполнить пробел, который должен иметь тип Void,

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

uncozipL :: Functor f => Either (f a) (f b) -> f (Either a b)
uncozipL (Left l) = Left <$> l
uncozipL (Right r) = Right <$> l

Это определено вadjunctions (найдено с помощью Google).

На основе подсказки @leftaroundabout, чтобы посмотреть на дополнения, в дополнение к uncozipL что он упоминает в своем ответе, если мы отложим "по умолчанию первое значение пары в левом случае любого из них", мы также можем решить это с помощью unzipR:

unzipR :: Functor u => u (a, b) -> (u a, u b)

Тогда все равно будет необходимо отобразить первый элемент в паре и вывести значение с чем-то вроде either (const "default") id, Интересно, что если вы используете uncozipLВам нужно знать, что одна из вещей - это пара. Если вы используете unzipRВам нужно знать, что один из них. Ни в том, ни в другом случае вы не используете абстрактный бифунктор.

Кроме того, кажется, что шаблон или абстракция, которую я ищу, является распределительной решеткой. Википедия говорит:

Решетка (L,∨,∧) является дистрибутивной, если для всех x, y и z в L выполняется следующее дополнительное тождество:

x ∧ (y ∨ z) = (x ∧ y) ∨ (x ∧ z).

это именно то свойство, которое я наблюдал во многих разных местах.

Другие вопросы по тегам