редполагается, что полученные навыки вы потом сможете перенести на любой язык (или написать свой). А практические задачи лучше решать на практических языках — CL и Clojure (которая сама может использовать все Java библиотеки, куда уж больше?).
значит Scheme мне не подходит, мне практические задачи решать.
По-моему, наоборот. Лучше сделать SICP первой книгой по программированию, там очень мягко и ненапряжно вводятся самые базовые концепции без лишних деталей, что позволяет вообще понять что такое программирование и зачем.
В программировании я не новичек, а вот с лиспами никогда не пересекался.
Как из схемы работать с базой данных? Тот же Postgres?
Куча ORM и всякого есть
итд
Касательно схемы - опять же, не смотри в сторону схемы, это язык для обучения и не более. Там в реализациях и библиотеках такой разброд и шатание что повеситься можно
библиотека вроде есть, но она в статусе alpha, но по крайней мере хоть что-то.
А вот для clojure есть aws-api причем похоже от компании в которой работал создатель clojure.
Посмотрел описание и документацию по Racket - выглядит как очень удобный учебный язык. Вряд ли подойдет.
Racket родился как учебный, но нынче полнофункциональный язык общего назначения. Примерно как Паскаль был изначально учебный, а потом из него сделали Delphi.
И в добавок похоже строки по умолчанию идут в Unicode, что могло бы избавить в случае описанном den73 от проблем с производительностью при перекодировке.
Тогда у меня вопрос - в чем отличие того же Racket (или вообще scheme) от Common Lisp? Что их делает разными языками?
На Common Lisp обычно получается чуть быстрее написать. Но намного сложнее получить гарантии надёжной работы. И принято документировать между строк кода.
Причём всё сделано не в стиле, принятом в C и Common Lisp, когда достаточно, чтобы библиотека работала в 90% случаев, а если что не так, то будет UB в C или отладчик в CL.
Если контракты, то с возможностью указать контракт типа «данная функция возвращает функцию от числа, которая всегда возвращает число не меньше переданного первого аргумента»:
Если типизация, то система типов уровня Haskell, а не C++.
Если документация, то с возможностью вывода в HTML и TeX и возможностью вычислять во время формирования текста.
Если макросы, то с гарантией того, что имя снаружи может протечь в результат раскрытия макроса.
Если компиляция, то воспроизводимая. В CL все модули компилируются в одной виртуальной машине и процесс компиляции модуля может влиять на компиляцию следующего. В Racket компиляция каждого модуля в изолированной среде.
В недостатке — чуть ниже производительность (аналога declare unsafe из CL нет) и хуже отладчик.
Scheme мне не подходит, мне практические задачи решать
Для решения практических задач, конечно, нужно углубляться непосредственно в язык, на котором ты будешь их решать. Но кроме конкретных знаний об использовании конкретного языка нужны ещё общие, абстрактные знания о программировании как таковом (как устроены вычислительные процессы и как их можно организовывать), независимо от языка. И здесь уже, понятное дело, конкретный язык особого значения не имеет. Такие знания переносятся на любой язык, который ты уже освоил или захочешь освоить когда-нибудь. ROI просто зашкаливает %)
Такого рода материала полно как раз на схеме — Little/Seasoned/Reasoned Schemer, HTDP, SICP. Так сложилось исторически (тм).
Конечно, немножко схемы выучить придётся, но после кложи/CL это будет изи катка. Проще схемы только наскальная живопись.
И здесь уже, понятное дело, конкретный язык особого значения не имеет. Такие знания переносятся на любой язык, который ты уже освоил или захочешь освоить когда-нибудь.
Вот нет. То есть, конечно, «настоящий программист может программировать на Фортране на любом языке». Но в реальности язык определяет как минимум базовую абстракцию: байтовый массив для Си/Си++, список для лиспов, массив (многомерный) для APL/J/K, бесконечноый ленивый список для Haskell.
В этом смысле Racket также почти уникален, так как на него можно переносить код практически с любого языка не меняя семантики: семантика позволяет работать как с ленивыми списками в стиле Haskell, так и с указателями в стиле Си++.
LoL тоже на С++ замучаешься переводить, но SICP даёт такую базовую базу, что её можно будет применять где угодно.
Взаимоисключающие утверждения. Часть SICP в C++ применить невозможно (макросы, лямбды), часть возможно, но вредно (рекурсия, функции как параметры). При этом такой базовой идеи C++ как итераторы в SICP вообще нет.
Я про С++ знаю мало, но там же есть и то, и другое, разве нет?
Макросы - скорее нет, чем да. Отладки макросов нет, возможности очень ограничены, использовать не рекомендуется. Аналог лисповых макросов — кодогенерация. Но это очень накладно.
Лямбды есть, но из-за отсутствия сборщика мусора также куча ограничений.
Спорно. С чего бы это вредно?
С того, что в Си++ вызов функции — это аппаратный вызов с использованием стека. Причём оптимизация хвостовых вызовов отсутствует. Поэтому если в Си++ написать, как принято в лиспах что-то вроде
int f(int n, int k)
{
if (n) f(n-1, g(n, k)); else return k;
}
то это будет значительно медленнее, чем через нормальный for.
На самом деле, не советую читать SICP – это очень плохая книжка, которую практически невозможно читать как книжку. Зато это очень хороший сборник рецептов и эссе на тему «структуры и интерпретации…», в этом смысле лучше ты не найдешь.
> (list cons car cdr)
(#<procedure:mcons> #<procedure:mcar> #<procedure:mcdr>)
делает список из трех функций
Почему в Common Lisp тот же код выдает ошибку?
> (list cons car cdr)
*** - SYSTEM::READ-EVAL-PRINT: variable CONS has no value
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead of CONS.
STORE-VALUE :R2 Input a new value for CONS.
ABORT :R3 Abort main loop
Break 1 [4]>
при этом эти функции точно определены:
[6]> (cons 1 2)
(1 . 2)
Ведь даже в Go можно передать функцию как параметр по имени:
Я же написал, так сделали, чтобы можно было именовать переменные и функции одинаково.
А это, в свою очередь, сделали, чтобы минимизировать проблемы от негигиеничности макросов. Если имена переменных ещё можно всюду заменять на одноразовые, то если сделать то же самое ещё и с именами функций, код будет нечитаем.
В результате всё равно утечка потенциально возможна. Например, если в (labels ((list ...)) (m ...)) макрос m использует функцию list, то будет использоваться локально определённая. Поэтому в CL просто просят не перекрывать стандартные функции локальными и не использовать в макросах что-то кроме стандартных функций или функций из пакетов, не предназначенных для пользователя библиотеки.
Вот это как раз пример того, где одна и та же задача в CL и Scheme/Racket решена кардинально по-разному. В CL сделано так, чтобы было проще отлаживать. В Scheme/Racket сделано так, чтобы вызов макроса гарантированно делал то, что предполагал разработчик макроса.
Лямбды есть, но из-за отсутствия сборщика мусора также куча ограничений.
Однако, после SICP у человека будет в принципе понимание «что такое лямбды и зачем их придумали». Это важный педагогический результат.
то это будет значительно медленнее, чем через нормальный for.
Зато значительно более выразительно. В итоге, с правильной подготовкой мы получим более образованного специалисты с лучшим кругозором, а не ПТУшника, который выучил парочку приёмов и собирается прожить всю жизнь на этом интеллектуальном багаже.
До макросов я еще не дошел, поэтому не понимаю что такое «негигиеничный». Но похоже что второй namespace был введен для решения проблемы порожденной гибкостью языка. Хотя все равно странно иметь две переменные с одинаковым именем и при этом в одной из них данные, а в другая - функция.
В обоих отлично работает как сверху вниз, так и снизу вверх. Но в CL когда регулярно натыкаешься на невозможность гарантировать корректную работу при любых действиях пользователя, то постепенно начинаешь писать библиотеку в стиле «вот документация как пользоваться, если кто-то сделал что-то не то, сам виноват». И при использовании библиотек, соответственно, периодически приходится лазить в их исходники. Зато если разработчик библиотеки что-то забыл, можно переопределить почти что угодно в коде, использующем библиотеку.
В Racket, наоборот. Если что-то не так, то только менять исходный текст библиотеки. Но разработчик библиотеки может гарантировать, что она работает корректно. Например, если есть библиотека на Racket и конструктор структуры устанавливает какое-либо поле и не экспортируется функция изменения этого поля, то при получении данных от пользователя библиотеки можно быть уверенным что это поле заполнено.
Я исхожу из логики «если есть, но неудобно и сложно, значит нету». В пределе defmacro и #define делают одно и то же, но в одном случае макропрограммирование считается удобным подспорьем, а в другом — кошмаром, который следует избегать любой ценой. И эти практические выводы гораздо важнее теоретической применимости.