Pull to refresh

Comments 85

много инженеров потратило десятки человеко-лет на разработку виртуальной машины и достигли скоростей, превышающих Си++


А можно ссылку на замеры? Или это просто так сказано, чтобы похвалить JVM?
На мой взгляд: не стоит начинать программировать с языка Scala.

Причины:

У скалы один из самых сложных компиляторов

важно знать как устроена виртуальная машина джава, и учитывать ее особенности. Например, такие как Type Erasure

появилось типизированное абстрактное синтаксическое дерево компилятора

Объектно-ориентированное программирование

Функциональное программирование

умение пользоваться иммутабельными структурами данных

безопасный многопоточный код

гибкие и красивые DSL, вкладывать абстракции друг в друга


ну и самое главное

вакансий для скала-программистов значительно меньше, чем для джава-программистов
Зато много вакансий спарк программистов. И хорошие зарплаты.
Не, на самом деле тут есть рациональное зерно. Я бы так сказал — скала в качестве первого языка требует наличия хорошего преподавателя. Чтобы правильно спланировать обучение и ограничить набор тех вещей, которые обучающемуся нужно запомнить одновременно.
ну то есть мало реализуемо на практике
В смысле таких преподавателей мало? Ну да, пожалуй что так.
UFO just landed and posted this here
Scala слишком сложен для новичков. Лучше для начала хорошо освоить ООП стиль на примере Java или Python. А затем уже смешивать его с функциональным.

Питон — это не про ооп и не про стиль. Из-за гибкости и вседозволенности питона в нём на ура используются подходы, которые в других языках считаются антипаттернами.
1) наследование в питоне есть, но его почти не используют. Из-за динамической типизации очень легко сломать код при наследовании, сложных иерархий не делают.
2) типизация утиная, интерфейсы и абстрактные методы практически бесполезны. Их тоже почти не используют. Теоретически, можно обложиться модулями типа abc, но это не похоже на pythonic way.
3) инкапсуляции нет, давать название с двойным подчёркиванием — просто общепринятая практика, но эти поля всё равно доступны для изменения.
А вот Java реально хороша для изучения — язык прям создан под ООП и он довольно строгий, чтобы заставлять пользователя красивый с точки зрения ООП код.

В чем именно проблема с наследованием в Питоне?
Из-за динамической типизации? Если так, то это легко лечится

3) инкапсуляции нет, давать название с двойным подчёркиванием — просто общепринятая практика, но эти поля всё равно доступны для изменения.

Ну тогда и в джаве её нет, через рефлексию можно достучаться до приватных членов. Зато в JS внутрь замыкания, насколько я знаю, нельзя залезть из самой программы. Вот там инкапсуляция так инкапсуляция!


Более того, иногда эта "инкапсуляция" через модификаторы доступа иногда мешает работать, когда в какой-то библиотеке в классе есть приватный метод, а тебе хочется изменить его работу. Но увы, автор такого не предусмотрел, и ты вынужден оверрайдить другой метод, переписывая иногда пол-класса, чтобы изменить поведение одной строчки.


На самом деле, инкапсуляция и видимость членов класса — это немного разные вещи, хотя многие java/c/c# программисты считают их синонимами. Настоящий смысл инкапсуляции — в сокрытии деталей реализации, а не в невозможности их каким-то образом посмотреть.

Ну тогда и в джаве её нет, через рефлексию можно достучаться до приватных членов

иногда эта «инкапсуляция» через модификаторы доступа иногда мешает работать, когда в какой-то библиотеке в классе есть приватный метод, а тебе хочется изменить его работу.


Нет, совсем не странно, такие вещи писать должно быть вообще стыдно.
Никакие вещи не должно быть стыдно писать.
Человек пишет то, что считает правильным.
Может, он знает/понимает меньше чем Вы, а может — больше (из короткого диалога это скорее всего будет непонятно).
Никакие вещи не должно быть стыдно писать.
рукалицо
Если, допустим, кто-то понимает какую-то тему на порядок глубже, чем Вы, ему, по-Вашему, должно быть стыдно писать своё мнение только потому, что есть риск, что Вы не поймётё до конца его позицию и сочтёте его дураком?
(А может и нет, может, наоборот, он хуже Вас знает. Но это в любом случае должно выясняться аргументами (ну или не выясняться вообще — разошлись при своих мнениях из-за нехватки времени), а не «ай-ай-ай»/«фу-фу-фу» и подобными эмоциональными манипуляциями.)
Просто когда говорят, что «иногда инкапсуляция мешает работать», то возникает вопрос — может человек вообще хочет вернуть GOTO? А на замечание, что это не комильфо, он отвечает «а мне комильфо». Ну, и применив бритву Окама, получаем вывод, что данный персонаж плохо разбирается в теме.
Вы перекручиваете. Человек писал слово инкапсуляция в кавычках, подразумевая, что иногда области видимости мешают работать (и что они не есть настоящая инкапсуляция).

Не стоит додумывать за меня, что я хочу вернуть, а что нет. Я же писал, что инкапсуляция — это больше про сокрытие деталей, нежели про модификаторы доступа.


Публичные модификаторы доступа в некоторых языках, или список экспортов из модулей в js, haskell и др. — это по-сути просто способ описания публичного интерфейса, апи для работы с неким компонентом системы (ну и один из способов реализовать инкапсуляцию, да). Автор класса/модуля таким образом определяет, что потребители могут использовать извне, а что нет. В идеальном мире авторы предусматривают наперёд все возможные случаи и апи у них идеальный. В нашем мире, увы, это не так.


Недавно надо было изменить алгоритм проверки жизни jwt-токена в одной библиотеки для oidc. Пришлось делать форк библиотеки просто потому что авторы не предусмотрели, что эту проверку нужно будет заменить, а вместо этого решили запрятать её глубоко в недра. Было бы там побольше экспортируемых классов — всё обошлось бы малой кровью и небольшим количеством кода. Самое смешное, что в другой библиотеки для работы с oidc такие же проблемы — метод с проверкой токена приватный, так что нужно переписывать почти весь класс, вместо того чтобы поменять 1 строчку.


Я не особо горжусь, что переписал половину библиотеки, чтобы заменить одну простую вещь. Но и ничего стыдного тут не вижу. Я рад за вас, что вы никогда не сталкивались с такими проблемами. Но мне вот пришлось.

>Ну тогда и в джаве её нет
Ну, это все же не совсем одинаковые случаи.
UFO just landed and posted this here
Да, такой вариант тоже может иметь смысл. Главное не смешивать эти 2 стиля до того момента, когда придет понимание их достоинств и ограничений.
Потому что весьма вероятно будет каша. В моей практике обучения скале как раз это обычно вызывало больше всего вопросов. Почему вот тут функция, а вот тут какой-то метод какого-то объекта? Как это логично объяснить, ну кроме того, что автор такой API придумал? А если нельзя логично объяснить — то сложно запомнить.
Ну, не знаю… С моей точки зрения ФП и ООП полноценны только в паре (использовать ООП без ФП или ФП без ООП это как пытаться ходить только левой ногой или только правой — можно, но зачем?). (Точнее говоря, с моей точки зрения, ООП и ФП идут к одному и тому же с разных сторон, т.е. «настоящее ООП»=«настоящее ФП», только не все языки и среды проходят этот путь до конца.)
UFO just landed and posted this here

Их можно в определённой степени совмещать, но в основе своей идеологии (без которой это больше синтаксический сахар, а не цельная парадигма) они всё-таки друг другу противоречат. В ООП объекты существуют ради инкапсулированного состояния, которое может изменяться в ответ на внешние раздражители. ФП, ради ссылочной прозрачности, изменяемого состояния старается избегать.
EDIT: ой, OleksiiVoropai уже описал это подробнее ниже.

Ну, вот я как раз их обоих воспринимаю как синтаксический сахар над классическим процедурным программированием.
Не вижу причин ударяться в какую-то «метафизику» на пустом месте.
Всё есть синтаксический сахар над последовательностью нулей и единиц, но вы же все-таки предпочитаете писать на высокоуровневых языках программирования?
ИМХО: высокоуровневые языки хороши тем, что (как правило) расширяют возможности, а если они их (безосновательно) сужают, то лучше нафиг.

Сужение возможности стрелять по ногам – это, как правило, очень даже основательно.

Одну и ту же задачу можно реализовать как в ООП так и в функциональном стиле. Грубо говоря, ООП лучше подходит, если приложение ориентированно на данные, функциональный стиль — если на вычисления. Чтобы выбрать правильный стиль для каждой части приложения нужен опыт. Новичку очень трудо будет объяснить эти ньюансы. Ему для начала надо освоить оба этих стиля по отдельности и на практике прочувствовать их ограничения. Только после этого он сможет понять, как их можно смешивать.
Ну, свою точку зрения я уже описал в другом месте: #comment_22220626. ИМХО, не бывает ОО-стиля и функционального стиля, ООП и ФП стремятся к одному и тому же, просто с разных сторон. Просто не везде они дошли до этой условно-идеальной точки, много где остановились на полпути (и вот эти недо-ООП и недо-ФП, увы, многие принимают за эталон). Ну, это моё слабокомпетентное мнение. Могу ошибаться.
Согласно ФП программа будет представлять собой комбинацию функций. Данные в программе будут неизменяемые. Чистая функция не не производит ни каких внешних эффектов, не меняет состояние переменных, просто получает на вход данные и вычисляет результат. Любой вызов функции с одними и теми же переменными приведет к одному и тому же результату, результат вычиления функции зависит только от входных аргументов, но не от внешнего состояния программы.

В ООП программа будет представлять собой комбинацию объектов. Поля объектов хранят его внутреннее состояние. Методы содержат логику поведения объекта — изменения внутреннего состояния и получения доступа к нему. Объекты вызывают методы друг друга динамично меняя свое состояние.

Отличие в том, что программа в ООП стиле имеет внутреннее состояние (переменные и поля объектов), которое меняется по в процессе выполнения программы. Программа в ФП стиле такого состояния не имеет, функции просто трансформируют входные аругменты в выходные. Это фундаментальное отличие этих стилей. Они не сходятся, они существуют и развиваются параллельно. Одну и ту же задачу можно реализовать обоими способами, и структура программы будет разной.

Scala поддерживает оба стиля. Их можно смешивать, часть программы написать в одном стиле, часть в другом, выбирая наиболее удобный для конкретной задачи. Или просто вставить в класс несколько чистых функций, реализовав с помощью ФП какие-то сложные вычисления.
Разве всё это разделение не рушится, когда мы вспоминаем, что в реальном мире существуют указатели и внешняя среда (в частности ОС)?

И тогда мы приходим к средующему обобщению: у функции в общем случае могут быть внешние эффекты, просто зона появления этих эффектов может быть ограничена (в более продуманном языке — самой сигнатурой функции, в менее продуманном — увы, лишь документацией функции), в том числе аж до нуля (т.н. «чистая» функция), а может быть не ограничена (в худшем случае, когда функция воздействует наружу программы, например, в ОС, т.о. зоны внешних эффектов таких функций мы уже не можем разграничить).
Чистые фукции без побочных эффектов имеют целый ряд преимуществ. Их легко тестировать, достаточно сравнить результат с эталоном, окружение не нужно готовить до и очищать после теста. На уровне среды исполнения можно легко реализовать ленивые отложенные вычисления (вычислять функцию только тогда, когда нужен ее результат). Кеширование становится проще. Порядок вычисления функций не имеет значения: в выражении a(x) + b(x) не важно, какую из функций a или b вычислить первой. Компиляторы могут использовать это свойство для оптимизации кода программы. Программы в функциональном стиле проще для понимания, при их анализе не нужно учитывать текущее состояние среды.

При столкновении с реальным миром эта красивая теория конечно рушится. Пользовательский ввод/вывод, работа с БД, аппаратной частью связаны с побочными эффектами. В чисто функциональных языках эту проблему пытаются обходить с помощью разных хитрых трюков по типу монад. В некоторых случаях бывает, что усилия по сохранению чистоты функции создают такую сложность, что проще работать с обычными «грязными» функциями.

Но «слить» чистые функции с грязными на теоретическом уровне не получится. Появление побочных эффектов убивает все преимущества чистых функий.
Чистые фукции без побочных эффектов имеют целый ряд преимуществ.
Так кто ж спорит-то…

При столкновении с реальным миром эта красивая теория конечно рушится.
Частично.

Но «слить» чистые функции с грязными на теоретическом уровне не получится. Появление побочных эффектов убивает все преимущества чистых функий.
Мне кажется, всё немного проще. Достаточно позволить функции быть нечистой, но заставить в объявлении функции указывать максимально возможную зону нечистых проявлений. Увы, я знаю не так много языков, которые позволяют делать это качественно.
Если честно, я не уловил идею с максимальной зоной нечистых проявлений. Можете привести пример?

Функция считается чистой, если вычисление зависит только от значений входных аргументов, и она не меняет состояние среды. Если функция считывает какие-то значения из памяти, оборудования, базы данных, откуда угодно кроме входных аргументов, то есть вероятность, что при следующем вызове с теми же аргументами она вернет другой результат. Абсолютно не важно, что на это повлияет, и из какого слоя появилась «нечистота». Результат начинает зависеть от последовательности вызовов функций.

Соответственно, теряются все преимущества чистоты функций, которые базируются на том, что функция абсолютно независима от всего, кроме своих аргументов.
я бы с процедурного советовал, меньше концепций учить для вхождения. Потом лучше ООП, так как распространеннее. Ну и начинающих на формочки часто тянет, формочки в ФП стиле — это так себе занятие. Потом только ФП, если припрет. Сразу на ФП замахиваться — гиблое дело.
UFO just landed and posted this here
Ну например патамучта Скала (со всеми ее недостатками) гораздо более интересная (с какой стороны ни посмотри). Впрочем, это личное впечатление конечно же.

Если вам для андроида — то да, котлин пойдет лучше, без вопросов. А если нет — то скажем опять же лично для меня у котлина нет особых преимуществ перед старым-престарым груви. И перед скалой. Не вообще нет — а достаточно существенных, чтобы перейти.
нет особых преимуществ перед старым-престарым груви

Статическая типизация. Для неокрепшего (и не только) мозга это серьезная помощь в написании программы.
И перед скалой

Более простой синтаксис, отсутствие мощных (и непростых для понимания) концепций «в шкафу».
>Статическая типизация.
Пардон, но у груви тоже статическая. Ну, точнее так: Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities. Ну то есть, если хотите — то все у вас будет.

>Более простой синтаксис,
Ну, возможно, но можем рассмотреть на примере. Я бы сказал, что если не лезть вглубину, то вы не заметите разницы. Ну то есть, она конечно будет, и наличие сложных конценций будет тоже, но на мой взгляд это решаемо.
Ну то есть, если хотите — то все у вас будет

Ключевое слово «если захотите». Если захотите, то и на Scala можно без имплиситов и линз. Напоминаю, что с языком сталкивается новичок. А на грувях статическая типизация не прибита гвоздями, в отличие от того же Котлина.
Ну однако же мне никогда за 15 лет это не мешало. При том что я люблю статическую типизацию и пользуюсь ей. Никаких сверхусилий ее применение не требует вообще.
А на грувях статическая типизация не прибита гвоздями, в отличие от того же Котлина.
И это скорее преимущество.
UFO just landed and posted this here
То что у вас есть экосистема (одна из самых богатых) — это замечательно. Вам не нужно писать всякую фигню, она уже лет 15 как написана. У нас еще поверх JVM имеется хадуп и спарк — и как-то ничего, не испытываем никаких проблем, наоборот, скала замечательно приживается. Среди непрограммистов в том числе.
Простенькую веб-страничку с туду-списком на скале можно написать, используя вполне «эндемичные» для неё Akka и Play, например.
При всем уважении к Scala, выбирать его в качестве первого языка стоит аккуратно. Есть вероятность, что для кого-то он может стать и последним языком программирования.
Не, ну это без сомнения. Есть преимущества, есть недостатки явные, есть менее явные. Вопрос зарплат тонкий — есть ниши (BigData), где все хорошо, есть прочие ниши, где скорее нет.
Я не про зарплаты, это ирония была про то, что язык может своей сложностью и высоким порогом вхождения может отбить желание программировать дальше.
Ну да, такое имеет место (может иметь), но тут все зависит от целей, ради которых вы его изучаете. Что аккуратно — тут вообще без вопросов. Но я просто пробовал на непрофессионалах — аналитиках, в целом каких-то непреодолимых проблем не встречается. Можно выбрать для себя подмножество (а лучше бы кто-то более опытный выбрал) — и в его рамках вполне жить достаточно долго.

Это замечательно когда вы программируете для души, вы можете себе выбрать подмножество. Но что будет если этот новичок(в программировании вообще) придет в команду скалистов, и столкнётся с горой придвинутых конструкций, может столкнуться с cats и так далее. Продолжит ли он его любить?

Ну, у него будут проблемы. Только я не очень представляю, как он реально туда попадет, в такую команду.
Тогда тем более не понятно зачем его учить как первый язык. Если взять условную java, то пройдя курсы и выучив основы можно идти работать в компанию, и на месте учиться уже основному. Сам я прошел такой путь.

А вот выучил я подмножество scala и как мне двигаться дальше? Постепенно изучать оставшееся подмножетсво на хобби проектах? Так можно, но этот путь будет сильно длинным и где брать наставников на это время. В случае с java наставниками выступали коллеги.

Все таки для первого языка очень крутой порог вхождения, имхо.

Не вижу разницы.
Джава простая, но на ней могут реализовать кучу довольно сложных и неочевидных для новичка штук с аннотациями, кодогенерацией, тестами, библиотечками для dependency injection или вообще энтерпрайз фреймворками. Вам придётся их осваивать.
В скале аналогично, просто язык сложнее и гибче, и некоторые внешние библиотеки за счёт этого получаются проще. Та же cats хороша тем, что она общая для разных кодовых баз, и при переходе на другую работу вам не придётся осваивать всё заново.

>Тогда тем более не понятно зачем его учить как первый язык.
Ну смотрите, я это в общем не предлагал. Я это пробовал, но не учить по факту как первому языку — у меня просто нет таких подопытных кроликов. Я учил аналитиков, они не профессиональные программисты. Но языки какие-то знают. И в общем, все проходит достаточно спокойно, при том что у нас не было в общем-то систематических занятий.

Т.е. то что порог крутой — ну это с какой стороны зайти, на уровне скажем переменных, констант, вещей типа if, функций, коллекций и т.п. некоторые вещи наоборот проще. Оне не обязательно такие же — после java скажем регулярки у вас поначалу могут вызвать недопонимание.

Ну так, в качестве примера — попробуйте в java сделать стрим по массиву. И попробуйте в скале. У массива в java нет методов stream(), ну и дальше нет .map, .filter и т.п. — это все можно, но с некими приседаниями. Несложными. Массив — это такая кривая коллекция. Специальная. Которая усложняет жизнь.

А теперь попробуйте в скале найти коллекцию, чтобы у нее не было .map? В итоге все вещи типа перебора коллекций делаются или так же, или проще. А это в общем-то вещи совсем базовые.

И еще я пожалуй присоединюсь к комменту ниже. У вас и в java такие же неочевидные моменты будут. И путь может быть длинным. Просто конечная точка будет разная.
Вангую, что Scala 3 тоже не взлетит:
— новых ниш, где она была бы явно востребована пока нет;
— в старых нишах хорошо себя чувствуют другие ЯП, которые оптимальны для них по набору своих сильных и слабых сторон;
— весь интерес к ней — это просто желание разработчиков к чему-то новому, но так как все ЯП развиваются, то в них тоже можно удовлетворять свое любопытство;
— мало вакансий даже на прежних версиях Скалы.

Единственное, за счет чего она сможет взять свой небольшой процент от общего пирога — это стать лучшим функциональным ЯП из всех существующих.

А так даже приятно, что люди с такими серьезными лицами фаном занимаются…
Вангую, что Scala 3 тоже не взлетит:

Даже если и не взлетит, возможно, заложенные идеи перетекут в другие языки. Если бы в свое время не появились Groovy, Scala, Clojure, кто знает сколько бы еще ждали в Java лямбд и Stream API. А сейчас в Java уже и var и аналоги case классов и чуть ли не паттернматчинг.
Я тот самый новичок. Из языков программирования — Basic в школе и Delphi в институте, дальше всякие вспомогательные скриптовые мелочи.
Честно прочитал статью, но так и не понял, почему стоит браться за Scala. Вижу много умных слов, но не понимаю, что за ними стоит. А те, что понимаю, не кажутся мне важными. Долгая компиляция? Ну, вряд ли ближайшие пару лет меня это как-то коснётся — хеллоуворлд скомпилируется в приемлемое время. То же самое касается и производительности.
Быстрые изменения? Значит, то, что я сейчас с таким усердием учу, завтра будет неактуально. Типизация и ООП? Да-да, я помню, TForm1.Button1Click…
А все остальные разделы помечены как «не для новичков», и я там ожидаемо мало что понял.

Так чем же хорош Scala именно как первый язык?
У всех по разному воспринимается первый язык, скажем так, зависит от усидчивости. Если хватит терпения на scala, c++ или rust то можно начинать с них. потому что языки богаты на возможности. Но для большинства желательно язык который дает быстрый результат, чтобы подстегнуть интерес. Большинство просто забросит язык в котором нужно месяц разбираться чтобы сделать что то не из туториала. А программисту все равно нужны выучить все парадигмы и попробовать, а в одном языке их не собрать. Так что да, динамически типизированный и декларативный язык тоже нужно попробовать.
я бы посоветовал пройти несколько первых уроков курса Мартина Одерски, когда он обновит его до новой версии языка. Даже если вы не будете программировать на скале, это даст вам неплохую базу в функциональном программировании
Мой комментарий не про то, как выучить Scala. Он про то, что из статьи я так и не понял, почему мне стоит учить Scala как первый язык.
для вас я напишу специальный текст «Почему мне стоит учить Scala», без DOT и умных слов. Просто это будет не хабровский формат и я выложу его куда-нибудь в другое место, а здесь дам ссылку.

Если коротко, то Scala — это возможность научиться функциональному программированию на безопасном языке, у которого есть большое будущее
UFO just landed and posted this here
UFO just landed and posted this here
У вас ошибка в примере, нужно:
println(new Stack().push("hello").push(new Object()).push(7))

Это же не Case классы.
я очень ждал этого комментария от внимательного читателя.

на самом деле в третьей скале можно уже не писать new
Кстати, насколько она уже жизнеспособна на ваш взгляд?
более чем жизнеспобна по-моему.

это то, какой она должна была быть с самого начала
>с самого начала
Ну, с самого начала обычно сложно предсказать, что на самом деле важно. Вот я скажем сижу сейчас на 2.11.8, примерно, потому что у меня спарк с ней собран, и совместимости нет. При этом я легко могу себе вообразить проект, для которого это вообще не проблема — пересобрали и в путь. Но пересобрать спарк, размер кодовой базы которого на порядок-другой больше, чем у типового своего проекта — это такое развлечение, которым вряд ли кто-то будет заниматься.
Функциональное программирование — это многообещающая тенденция. Скале она присуща в не меньшей степени чем ООП, и они взаимно обогащают друг друга благодаря этому языку. Рискну предположить, что скала — первый язык промышленного уровня с такими свойствами.

Разумеется не первый. Задолго до Scala вполне существовали подобные языки. Из тех что я знаю, это Common Lisp (CLOS) и Ocaml (диалект ML с элементами ООП). Если верить википедии, то эти языки повлияли на Scala. Также многие реализации языка Scheme имеют поддержку ООП, но это не определено в стандарте языка (например GNU Guile).

Мало какой язык может похвастать математически точным исчислением, лежащем в его основе.

Опять же из списка языков, которые повлияли на Scala. Standard ML и Scheme. Оба имеют формальную спецификацию. Но да, таких языков действительно мало.

В написанной вами цитате вы, кажется, игнорируете фразу «промышленного уровня».

Язык-то далеко не первый, это правда, но мне кажется (и, видимо, автору тоже), что он получил заметно больше распространения в «широком IT», чем упомянутые вами языки. Так ли это на самом деле — не знаю, но впечатление такое есть. Фраза «рискну предположить» в этом смысле вполне честный disclaimer.
UFO just landed and posted this here

"вспомним регулярные выражения, паттерн-матчинг и парсер-комбинаторы" - это примеры плохого?

хм, я наверное неудачно выразился. Я имел в виду, что регулярные выражения, выражения для паттерн-матчинга и описания парсер-комбинаторов могут быть очень трудночитаемыми (и при этом лаконичными)

Вот как выражения для паттерн-мачинга можно назвать трудночитаемыми я ни когда понять не мог. Те же самые данные, только с плейсхолдерами. Вызовы конструктора же мы сложными для чтения не считаем.

Регулярные выражения и парсер-комбинаторы - с чем сравнивать. С реализацией руками или через DSL типа yacc? Это сложность предметной области. Если на понимать работу грамматик то читать будет сложно независимо от того, в каком синтаксисе они записаны.

Sign up to leave a comment.

Articles