Но и в чисто прикладных вещах этот тезис получает своё подтверждение. Лично мне на ум приходит понятие итераторов (на всякий случай: сказанное ниже в основном относится к external iterator). Казалось бы, операции типа map, filter и fold/reduce куда проще определить непосредственно на коллекциях (и, собственно, в Javanoscript и Kotlin так и делают). Понятие итератора уже является некоторой абстракцией, полезность которой не ясна с первого взгляда, а уж комбинатор итераторов является абстракцией высшего порядка: он принимает итератор на вход и выдаёт итератор на выход. Казалось бы, к чему эти усложнения, почему нельзя обойтись монолитными операциями? В пользу итераторов есть несколько аргументов (впрочем, сильно завязанных друг на друга):
Ленивость: итератор не делает ничего, пока его о чём-то не попросят. Ленивость позволяет обрабатывать данные из источников, которые в развёрнутом виде не влезают в память — либо потому, что они в принципе бесконечны, либо потому, что банально слишком крупные для этого (файл на диске запросто может быть слишком велик, чтобы уместиться в свободное место в оперативке).
Выразительность: с помощью итераторов можно выразить то, что невозможно выразить на ограниченном наборе монолитных операций. Например, имея на руках map, невозможно в его терминах выразить операцию zip, для которой требуется перебирать последовательности синхронно друг с другом. Имея абстракцию итераторов, можно написать zip самостоятельно, в случае же с монолитными операциями мы вынуждены внести в набор примитивных операция zip... Для каждой пары коллекций! Другой пример — раннее прерывание итерации по какому-то условию. Опять-таки, вызывающий код контролирует, когда итератор выдаёт элементы, и потому ничто не мешает ему самостоятельно принимать решение, когда итерацию прерывать (и начинать ли её вовсе). В случае с монолитными операциями нет никакой возможности вклиниться в процесс итерации. Также можно упомянуть операции над данными, которые материализуются на лету, например, вычисляются или считываются по сети. Для таких источников операция map, сохраняющая тип "коллекции", попросту не имеет смысла, итераторы же работают с этим без проблем.
Расширяемость: мы (как правило) не можем переписать реализацию методов для чужих типов или добавлять свои (а даже если и можем, это, как правило, считается дурным тоном). Выделение же промежуточной абстракции позволяет расширять итераторные операции: как за счёт добавления преобразований в итераторы для существующих типов, так и за счёт добавления новых комбинаторов. С монолитными операциями добавление новых комбинаторов, равно как и итерируемых объектов, требует линейного количества работы — по реализации метода для каждой из уже итерируемой коллекции в первом случае и по реализации каждого из уже имеющихся методов для новой коллекции во втором. В случае же с итераторами количество работы при расширении таблицы в обе стороны константно.
Производительность: обработка по одному элементу за раз при достаточном количестве данных, требуемых обработки, требует меньше ресурсов, чем аналогичные монолитные методы, выделяющие по коллекции на каждый промежуточный шаг.
===
В заключение хотелось бы привести пример, когда этот принцип, судя по всему, не выполняется: аксиоматика евклидовой геометрии. Оригинальный набор аксиом, записанный Евклидом в "Началах", был избыточен, и со временем его удалось сократить. Однако пятый постулат, который упорно сопротивлялся попыткам быть доказанным (и, как оказалось, неспроста — в процессе математики открыли неевклидовы геометрии), во многом выглядел как что-то, что можно доказать, из-за крайне громоздкой формулировки самого Евклида:
И если прямая, падающая на две прямые, образует внутренние и по одну сторону углы, меньшие двух прямых, то продолженные неограниченно эти прямые встретятся с той стороны, где углы меньше двух прямых.
В современном изложении планиметрии эту аксиому обычно заменяют эквивалентной аксиомой о параллельной прямой:
В плоскости через точку, не лежащую на данной прямой, можно провести одну и только одну прямую, параллельную данной.
Ленивость: итератор не делает ничего, пока его о чём-то не попросят. Ленивость позволяет обрабатывать данные из источников, которые в развёрнутом виде не влезают в память — либо потому, что они в принципе бесконечны, либо потому, что банально слишком крупные для этого (файл на диске запросто может быть слишком велик, чтобы уместиться в свободное место в оперативке).
Выразительность: с помощью итераторов можно выразить то, что невозможно выразить на ограниченном наборе монолитных операций. Например, имея на руках map, невозможно в его терминах выразить операцию zip, для которой требуется перебирать последовательности синхронно друг с другом. Имея абстракцию итераторов, можно написать zip самостоятельно, в случае же с монолитными операциями мы вынуждены внести в набор примитивных операция zip... Для каждой пары коллекций! Другой пример — раннее прерывание итерации по какому-то условию. Опять-таки, вызывающий код контролирует, когда итератор выдаёт элементы, и потому ничто не мешает ему самостоятельно принимать решение, когда итерацию прерывать (и начинать ли её вовсе). В случае с монолитными операциями нет никакой возможности вклиниться в процесс итерации. Также можно упомянуть операции над данными, которые материализуются на лету, например, вычисляются или считываются по сети. Для таких источников операция map, сохраняющая тип "коллекции", попросту не имеет смысла, итераторы же работают с этим без проблем.
Расширяемость: мы (как правило) не можем переписать реализацию методов для чужих типов или добавлять свои (а даже если и можем, это, как правило, считается дурным тоном). Выделение же промежуточной абстракции позволяет расширять итераторные операции: как за счёт добавления преобразований в итераторы для существующих типов, так и за счёт добавления новых комбинаторов. С монолитными операциями добавление новых комбинаторов, равно как и итерируемых объектов, требует линейного количества работы — по реализации метода для каждой из уже итерируемой коллекции в первом случае и по реализации каждого из уже имеющихся методов для новой коллекции во втором. В случае же с итераторами количество работы при расширении таблицы в обе стороны константно.
Производительность: обработка по одному элементу за раз при достаточном количестве данных, требуемых обработки, требует меньше ресурсов, чем аналогичные монолитные методы, выделяющие по коллекции на каждый промежуточный шаг.
===
В заключение хотелось бы привести пример, когда этот принцип, судя по всему, не выполняется: аксиоматика евклидовой геометрии. Оригинальный набор аксиом, записанный Евклидом в "Началах", был избыточен, и со временем его удалось сократить. Однако пятый постулат, который упорно сопротивлялся попыткам быть доказанным (и, как оказалось, неспроста — в процессе математики открыли неевклидовы геометрии), во многом выглядел как что-то, что можно доказать, из-за крайне громоздкой формулировки самого Евклида:
И если прямая, падающая на две прямые, образует внутренние и по одну сторону углы, меньшие двух прямых, то продолженные неограниченно эти прямые встретятся с той стороны, где углы меньше двух прямых.
В современном изложении планиметрии эту аксиому обычно заменяют эквивалентной аксиомой о параллельной прямой:
В плоскости через точку, не лежащую на данной прямой, можно провести одну и только одну прямую, параллельную данной.
MDN Web Docs
Array.prototype.map() - JavaScript | MDN
The map() method of Array instances creates
a new array populated with the results of calling a provided function on
every element in the calling array.
a new array populated with the results of calling a provided function on
every element in the calling array.
👍4🔥1
Хоспаде, изначально я хотел этот пост написать сразу после публикации того видео от 3b1b. Почему на это ушла целая неделя?
#prog #php #cpp #article
Как мы наш большой проект на KPHP мигрировали
Безумству храбрых поём мы песню
Как мы наш большой проект на KPHP мигрировали
Безумству храбрых поём мы песню
Хабр
Как мы наш большой проект на KPHP мигрировали
История о том, как мы мигрировали нашу систему управления проектами на KPHP. Если у вас есть PHP-проект с длинной историей и вы хотите запуститься на KPHP для получения выгод, то приготовьтесь! Будет...
😱5🔥2
Блог*
Делать ли отдельный хештег под неочевидные способы потерять производительность?
Сделал, performancetrap
🔥5🤨3🤯1🤮1
#prog #rust #rustlib
smart-default
Custom derive for automatically implementing the Default trait with customized default values:
smart-default
Custom derive for automatically implementing the Default trait with customized default values:
#[macro_use]
extern crate smart_default;
#[derive(SmartDefault)]
enum Foo {
Bar,
#[default]
Baz {
#[default = 12]
a: i32,
b: i32,
#[default(Some(Default::default()))]
c: Option<i32>,
#[default(_code = "vec![1, 2, 3]")]
d: Vec<u32>,
#[default = "four"]
e: String,
},
Qux(i32),
}
assert_eq!(
Foo::default(),
Foo::Baz {
a: 12,
b: 0,
c: Some(0),
d: vec![1, 2, 3],
e: "four".to_owned(),
},
);👍9🤔3👎2🥴2😁1
Forwarded from Питонические атаки
Оказывается, механизм разрешения зависимостей из Poetry можно использовать для решения судоку. И судоку, и разрешение зависимостей — это задачи удовлетворения ограничений (constraint satisfaction problem), поэтому достаточно лишь записать правила судоку в виде пакетов с зависимостями и заставить Poetry это установить. А если добавить флажки для подробного вывода, то Poetry еще по пути будет объяснять, почему он решает судоку именно так. Офигенно!
Статья | Тред на реддите
Статья | Тред на реддите
🔥17👍2❤1👎1
#prog #parsing #rust #rustlib
Chumsky
A parser library for humans with powerful error recovery.
Пример парсера brainfuck:
Chumsky
A parser library for humans with powerful error recovery.
Пример парсера brainfuck:
use chumsky::prelude::*;
#[derive(Clone)]
enum Instr {
Left, Right,
Incr, Decr,
Read, Write,
Loop(Vec<Self>),
}
fn parser() -> impl Parser<char, Vec<Instr>, Error = Simple<char>> {
recursive(|bf| {
choice((
just('<').to(Instr::Left),
just('>').to(Instr::Right),
just('+').to(Instr::Incr),
just('-').to(Instr::Decr),
just(',').to(Instr::Read),
just('.').to(Instr::Write),
bf.delimited_by(just('['), just(']')).map(Instr::Loop),
))
.repeated()
})
}GitHub
GitHub - zesterer/chumsky: Write expressive, high-performance parsers with ease.
Write expressive, high-performance parsers with ease. - zesterer/chumsky
🔥12
Блог*
#prog #parsing #rust #rustlib Chumsky A parser library for humans with powerful error recovery. Пример парсера brainfuck: use chumsky::prelude::*; #[derive(Clone)] enum Instr { Left, Right, Incr, Decr, Read, Write, Loop(Vec<Self>), } …
Пример сообщения о синтаксической ошибке для разрабатываемого этим же человеком своего языка программирования.
🤯30