Хороші приклади не функціонера / функціонера / додатка / монади?


209

Пояснюючи комусь, що таке клас X, я намагаюся знайти хороші приклади структур даних, які є саме X.

Отже, я прошу приклади для:

  • Конструктор типу, який не є Функтором.
  • Конструктор типу, який є функтором, але не є застосунним.
  • Конструктор типу, який є додатком, але не є монадою.
  • Конструктор типу - це монада.

Я думаю, що всюди є багато прикладів Монади, але гарний приклад Монади, який має відношення до попередніх прикладів, може доповнити картину.

Я шукаю приклади, які були б схожі між собою, відрізняючись лише аспектами, важливими для належності до певного класу типу.

Якби вдалося підкрасти приклад Стрілки десь у цій ієрархії (це між Applicative та Monad?), Це теж було б чудово!


4
Чи можливо зробити конструктор типу ( * -> *), для якого немає відповідного fmap?
Оуен

1
Оуен, я думаю, що a -> Stringце не функтор.
Ротсор

3
@Rotsor @Owen a -> String- це математичний функціонер, але не Haskell Functor, щоб було зрозуміло.
Дж. Абрахамсон

@J. Авраамсон, в якому сенсі це тоді математичний функтор? Ви говорите про категорію із перевернутими стрілками?
Ротсор

3
Для людей , не знають, контраваріантен функтор має БПМЖ типу(a -> b) -> f b -> f a
AJFarmar

Відповіді:


100

Конструктор типу, який не є Функтором:

newtype T a = T (a -> Int)

Ви можете зробити з нього противаріантний функтор, але не (коваріантний) функтор. Спробуйте написати, fmapі ви не зможете. Зауважте, що противаріантна версія функтора зворотна:

fmap      :: Functor f       => (a -> b) -> f a -> f b
contramap :: Contravariant f => (a -> b) -> f b -> f a

Конструктор типу, який є функтором, але не застосовується:

Я не маю хорошого прикладу. Є Const, але в ідеалі я хотів би конкретного немоноїда і не можу придумати жодного. Усі типи в основному числові, перерахування, продукти, суми або функції, коли ви переходите до нього. Ви можете бачити нижче піратів, і я не погоджуюся, чи Data.Voidє це Monoid;

instance Monoid Data.Void where
    mempty = undefined
    mappend _ _ = undefined
    mconcat _ = undefined

Оскільки _|_юридична цінність у Haskell є фактично єдиною юридичною цінністю Data.Void, це відповідає нормам Monoid. Я не впевнений, що unsafeCoerceстосується цього, оскільки ваша програма вже не гарантує, що не порушує семантику Haskell, як тільки ви використовуєте будь-яку unsafeфункцію.

Дивіться у статті Haskell Wiki для статті внизу ( посилання ) або небезпечних функцій ( посилання ).

Цікаво, чи можна створити конструктор такого типу за допомогою системи більш багатого типу, наприклад, Agda чи Haskell з різними розширеннями.

Конструктор типу, який є додатком, але не монадою:

newtype T a = T {multidimensional array of a}

Ви можете зробити додаток із нього, наприклад:

mkarray [(+10), (+100), id] <*> mkarray [1, 2]
  == mkarray [[11, 101, 1], [12, 102, 2]]

Але якщо ви зробите це монадою, ви можете отримати невідповідність вимірів. Я підозрюю, що подібні приклади на практиці рідкісні.

Конструктор типу, який є Monad:

[]

Про стрілки:

Запитувати, де стрілка лежить на цій ієрархії - це як запитати, що таке форма "червона". Зверніть увагу на різновид невідповідності:

Functor :: * -> *
Applicative :: * -> *
Monad :: * -> *

але,

Arrow :: * -> * -> *

3
Гарний список! Я б запропонував використовувати щось простіше, як Either aприклад для останнього випадку, оскільки це легше зрозуміти.
fuz

6
Якщо ви все ще шукаєте конструктор типів, який є додатним, але не монадою, дуже поширеним прикладом може бути ZipList.
Джон Л

23
_|_мешкає у кожному типі *, але справа в Voidтому, що вам потрібно буде нахилитися назад, щоб побудувати його, або ви знищили його значення. Саме тому його не примірник Enum, Monoid і т.д. Якщо у вас вже є один, я радий повідомити вам розімніть їх разом (даючи вам Semigroup) , але mempty, але я не дам ніяких інструментів для явного побудови значення типу Voidв void. Ви повинні завантажити пістолет і навести його на ногу і самостійно натягнути курок.
Едвард КМЕТТ

2
Педантично я думаю, що ваше уявлення про Кофунктор неправильне. Подвійний фактор - це функтор, тому що ви перевертаєте і вхід, і вихід і в кінцевому підсумку - це те саме. Поняття, яке ви шукаєте, ймовірно, "протирічний функтор", який дещо відрізняється.
Бен Мілвуд

1
@AlexVong: "Застарілі" -> люди просто використовують інший пакет. Якщо говорити про "протирічний функтор", а не про "подвійний функтор", вибачте за плутанину. У деяких контекстах я бачив "кофунктор", який використовується для позначення "контраваріантних функторів", тому що функтори самодуальні, але, здається, вони просто заплутують людей.
Дітріх Епп

87

Мій телефон може бути тісним моїм телефоном, але ось що.

newtype Not x = Kill {kill :: x -> Void}

не може бути функціонером. Якби це було, ми б мали

kill (fmap (const ()) (Kill id)) () :: Void

а Місяць був би із зеленого сиру.

Тим часом

newtype Dead x = Oops {oops :: Void}

є функтором

instance Functor Dead where
  fmap f (Oops corpse) = Oops corpse

але це не може бути застосовним, або ми б це мали

oops (pure ()) :: Void

і Зелений був би з сиру Місяця (що насправді може статися, але лише пізніше ввечері).

(Додаткова примітка: Voidяк у Data.Voidпорожньому типі даних. Якщо ви намагаєтесь undefinedдовести, що це Monoid, я використаюunsafeCoerce щоб довести, що це не так.)

Радісно,

newtype Boo x = Boo {boo :: Bool}

є корисним для багатьох способів, наприклад, як це було б у Dijkstra,

instance Applicative Boo where
  pure _ = Boo True
  Boo b1 <*> Boo b2 = Boo (b1 == b2)

але це не може бути монадою. Щоб зрозуміти, чому ні, дотримуйтесь цього повернення потрібно постійно Boo Trueабо Boo False, отже, це

join . return == id

неможливо утримати

О так, я майже забув

newtype Thud x = The {only :: ()}

є Монада. Згорніть своє.

Літак, щоб зловити ...


8
Пустота порожня! Морально, як би там не було.
pigworker

9
Пустотою є тип з 0 конструкторами, я припускаю. Це не моноїд, тому що його немає mempty.
Ротсор

6
невизначений? Як грубо! На жаль, unsafeCoerce (unsafeCoerce () <*> undefined) не є (), тому в реальному житті є спостереження, які порушують закони.
pigworker

5
У звичайній семантиці, яка допускає рівно один вид невизначеного, ви цілком праві. Звичайно, є й інші семантики. Пустота не обмежується субмоноїдом у загальному фрагменті. Також це не моноїд у семантиці, яка розрізняє способи відмови. Коли у мене з’явиться простіший час, ніж телефонне редагування, я поясню, що мій приклад працює лише в семантиці, для якої не існує точно одного виду невизначених.
pigworker

22
Багато _|_
прихильності

71

Я вважаю, що інші відповіді пропустили кілька простих і поширених прикладів:

Конструктор типу, який є функтором, але не є додатком. Простий приклад - пара:

instance Functor ((,) r) where
    fmap f (x,y) = (x, f y)

Але немає способу визначити його Applicativeпримірник без накладення додаткових обмежень на r. Зокрема, не існує способу визначення pure :: a -> (r, a)довільностіr .

Конструктор типу, який є додатком, але не є монадою. Відомий приклад - ZipList . (Це те, newtypeщо обгортає списки та надає різніApplicative примірників для них.)

fmapвизначається звичайним способом. Але pureі <*>визначаються як

pure x                    = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

таким чином pureстворюється нескінченний список, повторюючи задане значення, і <*>зіпсує список функцій зі списком значень - застосовує i -му функцію до i -го елемента. (Стандарт <*>на[] виробляє всі можливі комбінації застосування i- ї функції до j -го елемента.) Але немає розумного способу визначення монади (див. Цей пост ).


Як стрілки вписуються в ієрархію функтора / додатка / монади? Дивіться Ідіоми не забувають, стріли прискіпливі, монади розбещені Сем Ліндлі, Філіп Вадлер, Джеремі Яллоп. MSFP 2008. (Вони називають ідіоми аплікативних функторів .) Реферат:

Ми знову переглядаємо зв'язок між трьома поняттями обчислення: монадами Моггі, стрілами Х'юза та ідіомами Макбріда та Патерсона (їх також називають прикладними функторами). Покажемо, що ідіоми еквівалентні стрілкам, які задовольняють тип ізоморфізму A ~> B = 1 ~> (A -> B) і що монади еквівалентні стрілкам, які задовольняють тип ізоморфізму A ~> B = A -> (1 ~ > В). Далі, ідіоми вбудовуються в стрілки, а стріли - в монади.


1
Так ((,) r)це функтор, який не є додатком; але це лише тому, що ви не можете загалом визначитись відразу pureдля всіх r. Отже, це вигадка стислості мови, намагаючись визначити (нескінченну) колекцію прикладних функторів з одним визначенням pureі <*>; в цьому сенсі, як видається , не буде нічого математично глибоко про це контрприклад , так як для будь-якого бетону r, ((,) r) може бути аплікативного функтор. Питання: Чи можете ви подумати про БЕЗКОШТОВНИЙ функтор, який не може бути застосовним?
Джордж

1
Дивіться stackoverflow.com/questions/44125484/… як повідомлення з цим питанням.
Джордж

20

Хорошим прикладом для конструктора типів, який не є функтором, є Set: Ви не можете реалізувати fmap :: (a -> b) -> f a -> f b, оскільки без додаткового обмеження Ord bви не можете побудувати f b.


16
Це насправді хороший приклад, оскільки математично ми б дуже хотіли зробити це функціонером.
Олександр К.

21
@AlexandreC. Я не погоджуюся з цим, це не гарний приклад. Математично така структура даних утворює функтор. Те, що ми не можемо реалізувати, fmap- лише проблема мови / впровадження. Крім того, можна Setперетворитись на монаду продовження, яка робить монаду з усіх властивостей, які ми очікували, дивіться це питання (хоча я не впевнений, чи можна це зробити ефективно).
Петро Пудлак

@PetrPudlak, як це питання мови? Рівність bможе бути невирішеною, у цьому випадку ви не можете визначитись fmap!
Туріон

@Turion Бути рішучими та визначеними - це дві різні речі. Наприклад, можна правильно визначити рівність у лямбда-термінах (програмах), навіть якщо це неможливо визначити за алгоритмом. У будь-якому випадку цього прикладу не було. Тут проблема полягає в тому, що ми не можемо визначити Functorекземпляр із Ordобмеженням, але це можливо з іншим визначенням Functorабо кращою підтримкою мови. Насправді з ConstraintKinds можна визначити клас типу, який може бути параметризований так.
Петро Пудлак

Навіть якби ми могли подолати ordобмеження, той факт, що Setне може містити повторюваних записів, означає, що це fmapмогло б змінити контекст. Це порушує закон про асоціативність.
Джон Ф. Міллер

11

Я хотів би запропонувати більш систематичний підхід до відповіді на це запитання, а також показати приклади, які не використовують жодних спеціальних хитрощів, таких як "найнижчі" значення чи нескінченні типи даних чи щось подібне.

Коли у конструкторів типів відсутні екземпляри класу типу?

Загалом, є дві причини, чому конструктор типів не може мати примірник певного класу типу:

  1. Неможливо реалізувати підписи типів необхідних методів із класу типу.
  2. Може реалізувати підписи типів, але не може задовольнити необхідні закони.

Приклади першого типу простіші за типи другого роду, тому що для першого типу нам просто потрібно перевірити, чи можна реалізувати функцію з підписом заданого типу, тоді як для другого роду нам потрібно довести, що жодної реалізації могло б задовольнити закони.

Конкретні приклади

  • Конструктор типів, який не може мати екземпляр функтора, оскільки тип не може бути реалізований:

    data F z a = F (a -> z)

Це контрафунктор, а не функтор щодо параметра типу a, тому що aв противаріантному положенні. Неможливо реалізувати функцію з підписом типу (a -> b) -> F z a -> F z b.

  • Конструктор типів, який не є законним функціонером, навіть якщо підпис типу fmapможе бути реалізований:

    data Q a = Q(a -> Int, a)
    fmap :: (a -> b) -> Q a -> Q b
    fmap f (Q(g, x)) = Q(\_ -> g x, f x)  -- this fails the functor laws!

Цікавим аспектом цього прикладу є те, що ми можемо реалізувати fmapправильний тип, навіть Fне може бути функтором, оскільки він використовує aв протилежній позиції. Таким чином, ця реалізація, fmapпоказана вище, вводить в оману - навіть якщо вона має правильний підпис типу (я вважаю, що це єдина можлива реалізація підпису цього типу), закони функтора не задовольняються. Наприклад, fmap idid, тому що let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"є 123, але let (Q(f,_)) = id (Q(read,"123")) in f "456"є456 .

Насправді Fце лише профунктор, - він не є ні функтором, ні контрафунктором.

  • Законний функтор, який не застосовується, оскільки підпис типу pureне може бути реалізований: візьміть монаду Writer (a, w)і усуньте обмеження, яке wповинно бути моноїдом. Тоді неможливо побудувати значення типу (a, w)з a.

  • Функтор , яка не апплікатівен , так як тип підпис <*>не може бути реалізована: data F a = Either (Int -> a) (String -> a).

  • Функтор, який не є законним застосунком, незважаючи на те, що методи класу типу можуть бути реалізовані:

    data P a = P ((a -> Int) -> Maybe a)

Конструктор типу P- це функтор, оскільки він використовується aлише в коваріантних положеннях.

instance Functor P where
   fmap :: (a -> b) -> P a -> P b
   fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))

Єдина можлива реалізація підпису типу <*>- це функція, яка завжди повертає Nothing:

 (<*>) :: P (a -> b) -> P a -> P b
 (P pfab) <*> (P pa) = \_ -> Nothing  -- fails the laws!

Але ця реалізація не відповідає закону про ідентичність для прикладних функціонерів.

  • Функтор, який є, Applicativeале не є,Monad тому що підпис типу bindне може бути реалізований.

Я не знаю таких прикладів!

  • Функтор, який є, Applicativeале не є,Monad тому що закони не можуть бути виконані, навіть якщо підпис типу bindможе бути реалізований.

Цей приклад породив досить багато дискусій, тому можна з упевненістю сказати, що довести цей приклад правильним непросто. Але кілька людей це перевірили самостійно різними методами. Див. `Дані PoE a = Порожня | Пара aa` монада? для додаткового обговорення.

 data B a = Maybe (a, a)
   deriving Functor

 instance Applicative B where
   pure x = Just (x, x)
   b1 <*> b2 = case (b1, b2) of
     (Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
     _ -> Nothing

Дещо громіздко довести, що не існує законної Monadінстанції. Причина немональної поведінки полягає в тому, що не існує природного способу реалізації, bindколи функція f :: a -> B bможе повернутися Nothingабо Justдля різних значеньa .

Можливо, зрозуміліше врахувати Maybe (a, a, a), що також не є монадою, і спробувати здійснити joinдля цього. Виявите, що немає інтуїтивно розумного способу реалізації join.

 join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
 join Nothing = Nothing
 join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
 join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
 -- etc.

У випадках, зазначених у ???, видається очевидним, що ми не можемо виготовити Just (z1, z2, z3)будь-який розумний і симетричний з шести різних типів значень a. Ми, безумовно, могли вибрати якийсь довільний підмножина з цих шести значень, - наприклад, завжди приймати перше не порожнє Maybe- але це не задовольнило б закони монади. Повернення Nothingтакож не буде задовольняти закони.

  • Деревоподібна структура даних, яка не є монадою, хоча і має асоціативність, bindале не відповідає законам ідентичності.

Звичайна деревна монада (або «дерево з гілками у формі функтора») визначається як

 data Tr f a = Leaf a | Branch (f (Tr f a))

Це вільна монада над функтором f. Форма даних - це дерево, де кожна точка гілки є "функтор-фул" підрядів. Стандартне двійкове дерево було б отримане за допомогою type f a = (a, a).

Якщо ми модифікуємо цю структуру даних, створюючи також листя у формі функтора f, ми отримуємо те, що я називаю «семімонадою» - це таке, bindяке задовольняє закони природності та асоціативності, але його pureметод не відповідає одному із законів тотожності. "Семімонади - це напівгрупи в категорії ендофаніків. У чому проблема?" Це клас типу Bind.

Для простоти я визначаю joinметод замість bind:

 data Trs f a = Leaf (f a) | Branch (f (Trs f a))
 join :: Trs f (Trs f a) -> Trs f a
 join (Leaf ftrs) = Branch ftrs
 join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)

Філія щеплення є стандартною, але лист щеплення не є стандартною і виробляє Branch. Це не є проблемою для закону про асоціативність, але порушує один із законів тотожності.

Коли поліноміальні типи мають монадні екземпляри?

Жоден з функціонерів Maybe (a, a)і не Maybe (a, a, a)може бути наділений законним Monadекземпляром, хоча вони, очевидно Applicative.

У цих функторів немає жодних хитрощів - ні Voidі bottomніде, немає хитрої ліні / суворості, нескінченних структур і жодних обмежень класу типів. ApplicativeПримірник повністю стандартні. Функції returnі bindможуть бути реалізовані для цих функторів, але не відповідають законам монади. Іншими словами, ці функтори не є монадами, оскільки конкретна структура відсутня (але зрозуміти, чого саме не вистачає, непросто). Наприклад, невелика зміна функтора може перетворити його в монаду: data Maybe a = Nothing | Just aце монада. Ще один подібний функторdata P12 a = Either a (a, a) - це монада.

Конструкції для поліноміальних монад

Загалом, ось деякі конструкції, які випускають законні Monadз поліноміальних типів. У всіх цих конструкціях Mє монада:

  1. type M a = Either c (w, a)де wбудь-який моноїд
  2. type M a = m (Either c (w, a))де mбудь-яка монада і wбудь-який моноїд
  3. type M a = (m1 a, m2 a)де m1і m2є якісь монади
  4. type M a = Either a (m a)де mбудь-яка монада

Перша конструкція WriterT w (Either c), друга конструкція WriterT w (EitherT c m). Третя конструкція є складовою продуктом монад: pure @Mвизначається як компонентний добуток pure @m1і pure @m2, і join @Mвизначається, опускаючи дані про поперечні продукти (наприклад m1 (m1 a, m2 a), відображається m1 (m1 a)шляхом опускання другої частини кортежу):

 join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
 join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))

Четверта конструкція визначається як

 data M m a = Either a (m a)
 instance Monad m => Monad M m where
    pure x = Left x
    join :: Either (M m a) (m (M m a)) -> M m a
    join (Left mma) = mma
    join (Right me) = Right $ join @m $ fmap @m squash me where
      squash :: M m a -> m a
      squash (Left x) = pure @m x
      squash (Right ma) = ma

Я перевірив, що всі чотири конструкції виробляють законні монади.

Я здогадуюсь, що немає інших конструкцій для поліноміальних монад. Наприклад, функтор Maybe (Either (a, a) (a, a, a, a))не отримується за допомогою будь-якої з цих конструкцій і тому не є монадичним. Тим НЕ менше, Either (a, a) (a, a, a)це Монадический тому вона ізоморфна добутку трьох монад a, aі Maybe a. Крім того, Either (a,a) (a,a,a,a)є монадичним, оскільки є ізоморфним продукту aі Either a (a, a, a).

Чотири конструкції, показані вище, дозволять нам отримати будь-яку суму будь-якої кількості продуктів будь-якої кількості a, наприклад, Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))тощо. Усі конструктори такого типу матимуть (принаймні один) Monadекземпляр.

Зрозуміло, залишається зрозуміти, які випадки використання можуть існувати для таких монад. Інша проблема полягає в тому, що Monadвипадки, отримані за допомогою конструкцій 1-4, як правило, не є унікальними. Наприклад, конструктору типу type F a = Either a (a, a)можна надати Monadекземпляр двома способами: шляхом побудови 4 за допомогою монади (a, a)та за допомогою конструкції 3 за допомогою ізоморфізму типу Either a (a, a) = (a, Maybe a). Знову ж таки, пошук випадків використання для цих реалізацій не відразу очевидний.

Залишається питання - даючи довільний тип поліноміальних даних, як розпізнати, чи має він Monadекземпляр. Я не знаю, як довести, що немає інших конструкцій для поліноміальних монад. Я не думаю, що поки що існує якась теорія, яка б відповіла на це питання.


Я думаю B , це монада. Чи можете ви надати контрприклад цьому пов'язуванню Pair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Empty?
Френкі

@Franky Associactivity не відповідає цьому визначенню, коли ви вибираєте fтаке, що f xє, Emptyале f yє Pair, і на наступному кроці обидва є Pair. Я перевірив вручну, що закони не застосовуються до цієї реалізації чи для будь-якої іншої реалізації. Але для цього досить працювати. Мені б хотілося, щоб це було простіше зрозуміти!
winitzki

1
@Turion Цей аргумент не поширюється, Maybeоскільки Maybeне містить різних значень, про які aслід турбуватися.
Даніель Вагнер

1
@Turion я довів це кількома сторінками розрахунків; аргумент про "природний шлях" - це лише евристичне пояснення. MonadПримірник складається з функцій , returnі bindщо закони , задовольняють. Є дві реалізації returnта 25 реалізацій, bindякі відповідають необхідним типам. Прямим розрахунком можна показати, що жодна з реалізацій не відповідає законам. Щоб скоротити кількість необхідної роботи, я використовував joinзамість цього bindі спочатку використовував закони про особу. Але це було досить багато роботи.
winitzki

1
@duplode Ні, я не думаю, що Traversableце потрібно. m (Either a (m a))перетворюється за допомогою pure @mв m (Either (m a) (m a)). Тоді банально Either (m a) (m a) -> m a, і ми можемо використовувати join @m. Це була реалізація, заради якої я перевірив закони.
winitzki
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.