Я читал статью здесь: http://www.paulgraham.com/avg.html, и часть о "парадоксе пузыря" была особенно интересной. Как человек, который в основном кодирует на c ++, но имеет знакомство с другими языками (в основном Haskell), я знаю о некоторых полезных вещах на этих языках, которые трудно воспроизвести в c ++. Вопрос в основном для тех, кто владеет как c ++, так и другим языком. Существует ли какая-то мощная языковая функция или идиома, которую вы используете в языке, который было бы трудно осмыслить или реализовать, если бы вы писали только на c ++?
В частности, эта цитата привлекла мое внимание:
По индукции, единственные программисты, которые в состоянии увидеть все различия в мощности между различными языками, - это те, кто понимает самый мощный язык. (Это, вероятно, то, что имел в виду Эрик Рэймонд о том, что Lisp делает вас лучшим программистом.) Вы не можете доверять мнению других из-за парадокса Blub: они довольны тем, каким языком они пользуются, потому что он диктует как они думают о программах.
Если окажется, что я эквивалентен программисту "Blub" благодаря использованию c ++, то возникает следующий вопрос: существуют ли какие-либо полезные концепции или приемы, с которыми вы столкнулись в других языках, которые вам было бы трудно осмыслить, если бы вы писал или "думал" на с ++?
Например, парадигма логического программирования, наблюдаемая в таких языках, как Prolog и Mercury, может быть реализована в c ++ с использованием библиотеки Castor, но в конечном итоге я нахожу, что концептуально я думаю в терминах кода Prolog и перехожу на эквивалент c ++ при использовании этого. В качестве способа расширения моих знаний в области программирования я пытаюсь выяснить, есть ли другие подобные примеры полезных / мощных идиом, которые более эффективно выражены в других языках, которые я мог бы не знать как разработчик на С ++. Другим примером, который приходит на ум, является макросистема в lisp, генерирующая программный код внутри программы, по-видимому, имеет много преимуществ для некоторых проблем. Кажется, что это трудно реализовать и думать изнутри c ++.
Этот вопрос не предназначен для дебатов типа "c ++ vs lisp" или каких-либо дебатов типа языковых войн. Задавать вопрос, подобный этому, - это единственный способ, которым я вижу возможность узнать о вещах, о которых я не знаю, о которых я не знаю.
источник
there are things that other languages can do that Lisp can't
- Маловероятно, поскольку Лисп завершен по Тьюрингу. Возможно, вы хотели сказать, что есть некоторые вещи, которые не практичны в Лиспе? Я мог бы сказать то же самое о любом языке программирования.Ответы:
Ну, так как вы упомянули Haskell:
Образец соответствия. Я считаю, что сопоставление с образцом намного легче читать и писать. Рассмотрите определение карты и подумайте о том, как она будет реализована на языке без сопоставления с образцом.
Система типов. Иногда это может быть боль, но это очень полезно. Вы должны запрограммировать это, чтобы действительно понять это и сколько ошибок он ловит. Кроме того, ссылочная прозрачность замечательна. После программирования на Haskell на некоторое время становится очевидным, сколько ошибок вызвано управлением состоянием на императивном языке.
Функциональное программирование в целом. Использование карт и складок вместо итерации. Рекурсия. Речь идет о мышлении на более высоком уровне.
Ленивая оценка. Опять же, речь идет о том, чтобы думать на более высоком уровне и позволить системе управлять оценкой.
Кабала, пакеты и модули. Для меня загрузка пакетов Cabal гораздо удобнее, чем поиск исходного кода, написание make-файла и т. Д. Возможность импортировать только определенные имена намного лучше, чем, по сути, скомпоновать все исходные файлы, а затем скомпилировать.
источник
Maybe
(для C ++std::optional
), а о том, чтобы явно пометить вещи как необязательные / nullable / возможно.Memoize!
Попробуйте написать это на C ++. Не с C ++ 0x.
Слишком громоздко? Хорошо, попробуйте это с C ++ 0x.
Посмотрите, сможете ли вы превзойти эту 4-строчную (или 5-строчную, что угодно: P) версию во время компиляции в D:
Все, что вам нужно сделать для вызова, это что-то вроде:
Вы также можете попробовать что-то подобное в Scheme, хотя это немного медленнее, потому что это происходит во время выполнения и потому что поиск здесь является линейным, а не хешированным (и хорошо, потому что это Scheme):
источник
C ++ - это мультипарадигмальный язык, что означает, что он пытается поддерживать множество способов мышления. Иногда функция C ++ является более неловкой или менее беглой, чем реализация на другом языке, как в случае с функциональным программированием.
Тем не менее, я не могу отмахнуться от своей головы о нативной функции языка C ++, которая делает то, что делает
yield
в Python или JavaScript.Другой пример - параллельное программирование . C ++ 0x будет иметь право голоса по этому поводу, но текущий стандарт не имеет, и параллелизм - это совершенно новый способ мышления.
Кроме того, быстрая разработка - даже программирование оболочки - это то, что вы никогда не узнаете, если не покинете область программирования на C ++.
источник
setjmp
иlongjmp
. Я понятия не имею, сколько это ломает, но я думаю, что исключения будут первыми. Теперь, если вы меня извините, мне нужно перечитать Modern C ++ Design, чтобы выбросить это из головы.Сопрограммы - чрезвычайно полезная языковая функция, которая лежит в основе многих более ощутимых преимуществ других языков по сравнению с C ++. Они в основном предоставляют дополнительные стеки, так что функции могут быть прерваны и продолжены, предоставляя языку средства, подобные конвейеру, которые легко передают результаты операций через фильтры для других операций. Это замечательно, и в Ruby я нашел его очень интуитивным и элегантным. Ленивая оценка также связана с этим.
Самоанализ и компиляция / выполнение / оценка кода во время выполнения - все это чрезвычайно мощные функции, которых нет в C ++.
источник
Внедрив систему компьютерной алгебры как на Лиспе, так и на С ++, я могу вам сказать, что в Лиспе задача была намного проще, хотя я был полным новичком в этом языке. Эта упрощенная природа всех списков упрощает множество алгоритмов. Конечно, версия C ++ была в миллионы раз быстрее. Да, я мог бы сделать версию для lisp быстрее, но код не был бы таким же странным. Сценарии - это еще одна вещь, которая всегда будет проще, например, lisp. Все дело в использовании правильного инструмента для работы.
источник
Что мы имеем в виду, когда говорим, что один язык «более мощный», чем другой? Когда мы говорим, что язык «выразителен»? Или "богатый?" Я думаю, мы имеем в виду, что язык приобретает силу, когда его поле зрения достаточно сужается, чтобы можно было легко и естественно описать проблему - на самом деле переход состояния, не так ли? - это живет в этом представлении. Тем не менее, этот язык значительно менее мощный, менее выразительный и менее полезный, когда наше поле зрения расширяется.
Чем более «мощный» и «выразительный» язык, тем более ограничено его использование. Так что, возможно, «мощный» и «выразительный» не те слова, которые нужно использовать для инструмента узкой полезности. Может быть, «соответствующие» или «абстрактные» - более подходящие слова для таких вещей.
Я начал с программирования, написав целую кучу низкоуровневых вещей: драйверы устройств с их процедурами прерывания; встроенные программы; код операционной системы. Код был близок к оборудованию, и я написал все это на языке ассемблера. Мы бы не сказали, что ассемблер является наименее абстрактным, но это был и является самым мощным и выразительным языком из всех. Я могу выразить любую проблему на ассемблере; это настолько мощно, что я могу делать все что угодно с любой машиной.
И все мое позднее понимание языка более высокого уровня обязано всем моему опыту с ассемблером. Все, что я узнал позже, было легко, потому что, видите ли, все - независимо от того, насколько абстрактно - должно в конце концов приспособиться к аппаратному обеспечению.
Возможно, вы захотите забыть о более высоких уровнях абстракции, то есть о более узких и узких полях зрения. Вы всегда можете подобрать это позже. Это совсем несложно узнать за считанные дни. На мой взгляд, было бы лучше выучить аппаратный язык 1 , чтобы быть как можно ближе к костям.
1 Возможно, не совсем уместно, но
car
иcdr
берут их имена из аппаратного обеспечения: первый Лисп работал на машине, у которой был действительный регистр декремента и реальный адресный регистр. Как насчет этого?источник
Ассоциативные массивы
Типичный способ обработки данных:
Правильный инструмент для этого - ассоциативный массив .
Мне не очень нравится синтаксис ассоциативных массивов JavaScript, потому что я не могу создать, скажем, [x] [y] [z] = 8 , сначала я должен создать [x] и a [x] [y] .
Хорошо, в C ++ (и в Java) есть хороший набор контейнерных классов, Map , Multimap , но если я хочу просмотреть, мне нужно сделать итератор, и когда я хочу вставить новый элемент более высокого уровня, я приходится создавать все верхние уровни и т. д. неудобно.
Я не говорю, что в C ++ (и Java) нет пригодных для использования ассоциативных массивов, но несимметричные (или не строго типизированные) языки сценариев превосходят скомпилированные, потому что они являются типизированными языками сценариев.
Отказ от ответственности: я не знаком с C # и другими языками .NET, AFAIK, они имеют хорошую ассоциативную обработку массивов.
источник
dict
тип (напримерx = {0: 5, 1: "foo", None: 500e3}
, обратите внимание, что не требуется, чтобы ключи или значения были одного типа). Попытка сделать что-то подобноеa[x][y][z] = 8
сложно, потому что язык должен смотреть в будущее, чтобы увидеть, собираетесь ли вы установить значение или создать другой уровень; выражениеa[x][y]
само по себе не говорит вам.Я не изучаю Java, C \ C ++, Assembly и Java Script. Я использую C ++, чтобы зарабатывать на жизнь.
Хотя я бы сказал, что мне больше нравится программирование на ассемблере и Си. Это в основном связано с императивным программированием.
Я знаю, что парадигмы программирования важны для категоризации типов данных и дают более абстрактные концепции программирования, позволяющие создавать мощные шаблоны проектирования и формализовать код. Хотя в некотором смысле каждая парадигма представляет собой набор шаблонов и коллекций для абстрагирования базового аппаратного уровня, поэтому вам не нужно думать о EAX или IP-адресе внутри компьютера.
Моя единственная проблема в том, что это позволяет людям и представлениям о том, как работает машина, превращаться в идеологию и неоднозначные утверждения о том, что происходит. В этом хлебе есть множество замечательных абстракций, а не только абстракции к какой-то идеологической цели программиста.
В конце концов, лучше иметь четкое мышление и границы того, что такое процессор и как компьютеры работают под капотом. Все, о чем заботится центральный процессор, - это выполнение серии инструкций, которые перемещают данные в память и из памяти в регистр и выполняют инструкцию. Он не имеет понятия типа данных или каких-либо более высоких понятий программирования. Это только перемещает данные.
Это становится более сложным, когда вы добавляете парадигмы программирования в микс, потому что все наши взгляды на мир различны.
источник
C ++ делает многие подходы неразрешимыми. Я бы даже сказал, что большую часть программирования сложно осмыслить, если вы ограничиваете себя C ++. Вот несколько примеров проблем, которые гораздо легче решить способами, которые C ++ делает сложными.
Распределение регистра и соглашения о вызовах
Многие люди думают о C ++ как о низкоуровневом низкоуровневом языке, но на самом деле это не так. Абстрагируя важные детали машины, C ++ усложняет концептуальные практические аспекты, такие как распределение регистров и соглашения о вызовах.
Чтобы узнать о таких понятиях, как я, я рекомендую ознакомиться с программированием на ассемблере и ознакомиться с этой статьей о качестве генерации кода ARM .
Генерация кода во время выполнения
Если вы знаете только C ++, вы, вероятно, думаете, что шаблоны - это начало и конец всего метапрограммирования. Это не так. На самом деле, они объективно плохой инструмент для метапрограммирования. Любая программа, которая манипулирует другой программой, является метапрограммой, включая интерпретаторы, компиляторы, системы компьютерной алгебры и средства доказательства теорем. Генерация кода во время выполнения является полезной функцией для этого.
Я рекомендую
EVAL
запустить реализацию Схемы и поиграть с ней, чтобы узнать о метациркуляционной оценке.Манипулирование деревьями
Деревья везде в программировании. При разборе у вас есть абстрактные синтаксические деревья. В компиляторах у вас есть IR, которые являются деревьями. В графике и программировании GUI у вас есть деревья сцены.
Этот «смешной простой JSON Parser для C ++» весит всего 484 LOC, что очень мало для C ++. Теперь сравните его с моим собственным простым парсером JSON, который весит всего 60 лок F #. Разница заключается, прежде всего, в том, что алгебраические типы данных ML и сопоставление с образцом (включая активные рисунки) значительно упрощают манипулирование деревьями.
Посмотрите на красно-черные деревья в OCaml .
Чисто функциональные структуры данных
Отсутствие GC в C ++ делает практически невозможным принятие некоторых полезных подходов. Чисто функциональные структуры данных являются одним из таких инструментов.
Например, посмотрите этот 47-строчный сопоставитель регулярных выражений в OCaml. Краткость объясняется в основном широким использованием чисто функциональных структур данных. В частности, использование словарей с ключами, которые установлены. Это действительно трудно сделать в C ++, потому что словари и наборы stdlib являются изменяемыми, но вы не можете изменить ключи словаря или разбить коллекцию.
Логическое программирование и буферы отмены - другие практические примеры, когда чисто функциональные структуры данных делают что-то сложное в C ++ действительно простым в других языках.
Хвостовые звонки
Мало того, что C ++ не гарантирует хвостовые вызовы, RAII принципиально расходится с этим, потому что деструкторы мешают вызову в хвостовой позиции. Хвостовые вызовы позволяют вам совершать неограниченное количество вызовов функций, используя только ограниченное количество стекового пространства. Это отлично подходит для реализации конечных автоматов, в том числе расширяемых конечных автоматов, и это отличная карта "выхода из тюрьмы" во многих иначе неловких обстоятельствах.
Например, проверьте эту реализацию задачи о ранце 0-1, используя стиль передачи продолжения с памяткой в F # из финансовой индустрии. Когда у вас есть хвостовые вызовы, стиль передачи продолжения может быть очевидным решением, но C ++ делает его неразрешимым.
совпадение
Другой очевидный пример - параллельное программирование. Хотя это вполне возможно в C ++, он чрезвычайно подвержен ошибкам по сравнению с другими инструментами, особенно в том, что касается последовательных процессов, как это видно в таких языках, как Erlang, Scala и F #.
источник
Это старый вопрос, но так как никто не упомянул об этом, я добавлю список (и теперь буду диктовать) понимания. Легко написать однострочную версию на Haskell или Python, которая решает проблему Fizz-Buzz. Попробуйте сделать это в C ++.
В то время как C ++ сделал огромный шаг к современности с C ++ 11, называть его «современным» языком довольно сложно. C ++ 17 (который еще не выпущен) делает еще больше шагов, чтобы приблизиться к современным стандартам, при условии, что «современный» означает «не из предыдущего тысячелетия».
Даже самые простые из представлений, которые можно написать в Python одной строкой (и соблюдая ограничение длины строки в 79 символов Гвидо), становятся множеством строк кода при переводе на C ++, и некоторые из этих строк кода C ++ довольно запутаны.
источник
Скомпилированная библиотека, вызывающая функцию обратного вызова, которая является определенной пользователем функцией-членом определенного пользователем класса.
Это возможно в Objective-C, и это делает программирование пользовательского интерфейса быстрым. Вы можете сказать кнопке: «Пожалуйста, вызовите этот метод для этого объекта, когда вы нажмете», и кнопка сделает это. Вы можете использовать любое имя метода для обратного вызова, которое вам нравится, оно не зафиксировано в коде библиотеки, вам не нужно наследовать от адаптера, чтобы он работал, и компилятор не хочет разрешать вызов во время компиляции, и, что не менее важно, вы можете сказать двум кнопкам вызывать два разных метода одного и того же объекта.
Я еще не видел столь же гибкого способа определения обратного вызова на любом другом языке (хотя мне было бы очень интересно узнать о них!). Наиболее близким эквивалентом в C ++, вероятно, является передача лямбда-функции, которая выполняет требуемый вызов, что снова ограничивает код библиотеки в качестве шаблона.
Именно эта особенность Objective-C научила меня ценить способность языка свободно передавать любые типы объектов / функций / все, что важно в концепции языка, а также способность сохранять их для переменные. Любая точка в языке, которая определяет любой тип концепции, но не предоставляет средства для ее хранения (или ссылки на нее) во всех доступных типах переменных, является существенным камнем преткновения и, вероятно, источником многих уродливых, дублированный код. К сожалению, языки программирования в стиле барокко, как правило, имеют ряд таких моментов:
В C ++ вы не можете записать тип VLA или сохранить указатель на него. Это эффективно запрещает истинные многомерные массивы динамического размера (которые доступны в C начиная с C99).
В C ++ вы не можете записать тип лямбды. Вы не можете даже определить это. Таким образом, нет способа обойти лямбду или сохранить ссылку на нее в объекте. Лямбда-функции можно передавать только в шаблоны.
В Фортране вы не можете записать тип списка имен. Просто нет возможности передать список имен какой-либо рутине. Итак, если у вас есть сложный алгоритм, который должен обрабатывать два разных списка имен, вам не повезло. Вы не можете просто написать алгоритм один раз и передать ему соответствующие списки имен.
Это всего лишь несколько примеров, но вы видите общее: всякий раз, когда вы видите такое ограничение в первый раз, вам, как правило, все равно, потому что кажется, что делать запретную вещь - это такая безумная идея. Однако, когда вы занимаетесь серьезным программированием на этом языке, вы, в конце концов, приходите к тому, что это точное ограничение становится настоящей неприятностью.
источник
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!)
То, что вы только что описали, звучит точно так же, как работает управляемый событиями код пользовательского интерфейса в Delphi. (И в .NET WinForms, на которую сильно повлиял Delphi.)std::vector
. Хотя он немного менее эффективен из-за того, что не использует выделение стека, он функционально изоморфен VLA, поэтому на самом деле не считается проблемой типа «blub»: программисты на C ++ могут посмотреть, как это работает, и просто сказать: «ах, да , C делает это более эффективно, чем C ++ ".std::function
.object::method
и он будет преобразован в экземпляр любого интерфейса, который ожидает принимающий код. C # имеет делегатов. У каждого объектно-функционального языка есть эта особенность, потому что это в основном точка сечения двух парадигм.