могу понять аппликативы 🙁
Всюду, где я вижу объяснения аппликативов, говорится о том, что это функторы, у которых есть ap, который как fmap, только у него применяемая функция тоже поднята в контекст. Это понятно и просто — мы извлекаем из контекста не только аргументы, но и саму функцию. Но это не говорит мне "как пользоваться аппликативом", это лишь говорит о том, что это такое. И, получается, если у нас аппликатив Maybe, и вместо функции Nothing, то и на выходе Nothing, независимо от аргументов. А если у нас аппликатив List, то каждый из элементов в аргументах применяется поочередно к каждой функции в списке, а результаты конкатенируются. Тут понятно.
Или вот, например, аппликативы сравниваются с монадами. Утверждается, что в отличие от монады, которая обязана выполнять эффекты по очереди (потому что следующий эффект становится понятен только после того, как будет выполнен бинд для предыдущего), аппликативы могут выполнять свои эффекты как захотят — например, слева направо, или даже параллельно (но не могут выполнять их избирательно). Тут тоже понятно почему — если разворачивание из контекста "блокирующее", то сперва будет развернута функция, затем для нее будет развернут первый аргумент, затем к результату применения (который будет снова функцией в контексте) будет применен развернутый второй аргумент и т.д. И, если я правильно понял идею, паралеллизм можно обеспечить, зная арность применяемой функции заранее, и отложить выполнение эффектов, возвращая функции-заглушки до тех пор, пока не будут переданы все аргументы.
Я не понимаю, почему говорят, что аппликативы не могут выполнять свои эффекты селективно, если они могут выполнять их параллельно. И я все еще не понимаю, как этим пользоваться на практике.
Вот, например, есть parsec. У него есть монадические парсер-комбинаторы — очень простая и понятная вещь. Каждый парсер, будучи применен к потоку токенов, может скормить распарсенное значение в бинд, а результатом бинда будет следующий парсер, который будет применен к оставшимся токенам. Из-за того, что это монада, поверх нее можно навесить дополнительные штуки — например, скрестить с List и получить недетерминированный парсинг. И из-за того, что это монада, ее нельзя анализировать статически, т.к. в общем случае неизвестно, какой именно парсер вернется из бинда следующим. Это понятно.
Но парсеры могут быть построены и на аппликативных комбинаторах. И тут я перестаю понимать — что происходит? Как их комбинировать? Как "выполняются" аппликативы? Если у монад есть выделенный интерпретатор, аля runWriter, runState, runParser, то где аналогичный механизм у аппликативов? Что вообще из тебя представляет "аппликативный парсер"? Как подразумевается анализировать цепочки аппликативных парсеров? Если аппликативы "не могут основываться на предыдущих значениях в цепочке вычислений", то как тогда ими можно делать хоть что-то полезное?
Если у вас будет немного времени объяснить мне это, я был бы жутко признателен.
к моему вопросу выше, я почитал ваши ответы, почитал еще всякое, поговорил еще с людьми... правильно ли я понимаю, что практическое использование аппликативов сводится к двум вещам: 1. применение функций с арностью выше 1 к значениям в контексте? Т.е. если у нас есть каррированная ф-ция арности 2, мы отрицаем аппликативы и пытаемся работать как с функтором: Prelude> step1 = fmap (*) [2, 4] Prelude> step1 [(*2), (*4)] но теперь частично примененные функции арности 1 тоже обернуты в контекст, и повторное применение через fmap выглядит достаточно ужасно: Prelude> ugly_ap f x = concat $ flip fmap f (flip fmap x) Prelude> step2 = ugly_ap [(*2), (*4)] [100, 250] Prelude> step2 [200, 500, 400, 1000] И это то же самое, что и Prelude> step1 = pure (*) <*> [2, 4] Prelude> step2 = step1 <*> [100, 250] 2. построение цепочек из чистых data (т.е. без исполняемого кода для связки), который потом интерпретируется "корневым применителем"? т.е. на примере парсеров, можно представить, что каждому примитивному парсеру/парсер-комбинатору соответствует data-тип вида data Empty = Empty data Fail = Fail reason data Return = Return value data OneOf = OneOf chars data Seq = Seq a b data Map = Map f x и более сложные парсеры просто строятся как-то типа data MySuperIntParser = (Seq (Map (\c -> read c :: Int) (Many (OneOf "0123456789"))) Eof) и затем вся эта хрень скармливается в условный i = runParser MySuperIntParser "123" который просто паттерн-матчингом сверяет корневой элемент полученной структуры с каким-то из примитивных парсер-комбинаторов и выполняет соответствующий эффект runParser <$> (_ <$> seq (_ <$> map (\c -> read c :: Int) <*> (_ <$> many <*> (_ <$> oneOf "0123456789"))) <*> eof) как-то так?
функтор не отрицает аппликатив, а скорее игнорирует, это же частный случай
ну может я плохо выразился. имею в виду, что если я не знаю про аппликативы, у меня есть инстанс функтора, и я пытаюсь вершить дела с ним, как с функтором, делая fmap с 2-арной функцией
2. врядли. аппликатив позволяет впихнуть чистые вычисления в данные, данные уже получаются не "чистые" интерпретировать можно что угодно, нет строгой связи с аппликативами или функторами.
"вершить дела" — это что-то нечёткое. что именно вы можете сделать с бинарной функцией без аппликатива?
можно на "ты". в примере выше я без аппликатива делаю (*) <$> [2, 4] <*> [100, 250]
ugly_ap применяется к унарной функции
к унарной функции в контексте. получается хреновая версия ap
да, но не получается
Обсуждают сегодня