Да, это опечатка, спасибо за исправление. Я писал, будучи жутко возмущен прочитанным в статье, а к моменту того, когда я перечитал свое сообщение и обнаружил неточности, возможность редактирования уже была утеряна.
После прочтения этой статьи я уже нихера не понимаю.
Потому что автор ничего не понял, и пытается запутать остальных, давая намеренно некорректные утверждения, лишенные какой-либо логики.
Так тип категория или нет?
Нет. То, что вы написали до того, верно.
Мне это (либо очень очевидное, либо очень неправильное) понимание
Это очевидное и правильное понимание. Вы правильно поняли смысл функторов. Вы лифтите функцию int -> string, чтобы она работала с Maybe int -> Maybe string. Вы можете записать fmap как fmap (a -> b) -> (f a -> f b).
Аппликативный функтор. А вот тут непонятно.
Обычный функтор берет чистую функцию и преобразовывает ее так, чтобы она принимала обернутые значения.
Аппликативный функтор берет обернутую функцию и преобразовывает ее так, чтобы она принимала обернутые значения. Т.е. аппликативный функтор лифтит функции, не разворачивая их.
А вот почему монада — контейнер?
А она и не контейнер. Она, как вы верно заметили, вычисление. То, что некоторые монады могут быть представлены как операции над контейнерами (хоть это, имхо, и сбивающая с толку аналогия, приносящая больше вреда), не делает саму монаду контейнером.
Но с таким же успехом и функтор — контейнер, нет?
Если рассматривать неверные аналогии, то функтор куда ближе к контейнеру, чем монада.
В статье столько ошибок, что даже и не знаешь, с чего начать.
Категории — это не типы. Это, если уж пытаться натянуть сову на глобус, системы типов. Или не типов. В категории Set объектами (базовыми элементами категорий) являются множества разных элементов, которые в каком-то смысле могут быть типами (множество всех целых чисел, множество всех строк и т.д.). В категории Hask (которая не совсем категория, но это вопрос практического свойства) базовыми элементами являются типы хаскеля. Конечно, можно создать категорию Double, где объектами будут литералы, а морфизмы будут задавать преобразования между этими литералами, но что это даст?
Эндоморфизмы — это не "тип в себя", это морфизм в пределах одной категории. Преобразование из string в int вполне себе эндоморфизм. Преобразование из Maybe в Maybe тоже эндоморфизм.
Монады — это не контейнеры. Как пример, есть монады IO и Reader не являются "контейнерами", в которые можно что-то положить. Монады — это абстракция вычислений в контексте. Если уж хотите использовать аналогии, то ближайшее к монаде, что можно придумать из "обычного" мира — это Promise. Вы связываете промисы в цепочки вычислений, и каждое вычисление находится в контексте того, что оно случится когда-то. И, как забавное следствие, вы не можете просто взять и избавиться от Promise, если уж вы начали его использовать.
Функтор — это не обработчик данных. Функтор — это способ задать контекст вычислений для значения. С т.з. теорката — это функция над объектами и морфизмами, которая преобразовывает их из текущей категории в, возможно, новую категорию. Или в ту же, если это эндоморфизм. Если брать промисы как пример, то функтор на промисах работает так:
берет Promise<int>,
неявно извлекает int,
применяет к нему intToStr(x: int): string (чистая функция)
оборачивает в Promise
возвращает Promise<string>.
Если бы это была монада, то это выглядело бы так:
берет Promise<int>,
неявно извлекает int,
применяет к нему intToStr(x: int): Promise<string> (монадическая функция, может делать внутри async/await)
возвращает Promise<string>
Аппликативный функтор просто рассчитывает, что функция, которую нужно применить к значению в контексте, тоже находится в контексте.
Т.е., опять же, используя промисы как пример, у нас есть Promise<(x: int) -> string> и Promise<int>. Аппликативный функтор дает возможность применить Promise<(x: int) -> string> к Promise<int> и получить Promise<string>.
Что касается использования List как примера функторов и монад, и вытекающие отсюда неправильные представления о том, что это контейнеры, а монады и функторы работают с контейнерами. Нет. List не является массивом, List является абстракцией недетерминированных вычислений. То, что List можно вычислить, представив как массив, это лишь "ложный друг переводчика". Значение List[1,2,2,3] на самом деле описывает недетерминированное значение, которое с вероятностью 25% равно 1, с вероятностью 25% равно 3 и с вероятностью 50% равно 2.
Суммируя: вы ничего не поняли, но уже пошли объяснять остальным.
Да, конечно. Разница между ними, как вы заметили, в преднамеренности и, как следствие, в наличии секретной компоненты для противодействия преднамеренности.
Если искажение непреднамеренное (например, случайно флипнулся бит в оперативке, или в ходе передачи по сети) — оно с некоторой равной вероятностью случится в любом бите контента, и построение статистической модели такого искажения не выявит зависимости положения искажения от содержимого измененного бита или его соседей.
Для обнаружения таких случайных модификаций достаточно самых простых мер — раньше, например, для очень небольших пакетов (7-10 бит) использовались биты четности (суммирование по полиному x+1). Для данных большего размера хорошо подходят CRC32 и CRC64, которые тоже суммирование по полиному, просто большего порядка.
Для исправления таких искажений достаточно использовать какие-нибудь избыточные коды, те же коды Рида-Соломона являются очень популярным выбором и позволяют настраивать максимальное количество исправляемых и обнаруживаемых искажений.
Почему CRC и коды Рида-Соломона нельзя использовать для контроля целостности? Активный атакующий, цель которого — выдать свои данные за данные изначального отправителя, уже не вписывается в модель случайного шума, меняющего биты, т.к. его изменения зависят от положения в контенте и от значений конкретных бит соседей (например, атакующий ищет "authorized": false, и хочет поменять это на "authorized": true ,). Почему CRC и RS не подходят для защиты от таких изменений? Очевидно, что так как эти алгоритмы не содержат в себе секретной компоненты, атакующий может просто посчитать CRC и RS заново. Даже если используется нестандартный полином, не представляет проблемы его отреверсить при достаточно большом количестве наблюдаемого открытого текста — ни CRC, ни RS не являются криптографически безопасными примитивами, и не обеспечивают защиту от преднамеренной модификации.
Для контроля же целостности можно использовать HMAC — семейство функций с секретным симметричным ключом, основанные на криптографических хэш-функциях. Пересчитать HMAC повторно, даже зная низлежащую функцию (SHA3-256, например), не зная ключа, невозможно, а HMAC безопасен ровно настолько, насколько безопасна низлежащая функция. Поэтому если низлежащая функция (SHA1) уязвима к второй preimage атаке (для оригинального текста x найти такой x', который sha1(x) == sha1(x') при x != x'), то и HMAC-подпись небезопасна. Для CRC же поиск такого поддельного текста тривиален.
Как вариант, может использоваться асимметричная подпись хэша. В таком случае атакующий может пересчитать хэш (его алгоритм известен), но не сможет подделать для него правильную подпись, не восстановив секретную часть ключа.
Ну как раз тут речь и идет о том, что для SHA1 правило "один хэш — один контент" больше не выполняется? Какая разница, преднамеренно или нет.
И если вам так любы и дороги устаревшие небезопасные хэш-функции, вы всегда можете заиметь себе свою собственную реализацию, которая не будет зависеть от openssl, с которым вы линкуетесь.
Если вам важно соответствие "один хэш — один контент", вам никуда не деться от использования криптографических хэш-функций, потому что их разрабатывают с учетом такого свойства.
Если же вам нужна хэш функция для балансинга, или, например, для построения хэшмапа, то подойдет и SHA1, и RC4-Hash.
Если же вам вообще нужна контрольная сумма для противодействия непреднамеренному искажению (не для контроля целостности) — то и CRC32 хороший вариант.
Все зависит от того, зачем это вам нужно и какие свойства вы ожидаете получить.
Ну, тогда используйте identity function :) Правда, для файла в 10 гб идентификатор будет все те же 10 гб, потому что это и будет сам файл, но что стоят такие маленькие неудобства?
Надо бы закрыть границы, чтобы предотвратить отток специалистов. Идея хорошая, в прошлый раз 69 лет границы были временно закрыты, и никаких проблем с программистами.
А каким образом это влияет на написанное? Глупость написал не автор deno, а автор этого поста. И то, что у автора ноды получилось в первый раз сделать что-то хорошее, не дает гарантий того, что у него это получится во второй раз. Вот, deno тому ярким примером — хуже только php.
Что переименуем дальше? Whitespace в blackspace?
в статье и про ФП ничего не написано. Написаны выдумки автора, использующие те же слова, но с каким-то безумным (и бездумно переусложненным) смыслом.
Вы написали эту статью?
Да, это опечатка, спасибо за исправление. Я писал, будучи жутко возмущен прочитанным в статье, а к моменту того, когда я перечитал свое сообщение и обнаружил неточности, возможность редактирования уже была утеряна.
Опять же, нет. В ФП достаточно много перегруженных терминов.
List — это алгебраический тип данных. Какие-то упорядоченные данные в лениво вычисляющемся списке с произвольным доступом.
List — это функтор. Какие-то недетерминированно размазанные данные, реализованные поверх алгебраического типа данных List.
List — это монада. Цепочки вычислений над функтором List.
Потому что автор ничего не понял, и пытается запутать остальных, давая намеренно некорректные утверждения, лишенные какой-либо логики.
Нет. То, что вы написали до того, верно.
Это очевидное и правильное понимание. Вы правильно поняли смысл функторов. Вы лифтите функцию
int -> string
, чтобы она работала сMaybe int -> Maybe string
. Вы можете записатьfmap
какfmap (a -> b) -> (f a -> f b)
.Обычный функтор берет чистую функцию и преобразовывает ее так, чтобы она принимала обернутые значения.
Аппликативный функтор берет обернутую функцию и преобразовывает ее так, чтобы она принимала обернутые значения. Т.е. аппликативный функтор лифтит функции, не разворачивая их.
А она и не контейнер. Она, как вы верно заметили, вычисление. То, что некоторые монады могут быть представлены как операции над контейнерами (хоть это, имхо, и сбивающая с толку аналогия, приносящая больше вреда), не делает саму монаду контейнером.
Если рассматривать неверные аналогии, то функтор куда ближе к контейнеру, чем монада.
В статье столько ошибок, что даже и не знаешь, с чего начать.
Категории — это не типы. Это, если уж пытаться натянуть сову на глобус, системы типов. Или не типов. В категории Set объектами (базовыми элементами категорий) являются множества разных элементов, которые в каком-то смысле могут быть типами (множество всех целых чисел, множество всех строк и т.д.). В категории Hask (которая не совсем категория, но это вопрос практического свойства) базовыми элементами являются типы хаскеля. Конечно, можно создать категорию Double, где объектами будут литералы, а морфизмы будут задавать преобразования между этими литералами, но что это даст?
Эндоморфизмы — это не "тип в себя", это морфизм в пределах одной категории. Преобразование из string в int вполне себе эндоморфизм. Преобразование из Maybe в Maybe тоже эндоморфизм.
Монады — это не контейнеры. Как пример, есть монады IO и Reader не являются "контейнерами", в которые можно что-то положить. Монады — это абстракция вычислений в контексте. Если уж хотите использовать аналогии, то ближайшее к монаде, что можно придумать из "обычного" мира — это Promise. Вы связываете промисы в цепочки вычислений, и каждое вычисление находится в контексте того, что оно случится когда-то. И, как забавное следствие, вы не можете просто взять и избавиться от Promise, если уж вы начали его использовать.
На самом деле Promise не монада, но вполне могла бы ей быть.
Функтор — это не обработчик данных. Функтор — это способ задать контекст вычислений для значения. С т.з. теорката — это функция над объектами и морфизмами, которая преобразовывает их из текущей категории в, возможно, новую категорию. Или в ту же, если это эндоморфизм. Если брать промисы как пример, то функтор на промисах работает так:
Promise<int>
,intToStr(x: int): string
(чистая функция)Promise<string>
.Если бы это была монада, то это выглядело бы так:
Promise<int>
,intToStr(x: int): Promise<string>
(монадическая функция, может делать внутри async/await)Promise<string>
Аппликативный функтор просто рассчитывает, что функция, которую нужно применить к значению в контексте, тоже находится в контексте.
Т.е., опять же, используя промисы как пример, у нас есть
Promise<(x: int) -> string>
иPromise<int>
. Аппликативный функтор дает возможность применитьPromise<(x: int) -> string>
кPromise<int>
и получитьPromise<string>
.Что касается использования List как примера функторов и монад, и вытекающие отсюда неправильные представления о том, что это контейнеры, а монады и функторы работают с контейнерами. Нет. List не является массивом, List является абстракцией недетерминированных вычислений. То, что List можно вычислить, представив как массив, это лишь "ложный друг переводчика". Значение
List[1,2,2,3]
на самом деле описывает недетерминированное значение, которое с вероятностью 25% равно 1, с вероятностью 25% равно 3 и с вероятностью 50% равно 2.Суммируя: вы ничего не поняли, но уже пошли объяснять остальным.
Если вдруг решите оптимизировать по-настоящему, то посмотрите на эту статью — https://habr.com/ru/post/180135/
лови нациста-супрематиста!
Бело-золотое, это любому очевидно.
Ну так и почему?
Только у кого надо.
Да, конечно. Разница между ними, как вы заметили, в преднамеренности и, как следствие, в наличии секретной компоненты для противодействия преднамеренности.
Если искажение непреднамеренное (например, случайно флипнулся бит в оперативке, или в ходе передачи по сети) — оно с некоторой равной вероятностью случится в любом бите контента, и построение статистической модели такого искажения не выявит зависимости положения искажения от содержимого измененного бита или его соседей.
Для обнаружения таких случайных модификаций достаточно самых простых мер — раньше, например, для очень небольших пакетов (7-10 бит) использовались биты четности (суммирование по полиному x+1). Для данных большего размера хорошо подходят CRC32 и CRC64, которые тоже суммирование по полиному, просто большего порядка.
Для исправления таких искажений достаточно использовать какие-нибудь избыточные коды, те же коды Рида-Соломона являются очень популярным выбором и позволяют настраивать максимальное количество исправляемых и обнаруживаемых искажений.
Почему CRC и коды Рида-Соломона нельзя использовать для контроля целостности? Активный атакующий, цель которого — выдать свои данные за данные изначального отправителя, уже не вписывается в модель случайного шума, меняющего биты, т.к. его изменения зависят от положения в контенте и от значений конкретных бит соседей (например, атакующий ищет
"authorized": false,
и хочет поменять это на"authorized": true ,
). Почему CRC и RS не подходят для защиты от таких изменений? Очевидно, что так как эти алгоритмы не содержат в себе секретной компоненты, атакующий может просто посчитать CRC и RS заново. Даже если используется нестандартный полином, не представляет проблемы его отреверсить при достаточно большом количестве наблюдаемого открытого текста — ни CRC, ни RS не являются криптографически безопасными примитивами, и не обеспечивают защиту от преднамеренной модификации.Для контроля же целостности можно использовать HMAC — семейство функций с секретным симметричным ключом, основанные на криптографических хэш-функциях. Пересчитать HMAC повторно, даже зная низлежащую функцию (SHA3-256, например), не зная ключа, невозможно, а HMAC безопасен ровно настолько, насколько безопасна низлежащая функция. Поэтому если низлежащая функция (SHA1) уязвима к второй preimage атаке (для оригинального текста
x
найти такойx'
, которыйsha1(x) == sha1(x')
приx != x'
), то и HMAC-подпись небезопасна. Для CRC же поиск такого поддельного текста тривиален.Как вариант, может использоваться асимметричная подпись хэша. В таком случае атакующий может пересчитать хэш (его алгоритм известен), но не сможет подделать для него правильную подпись, не восстановив секретную часть ключа.
А что сделают недовольные, напишут гневный твит? Нормально все будет, вот увидите, когда границы таки закроют :)
Ну как раз тут речь и идет о том, что для SHA1 правило "один хэш — один контент" больше не выполняется? Какая разница, преднамеренно или нет.
И если вам так любы и дороги устаревшие небезопасные хэш-функции, вы всегда можете заиметь себе свою собственную реализацию, которая не будет зависеть от openssl, с которым вы линкуетесь.
Если вам важно соответствие "один хэш — один контент", вам никуда не деться от использования криптографических хэш-функций, потому что их разрабатывают с учетом такого свойства.
Если же вам нужна хэш функция для балансинга, или, например, для построения хэшмапа, то подойдет и SHA1, и RC4-Hash.
Если же вам вообще нужна контрольная сумма для противодействия непреднамеренному искажению (не для контроля целостности) — то и CRC32 хороший вариант.
Все зависит от того, зачем это вам нужно и какие свойства вы ожидаете получить.
Ну, тогда используйте identity function :) Правда, для файла в 10 гб идентификатор будет все те же 10 гб, потому что это и будет сам файл, но что стоят такие маленькие неудобства?
Например, BLAKE3. Быстрая, длинная и надежная.
Надо бы закрыть границы, чтобы предотвратить отток специалистов. Идея хорошая, в прошлый раз 69 лет границы были временно закрыты, и никаких проблем с программистами.
А каким образом это влияет на написанное? Глупость написал не автор deno, а автор этого поста. И то, что у автора ноды получилось в первый раз сделать что-то хорошее, не дает гарантий того, что у него это получится во второй раз. Вот, deno тому ярким примером — хуже только php.