20 Списков на 2020 год (серия из 7 частей)
“Поэтому я нахожу слова, которые никогда не думал произносить На улицах, которые я никогда не думал, что мне стоит посетить снова Когда я оставил свое тело на далеком берегу.”
Java была ключевым игроком в области программирования с момента ее создания в середине 90-х годов. Одной из главных достопримечательностей Java является ее модель выполнения “пиши один раз, запускай в любом месте”, где исходный код Java компилируется в байт-код составителем. Этот байт-код позже может быть выполнен виртуальной машиной Java (JVM), которая преобразует байт-код Java в машинные инструкции.
Разделение написания кода от выполнения кода на два домена означает, что разработчикам Java не нужно писать инструкции для конкретной архитектуры. Они могут просто написать код Java и ожидать, что люди, создающие JVM, позаботятся обо всех нюансах, специфичных для конкретной машины.
Конечно, JVM не знает как был сгенерирован байт-код, просто предполагается, что он должен его запускать. Разработчики языков вскоре поняли, что это означает, что они могут создавать новые языки, которые работают на JVM, при условии, что они создали промежуточные компиляторы, которые могли бы конвертировать эти языки в байт-код Java. И вот, почти сразу после выпуска первой версии Java начали появляться новые языки JVM. Ниже приведены некоторые из самых крутых функций, которые я нашел в этих языках JVM, отличных от Java.
Небольшое уточнение: некоторые из приведенных ниже функций доступны на нескольких языках в этом списке. Языки, которые развивались позже, естественно, находятся под влиянием тех, которые были до них, и языки, за которыми стоит больше ресурсов, могут включать в себя больше функций. Вот почему, например, набор функций Kotlin (финансируемый JetBrains, который взимает 500 евро в год за свою среду разработки IntelliJ), по сути, представляет собой надмножество всех функций других популярных современных языков, таких как Scala и Groovy.
Я не пытался выяснить, на каком языке первым реализована какая конкретная функция ниже, скорее, я хотел просто выделить некоторые интересные функции, которые (еще) не были включены в Java, насколько мне известно. Если вы обнаружите какие-либо ошибки ниже или знаете о каких-либо других полезных функциях, доступных на языках, отличных от Java JVM, пожалуйста, дайте мне знать в комментариях!
содержание
- Иокэ – гомоиконичность
- Gosu – система открытого типа
- Госу — необязательные и именованные параметры
- Незамедлительный — стандартные диалекты
- Фреге и Eta – “Хаскелл для JVM”
- Ateji PX — параллельные блоки
- Фантом – методы “один раз” и множество литералов
- Пока – проверка с помощью формальной спецификации
- Whiley – ввод текста с учетом потока
- Jython и JRuby – совместимость языков
- Clojure – “ШЕПЕЛЯВОСТЬ для JVM”
- Скала – “все есть объект”
- Скала – все есть выражение
- Scala – классы прецедентов и сопоставление шаблонов
- Scala – неявное программирование
- Groovy – частичное применение и состав функций
- Заводной – простые регулярные выражения
- Заводной – JSON-для-классов
- Котлин — транспиляция JavaScript
- Котлин — сопрограммы
#1. Иокэ – гомоиконичность
Гомоиконичность – это тема, которая требует небольшого объяснения, но она сводится к тому, что гомоиконическая программа состоит из языковых элементов, которые могут быть интерпретированы как объекты внутри самого языка.
Вы могли бы представить себе язык программирования, где каждый объект представляет собой ориентированный граф – циклы создаются с фактическими циклами на графике, и вся информация, необходимая для запуска программы, содержится в вершинах графика. Если этот язык сам может манипулировать графиками, то он может работать со своим собственным исходным кодом”. Код как данные” – это распространенная максима при работе с гомоиконическими языками.
В Ioke , “все есть выражение, состоящее из цепочки сообщений” . Весь синтаксис языка можно резюмировать следующим образом:
program ::= messageChain?
messageChain ::= expression+
expression ::= message | brackets | literal | terminator
literal ::= Text | Regexp | Number | Decimal | Unit
message ::= Identifier ( "(" commated? ")" )?
commated ::= messageChain ( "," messageChain )*
brackets ::= ( "[" commated? "]" ) | ( "{" commated? "}" )
terminator ::= "." | "\n"
comment ::= ";" .* "\n"
Поскольку каждая программа представляет собой цепочку сообщений, а цепочка сообщений сама по себе является объектом в Ioke, программы Ioke могут манипулировать своим собственным исходным кодом (или исходным кодом других программ Ioke) в качестве данных. Хотя жюри все еще не определилось относительно того, является ли гомоиконичность хорошей вещью ™ для языка программирования, это, безусловно, интересная функция, если не что иное.
[ вернуться к оглавлению ]
#2. Gosu – система открытого типа
Gosu – это язык JVM, который предоставляет систему открытого типа , что означает, что программисты Gosu могут добавлять функциональность в классы, даже если они сами не создавали эти классы . Это включает встроенные классы Java, такие как java.lang. Строка . Простой пример приведен на странице Госу в Википедии :
enhancement MyStringEnhancement : String {
function print() {
print(this)
}
}
Вышеупомянутое усовершенствование добавляет метод print() в java.lang. Строка класс, означающий a Строка теперь может печатать сама себя:
"Echo".print()
Gosu также обеспечивает чрезвычайно простые закрытия и усовершенствования для Повторяющийся интерфейс, который – в сочетании со скриптоподобным синтаксисом языка – делает Сбор манипуляций легким:
var lstOfStrings = { "This", "is", "a", "list" }
var longStrings =
lstOfStrings.where( \ s -> s.length > 2 )
.map( \ s -> s.toUpperCase() )
.order()
print(longStrings.join(", ")) // prints "LIST, THIS"
[ вернуться к оглавлению ]
#3. Gosu — необязательные и именованные параметры
Gosu также привносит в JVM функцию, которая существует во многих других языках, но (на момент написания этой статьи) все еще отсутствует в Java: дополнительные параметры .
В Java нет способа указать значение по умолчанию для параметра в конструкторе. Если вы хотите сделать параметр необязательным, вам необходимо использовать шаблон Builder , полностью удалить параметр из конструктора и полагаться на то, что пользователь установит его постфактум, или создать несколько конструкторов:
public class Person {
private String opt = "default";
public Person (String opt) {
this.opt = opt;
}
public Person() {
// ...
}
//...
}
Gosu – один из многих современных языков, который немного упрощает эту функциональность, предлагая дополнительные параметры . Вот некоторый код Gosu, аналогичный приведенному выше коду Java:
class Person {
var _opt : String
construct (opt : String = "default") {
_opt = opt
}
//...
}
Этот конструктор предоставляет значение по умолчанию "по умолчанию" параметру opt . Этот конструктор может быть вызван с одним параметром String или вообще без параметров. В этом случае будет использоваться значение по умолчанию.
Однако необязательные параметры могут вызвать проблемы, если конструкторы имеют более одного аргумента. Рассмотрим, например, этот случай:
class Person {
var _opt : String
var _ional : String
construct (opt : String = "default", ional : String = "ditto") {
_opt = opt
_ional = ional
}
//...
}
Эта новая реализация Person имеет два необязательных параметра. Поэтому, если мы вызовем его конструктор с:
new Person("potato")
…какому параметру следует присвоить значение "картофель" ? Они оба Строка s и, в конце концов, у них обоих есть значения по умолчанию. Что делать, если мы хотим использовать значение по умолчанию для opt , но значение не по умолчанию для ional ? Gosu решает эту проблему с помощью именованных параметров . Когда мы вызываем метод construct , мы можем указать, какому параметру мы хотим присвоить это значение:
new Person(:ional = "potato")
Именованный параметр ясно дает понять, что мы хотим присвоить это значение ional , а не opt .
[ вернуться к оглавлению ]
#4. Промпто – стандартные диалекты
Prompto – это облачный язык программирования, предназначенный для создания комплексных информационных систем, таких как клиентские веб-приложения, которым необходимо взаимодействовать с данными на стороне сервера (например, сайты электронной коммерции). Подсказка – это название языка программирования, используемого для создания этих приложений, а также платформы, на которой они работают.
Prompto (платформа) предоставляет различные инструменты разработки, в том числе веб-REPL , соединители баз данных, отладчик, компилятор на основе JVM и транспилятор JavaScript.
Prompto (язык) был запущен в качестве эксперимента в овеществлении атрибутов и предлагает некоторые радикальные функции для языка JVM, включая безопасное множественное наследование, простую интеграцию с различными языками (включая Java, JavaScript, C# и Python, с Swift в работах) и – моя любимая функция – диалекты.
С быстрыми диалектами “синтаксис – это деталь”. Программисты могут писать в том стиле, который им больше подходит, и код с одного диалекта может быть переведен без потерь на другие диалекты.
Тремя встроенными диалектами Prompto являются:
Objy , для синтаксиса со вкусом ООП
Деньги , для синтаксиса, подобного Python
и Английский , для синтаксиса, похожего на английский
Все три приведенных выше блока кода представляют собой один и тот же код запроса, выраженный на разных диалектах. Каждый язык программирования должен обеспечивать некоторый синтаксис для выполнения нескольких основных задач: определение переменных, выполнение кода в цикле до тех пор, пока не будет выполнено какое-либо условие, вывод информации пользователю и т.д. С помощью Prompt вы не зацикливаетесь на одном способе выражения этих концепций, вы можете выбрать стиль программирования, который подходит вам лучше всего!
Лично я очень взволнован идеей диалектов в языках программирования, и я бы хотел, чтобы больше языков последовали примеру Prompto и включили эту классную функцию.
[ вернуться к оглавлению ]
#5. Фреге и Эта – “Хаскелл для JVM”
Фреге и Eta являются двумя языками на основе JVM, которые оба претендуют на то, чтобы быть ” Хаскелл для JVM”. ( Haskell – это язык программирования, который стал почти синонимом парадигмы функционального программирования с момента первого выпуска языка почти 30 лет назад.) Таким образом, можно было бы ожидать, что оба этих языка принесут чистое функциональное программирование в JVM… так почему же это два разных языка?
Eta предназначен не только для того, чтобы выглядеть как Haskell, это есть Haskell (или его диалект), просто работающий на JVM. Eta стремится к максимальной совместимости с компилятором Glasgow Haskell (GHC) и общим хранилищем библиотек Haskell, Hackage. Кроме того, Eta обеспечивает совместимость Java со своим интерфейсом внешних функций со строгой типизацией (FFI), что означает, что вы можете писать функциональный код, но при этом использовать классы и функции Java, к которым вы привыкли.
Фреге, с другой стороны , “хотя он поддерживает базовый Haskell, ему не хватает многих ключевых расширений, необходимых для компиляции пакета, и, следовательно, он не может повторно использовать существующую инфраструктуру”. Страница Frege “Различия между Frege и Haskell” на их GitHub в последний раз обновлялась в мае 2017 года и объясняет, как Frege ошибается на стороне Java, а не Haskell:
“Фреге-Прелюдия аналогичным образом определяет многие функции, классы типов и типы, известные из Haskell. Цель состоит в том, чтобы реализовать большую часть стандартной библиотеки Haskell 2010 наиболее совместимым способом, насколько это имеет смысл.”
“Кроме этого, все остальное будет совсем другим, потому что Frege использует API Java всякий раз, когда это возможно. В настоящее время библиотечная поддержка, выходящая за рамки стандартных библиотек, пока невелика. “
Фреге более устоявшийся . Разработка Eta была более активной в последнее время . В обоих проектах есть один или два ключевых разработчика, и поэтому они поддерживаются очень небольшими командами. Хотя у них обоих примерно одинаковое количество звезд GitHub, “eta lang” возвращает почти в 75 раз больше результатов в Google, чем “frege lang”. У Eta также есть официальный веб-сайт .
Хотя оба языка интригуют, и оба рекламируют себя как “Haskell для JVM”, если бы мне пришлось изучать только один из этих двух языков, я бы, вероятно, выбрал Eta. Мне очень жаль, господин доктор Фреге.
[ вернуться к оглавлению ]
#6. Ateji PX – параллельные блоки
“Делай что-то одно и делай это хорошо” , мантра архитекторов UNIX в 1970-х годах, была хорошо воспринята разработчиками Ateji PX, языка программирования, который расширяет Java с помощью одной ключевой функции – простого параллелизма.
Ateji произносится “ах-те-ги” , а “PX” означает “параллельные расширения”, которые он предоставляет для экосистемы Java.
Писать распараллеленный (или “параллельный”) код по-прежнему непросто, несмотря на более чем десятилетнюю эру многоядерных процессоров. Разные языки решают эту проблему по-разному. Некоторые позволяют пользователям вручную создавать потоки, некоторые позволяют управлять пулами потоков на более высоком уровне, некоторые предоставляют сериализованные и распараллеленные версии одних и тех же методов, некоторые реализуют модели субъектов и многие языки выполняют многие (или все) из них . Сообщество языков программирования, похоже, еще не пришло к единому мнению о том, когда и как следует применять параллелизм, несмотря на хорошо зарекомендовавшее себя пи-исчисление , существующее в теоретической информатике на протяжении десятилетий.
Ateji PX выбросил свою метафорическую шляпу на ринг примерно в 2010 году с ее чрезвычайно простыми параллельными блоками:
public class HelloWorld {
public static void main(String[] args) {
[ System.out.println("Hello"); || System.out.println("World"); ]
}
}
Выше [] ограничивает параллельный блок, внутри которого оператор
Hello World
или
World Hello
…в зависимости от порядка завершения параллельных потоков. Само по себе это довольно впечатляюще, но Ateji PX использует оператор
for||(int i : N) array[i]++;
…реализовать рекурсивные, распараллеленные алгоритмы:
int fib (int n) {
if (n <= 1) return 1;
int fib1, fib2;
// recursively create parallel branches
[
|| fib1 = fib(n-1);
|| fib2 = fib(n-2);
]
return fib1 + fib2;
}
…пишите элегантные однострочные сообщения, используя сложные выражения для понимания.:
// sum all elements of the upper-left corner of matrix in parallel int sum = `+ for|| (int i:N, int j:N, if i+j
…используйте параллельный канал (локально или на нескольких компьютерах) для передачи сообщений между потоками:
// declare a channel visible by both branches, and instantiate it Chan chan = new Chan(); [ // in the first parallel branch, send a value over the channel || chan ! "Hello"; // in the second parallel branch, receive a value from the channel and print it || chan ? s; System.out.println(s); ]
…и используйте спекулятивный параллелизм для одновременного запуска нескольких алгоритмов, возвращая результаты, как только самый быстрый из них завершится:
int[] sort(int[] array) {
[
|| return mergeSort(array);
|| return quickSort(array);
]
}
Вы можете прочитать все об этих (и других) функциях в белой книге здесь .
К сожалению, Ateji, компания, которая создала и поддерживала Ateji PX (а также OptimJ , другое расширение Java), закрылась в 2012 году, поэтому разработка языка застопорилась.
Если вы заинтересованы в возрождении Ateji PX или применении его принципов к вашему (или другому) языку программирования, пожалуйста, отправьте мне личное сообщение, и я буду рад помочь вам связаться с одним из первоначальных разработчиков языка, который сказал, что он будет более чем рад помочь, поделившись своим опытом. Вы можете ознакомиться с руководством по языку здесь , чтобы начать работу.
Я должен сказать, что на самом деле я немного зол из-за того, что никогда раньше не видел этой записи. Это – если можно так сказать о языке программирования – прекрасно. Абстрагируясь от всей этой чепухи о Поток ы и Запускаемые ы и пулы потоков и замена всего этого одним оператором,
[ вернуться к оглавлению ]
#7. Фантом – методы “один раз” и множество литералов
Приятная особенность чисто функциональных языков (из-за правила “без побочных эффектов” ) заключается в том, что они часто могут упростить значения и методы, используя логические сокращения. В некоторых языках, например, функции, которые не принимают параметров, но возвращают значение, могут просто кэшировать это значение после его вычисления один раз. Затем, когда функция вызывается снова, значение просто возвращается, а тело функции фактически игнорируется.
Этот метод, называемый запоминанием , может быть очень эффективно использован, например, когда пользователю необходимо взаимодействовать с диском. Например, в проводнике файлов вместо взаимодействия с диском всякий раз, когда пользователь переходит в каталог или выходит из него, мы можем просто кэшировать все дерево файлов (или его подмножество) заранее, и пользователь может взаимодействовать с этим кэшированным деревом файлов, эффективно устраняя задержку чтения с диска.
Язык программирования Fantom привносит в JVM некоторые интересные новые функции, включая запоминание с помощью методов once |:
once Str fullName() { return "$firstName $lastName" }
(Фантом заменяет Яванский Строка введите с Ул .)
как только методы должны быть методами экземпляра (не статическими ), они не должны принимать параметров и должны возвращать значение. Они могут выдавать Исключения , но тогда тело метода будет повторно запускаться при последующих вызовах до тех пор, пока не будет возвращено некоторое значение . Как только значение возвращается из метода, это значение кэшируется, и тело метода больше никогда не выполняется.
Как вы можете видеть выше, Fantom также допускает интерполяцию строк. Ценности имя и фамилия вставляется в том месте, где они включены в строку с предшествующим символом $ . В дополнение к интерполяции строк, Fantom предлагает множество небольших дополнений к репертуару Java литералов , позволяющих создавать объекты без явного вызова нового объекта. Например:
0.2e+6D, 123_456d // BigDecimal literals 100ms, -0.5sec, 2hr // Duration literals `/some/path/file.txt` // URI literals 0..5, 3..x, a..
С Fantom доступно еще много литералов и улучшений в обработке строк. Phantom – это многопарадигмальный язык, который поддерживает функциональное программирование с помощью замыканий , параллелизм с помощью модели актера и использует подход “середины пути” к статической и динамической типизации. Фантом действительно мастер на все руки.
[ вернуться к оглавлению ]
#8. Пока – проверка с помощью формальной спецификации
В то время как язык JVM, представленный миру примерно в 2010 году, представляет собой попытку Дэвида Пирса представить язык программирования с проверяющим компилятором , как описано сэром Тони Хоаром :
” Проверяющий компилятор использует математические и логические рассуждения для проверки правильности программ, которые он компилирует.”
Хоар знаменит (среди прочего) своим изобретением и последующим осуждением нулевой ссылки :
“Я называю это своей ошибкой на миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую комплексную систему типов для ссылок на объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что все использование ссылок должно быть абсолютно безопасным, при этом проверка автоматически выполняется компилятором. Но я не мог устоять перед искушением вставить нулевую ссылку просто потому, что это было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет”. [ источник ]
Возможно, в качестве средства искупления своей “ошибки на миллиард долларов” Хоар позже разработал набор правил для рассуждений о правильности программ, теперь известных как Логика Хоара или логика Флойда-Хоара. В 2003 году Хоар поставил задачу перед учеными-компьютерщиками разработать проверяющий компилятор, предупредив, что это может “повлечь за собой разработку нового языка программирования и компилятора, специально разработанного для поддержки проверки”. В то время как попытки сделать именно это.
В отличие от других языков программирования, в которых были добавлены функции в попытке обеспечить проверяемость ( как ESC/Java , Frama-C и другие), В то время как был построен с нуля для облегчения проверки.
Whiley построен на чисто функциональной основе и заботится о том, чтобы различать функции (без побочных эффектов, без состояния) и методы (может вызывать побочные эффекты, может иметь какое-то внутреннее состояние). Функции могут быть обоснованы в отношении проверки, в то время как методы являются более сложными. В то время как также используется сложная система типов и по умолчанию используются неизменяемые значения для составных переменных, таких как массивы и карты.
Следующий пример, в то время как функция:
function abs(int x) -> (int r)
ensures r >= 0
ensures r == x || r == -x:
//
if x >= 0:
return x
else:
return -x
…имеет требование постусловия о том, что возвращаемое значение всегда должно быть неотрицательным ( r ). Во время компиляции While проверяет, что возвращаемое значение r соответствует этому постусловию, посредством логического анализа определения функции (см. ниже постусловия).
Добавляя подобные постусловия к функциям во всей программе, вы можете проверить во время компиляции, что программа построена правильно. Этот процесс проверки устраняет ошибки, такие как деление на ноль, выход массива за пределы и разыменование нуля.
В то время как все еще находится в активной разработке, самая последняя версия (0.4.2) доступна по состоянию на апрель 2018 года.
[ вернуться к оглавлению ]
#9. Whiley – ввод текста с учетом потока
Еще одной действительно замечательной особенностью While является ее совершенно новая парадигма набора текста, чувствительная к потоку типизация , которую она представила миру в 2009 году. В то время как полностью переписывает модель набора текста Java для простоты использования и для достижения своей цели проверки. Во-первых, вводятся типы записей :
type Circle is { int x, int y, int radius }
type Square is { int x, int y, int dimension }
type Rectangle is { int x, int y, int width, int height }
Типы записей были предложены для включения в предстоящие версии Java, но по-прежнему являются лишь функцией предварительного просмотра в Java 14. Типы записей в Java будут предоставлять – в рамках чрезвычайно компактного определения – определение класса с конструктором по умолчанию, общедоступными переменными экземпляра на основе предоставленных имен параметров, а также получателями и установщиками для этих параметров.
Приведенный выше код While – это все, что необходимо для определения трех классов с конструкторами и общедоступными переменными экземпляра. Кроме того, с помощью While к переменным экземпляра можно получить доступ и изменить с помощью более простой точечной нотации, а не с помощью подробных установщиков и геттеров:
Circle c1 = {x: 10, y: 20, radius: 30}
c1.x = 20
Whiley также предоставляет типы объединений :
type Shape is Circle | Square | Rectangle
В приведенном выше коде/| Форма представляет собой тип объединения Круга , Квадрата и Прямоугольника . "Каналы" | работают очень похоже на логический оператор OR из Java | -- объект - это Форма если она соответствует хотя бы одному из трех классов Круг , Квадрат , или Прямоугольник .
Ввод текста с учетом потока означает, что при написании кода допустимо следующее:
function area(Shape s) -> int:
if s is Circle:
return (3 * s.radius) * s.radius
else if s is Square:
return s.dimension * s.dimension
else:
return s.width * s.height
Оператор is , приведенный выше, проверяет во время выполнения, является ли объект s соответствует Кругу или Квадрат определение записи. Если первое, то мы можем просто использовать методы и средства доступа, специфичные для Circle , без необходимости явно приводить s к объекту Circle , как в Java. Поскольку While знает определение Circle , он может проверить, что все операции, выполняемые с использованием s после если s является ли предложение Circle действительным в соответствии с определением Circle
N.B. : Whiley еще не поддерживает числовые типы с плавающей запятой , следовательно, приведенная выше неправильная формула площади круга. Я предполагаю, что это связано с присущей типам с плавающей запятой детализацией на цифровых компьютерах и некоторыми трудностями с рассуждениями о “правильности” программы где машинные эпсилоны важны.
Вышесказанное достигается с помощью типов пересечений . В то время как принимает тип объекта s , что бы это ни было, и пересекает его с типом Круг . Поскольку s должно быть Форма , есть три возможности:
Circle & Circle Square & Circle Rectangle & Circle
Компилятор Уайли распознает, что последние два пересечения типов являются нулевым набором (потому что Круг также не распространяется Площадь или Прямоугольник и наоборот), поэтому оператор if будет выполняться только в том случае, если s представляет собой Круг запись. Более интересной версией этого может быть что-то вроде:
import string from std::ascii
import std::io
method main():
integerOrString(10)
integerOrString("10")
method integerOrString(int|string x):
if x is int:
io::println(x+5)
else:
io::println(x)
While использует чувствительный к потоку ввод текста для выбора ветви if или else во время выполнения. Если x является int , он будет увеличен на 5, а затем напечатан. В противном случае он будет напечатан как есть. Это, конечно, работает аналогично instanceof в Java, но гораздо элегантнее.
[ вернуться к оглавлению ]
#10. Jython и JRuby – совместимость языков
Jython и JRuby являются двумя самыми ранними языками JVM, не относящимися к Java, впервые выпущенными в 1997 и 2001 годах соответственно. Эти языки предназначены для обеспечения совместимости между Java и Python, а также Java и Ruby, с уважением. Это означает, что Jython позволяет запускать код Python из Java, и наоборот. JRuby предоставляет те же функции, но для языка Ruby.
Вот пример простого кода Python, выполняемого в Java с использованием Jython:
import org.python.util.PythonInterpreter;
public class JythonHelloWorld {
public static void main (String[] args) {
try(PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("print('Hello, World!')");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
…аналогичный код, но для Ruby, работающего с JRuby:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class JRubyHelloWorld {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine rbEngine = mgr.getEngineByExtension("rb");
public static void main (String[] args) {
try {
rbEngine.eval("puts 'Hello, World!'");
} catch (ScriptException ex) {
ex.printStackTrace();
}
}
}
Совместимость языков в настоящее время является довольно актуальной темой в сообществе JVM, особенно с недавно выпущенным GraalVM Виртуальная машина Java и набор для разработки. Graal VM обеспечивает взаимную совместимость между несколькими языками, включая :
- JavaScript и Node.js
- Рубин и Рубин на рельсах
- R
- Python 3
- Языки на основе LLVM
Будет ли GraalVM – продукт Oracle – означать конец для Jython и JRuby? Будет ли Oracle отвлекать разработчиков от этих других проектов для работы над виртуальной машиной Graal? Смогу ли я придумать третий пример? Нам просто придется подождать и посмотреть.
[ вернуться к оглавлению ]
#11. Clojure – “ШЕПЕЛЯВОСТЬ для JVM”
В то время как Frege и Eta предоставляют слегка измененные версии Haskell для JVM, Jython и JRuby предлагают совместимость с Python и Ruby соответственно, а GraalVM расширяет эту совместимость, включая JavaScript, R и многое другое, Clojure привносит в JVM еще один язык: LISP.
В последнее время я увлекся ШЕПЕЛЯВОСТЬЮ. Мне нравится тот факт, что — как Ярмо , выше – это гомоиконический язык , что означает, что сама программа представляет собой структуру данных в языке программирования, а именно список. Это свойство означает, что программы на ЛИСПЕ могут работать со своим собственным исходным кодом, как если бы они были данными (списком), потому что они . Ввод, вывод и исходный код соответствуют одной и той же структуре и могут быть легко прочитаны, записаны и обработаны с помощью языка LISP.
Clojure привносит эту гибкость в JVM, одновременно обновляя и обновляя LISP для двадцать первого века. Clojure также в полной мере использует преимущества JVM, позволяя вызывать методы и классы Java из кода Clojure:
(doto (java.util.HashMap.) (.put "apple" 1) (.put "banana" 2))
Приведенный выше код возвращает файл java.util. HashMap ; в синтаксисе Clojure это будет записано как {"банан" 2, "яблоко" 1} . Многопоточные макросы в Clojure также обеспечивают простое функциональное программирование:
(->> (range 10)
(map inc)
(filter even?))
Приведенный выше код возвращает список (2 4 6 8 10) .
Со всеми этими удивительными проектами, обеспечивающими совместимость между Java и различными другими языками программирования, я думаю, что JVM будет существовать еще очень, очень долго.
[ вернуться к оглавлению ]
#12. Скала – “все есть объект”
Когда мы приближаемся к концу этого списка, мы сейчас находимся в области “популярных” языков JVM, отличных от Java. Scala, Kotlin и Groovy являются единственными тремя языками в этом списке, которые входят в топ-25 самых популярных языков на GitHub . Поскольку Scala – это язык, с которым я относительно знаком, давайте начнем с этого.
Scala спроектирована так, чтобы быть Sca ярлыком La nguage отлично подходит для коротких сценариев, небольших проектов, гигантских монолитных сервисов и гибких микросервисов. Scala является детищем профессора Мартина Одерского из Федеральной политехнической школы Лозанны (EPFL), который помог разработать несколько языков JVM, включая Пиццу и Универсальную Java (которая стала основой универсальных типов в Java), а также реализовать компилятор GJ для Java, на котором затем был основан javac .
Scala находится на пересечении нескольких популярных парадигм и тенденций в программировании. Первым и, вероятно, наиболее очевидным из них является объектно-ориентированное программирование. Как язык JVM, Scala наследует иерархию объектов Java и основывается на ней.
В то время как у Java всегда был своего рода двухмировой подход к ООП, с “примитивами” обращались иначе, чем с истинными Объект s, Scala объединяет иерархию, продвигая эти базовые типы данных к “реальным” объектам – int становится Int , двойной становится Двойной и так далее. Эти типы значений все происходят от общего объекта значений, В любом случае .
“Но как насчет бокса и распаковки? “вы можете спросить: “…разве они не эффективно преобразуют примитивы в объекты и наоборот?” Хотя это верно, это преобразование должно быть выполнено явно, если вы хотите, скажем, вызвать метод на примитиве Java:
jshell> 3.toString() | Error: | ';' expected | 3.toString() | ^ jshell> int x = 3 x ==> 3 jshell> x.toString() | Error: | int cannot be dereferenced | x.toString() | ^--------^ jshell> (new Integer(3)).toString() $2 ==> "3"
Scala неявно выполняет эти преобразования для вас ( см. # 15 ), поэтому все методы Integer доступны для использования с целочисленными литералами:
scala> 3.toString() res0: String = 3
И поскольку компилятор Scala преобразует их в примитивы Java во время компиляции, никаких накладных расходов на производительность не возникает. Дополнительным преимуществом этого является то, что для примитивов не требуется определять специальные операторы. Арифметические операторы + , - , * , / (и многое другое) просто определяются как функции в Scala, что позволяет использовать всевозможные символы в идентификаторах.
Проект Java Valhalla направлен на то, чтобы перенести типы значений, подобные Scala, в Java с помощью общих специализаций . Valhalla также вводит “типы значений” для Java, которые отличаются от типов значений Scala, и ближе к C/| структуре s .
Аналогично, на стороне “Java-подобного объекта” дерева наследования мы имеем AnyRef , который является предком всех ссылочных типов . В любом случае и Любая ссылка оба происходят от Любой , эквивалент Scala корневого класса Java Object . Уточняющий Любой в параметре типа означает, что может быть заменен любой тип данных, даже базовые типы данных, такие как Int и Двойной .
Аналогично, в нижней части иерархии у нас есть Null и Ничего типы. Объект Null singleton может быть заменен любым классом , который расширяет AnyRef и Ничто вообще не может быть заменено каким-либо классом. Эти “точки соприкосновения” внизу и вверху иерархии классов объединяют объектную модель Scala таким образом, что Java выглядит немного фрагментированной и неполной.
В Scala есть и другие, менее очевидные проявления философии “все есть объект”. Это достигается главным образом с помощью синтаксического сахара , который интерпретирует имя объекта (или сопутствующего объекта класса * ) , за которым следует список параметров в скобках, как вызов метода apply() для этого объекта:
scala> :paste
// Entering paste mode (ctrl-D to finish)
object Example {
def apply(x: Int) { println(s"x is $x") }
}
// Exiting paste mode, now interpreting.
defined object Example
scala> Example(4) // equivalent to Example.apply(4)
x is 4
* Объекты, определенные с помощью ключевого слова object в Scala, эквивалентны одноэлементным классам в Java. А сопутствующий объект можно рассматривать как контейнер для статических подобных методов, которые могут быть вызваны любым объектом этого класса. Сопутствующие объекты Scala должны иметь то же имя, что и их сопутствующий класс. (Путаница между Объектом объектами, объектом s и объекты реальны.)
Этот синтаксический сахар означает, что у нас могут быть классы, которые кодируют как классы, но действуют как функции, в том числе:
фактическая
Функцияs — которые являются объектами которыерасширяют любую ссылкуи имеютapply()методКартаs — чьифункции apply()принимают ключевой параметр и возвращают значениеСписокs и другие последовательности — чьиметоды apply()принимают индекс и возвращают значение по этому индексу
Эти унификации иерархии объектов и синтаксиса означают, что в Scala меньше “исключений из правил”. Вы не обращаетесь к элементам массива с помощью [] , как вы делаете в Java; Функции не являются особым типом конструкции, отдельной от объектов; и нет различия между примитивом и объектом. Scala добавляет немного синтаксического сахара и немного расширяет иерархию объектов, и все, кажется, прекрасно встает на свои места.
[ вернуться к оглавлению ]
#13. Скала – все есть выражение
Scala пытается удалить еще больше “исключений из правил” с идеей, что “все есть выражение” . В большинстве языков программирования существуют различия между различными типами синтаксических конструкций, такими как выражения, операторы и управляющие структуры . Как упоминалось выше, Scala устраняет необходимость в явных, жестко закодированных операторах благодаря расширенной иерархии классов, но также упрощает многие структуры управления, применяя несколько концепций из парадигмы функционального программирования.
Во многих языках if-else представляет собой структуру управления, которая выглядит примерно так
if (condition) {
// do something
} else {
// do something different
}
В блоках if и else вы можете повлиять на выполнение программы с помощью побочных эффектов , изменяя переменные или выполняя ввод-вывод и т.д. Если бы мы хотели, чтобы переменная зависела от значения условия, мы бы сделали что-то вроде
var y;
if (condition) {
y = ...
} else {
y = ...
}
Это требует, чтобы мы могли изменять значение y (то есть. это не является постоянным, что может быть нежелательным), и это требует, чтобы мы “вышли” за пределы области действия блока if-else , чтобы повлиять на значение y в охватывающей области.
На многих языках вы не можете сделать что-то подобное, хотя:
var y = if (x < 3) "less then" else "greater than or equal to"
…то есть присваивание результата if-else переменной. Это требует некоторой переориентации с процедурного на функциональное программирование . В процедурном программировании у нас есть явные инструкции return , и часто “возвращать рано” по соображениям производительности и чистоты кода . Однако многократные и ранние возвраты не одобряются в функциональном программировании, поскольку компилятору сложнее рассуждать о потоке внутри программы.
Одним из распространенных приемов в функциональных языках является идея о том, что последнее вычисленное значение в блоке является “возвращаемым значением” этого блока. Это позволяет нам полностью опустить ключевое слово return и может значительно упростить поток кода. Например:
val area = {
val pi = 3.14159
val r = 10
pi * r * r
}
Отсутствие операторов return означает, что этот блок всегда будет выполняться от начала до конца. Поскольку мы присваиваем переменной, выражение никак не параметризуется, и значение блока всегда будет одинаковым. Если бы это была функция, мы могли бы кэшировать результат, чтобы избежать повторного вычисления одного и того же значения снова и снова.
Концепция “последнее вычисленное значение является возвращаемым значением” означает, что, хотя последняя строка блока ( pi * r * r ) не была назначена какой-либо переменной внутри блока, поскольку это последнее вычисленное значение в этом блоке, оно является значением блока . Поскольку блок может быть оценен, его можно назначить переменной, как это сделано выше.
операторы if-else в Scala следуют тем же правилам. Последнее вычисленное значение в блоке – это значение этого блока, поэтому, хотя в Java, следующий код
if (x < 3) {
"less than"
} else {
"greater than or equal to"
}
…не будет иметь никакого эффекта, в Scala весь блок if-else принимает значение после выполнения, что означает, что оно может быть присвоено переменной
var y = if (x < 3) {
"less than"
} else {
"greater than or equal to"
}
И точно так же, как в Java, мы можем отбросить {} вокруг однострочных блоков if-else :
var y = if (x < 3)
"less than"
else
"greater than or equal to"
// or just
var y = if (x < 3) "less than" else "greater than or equal to"
Обработка даже базовых управляющих операторов как выражений, которые “возвращают” значение, позволяет нам оптимизировать наш код и облегчает анализ и тестирование выполнения программы .
В качестве примечания, мы можем воссоздать это поведение, используя только “Java-стиль” если так и возвращает s с помощью неявных преобразований и параметров по имени:
scala> :paste
// Entering paste mode (ctrl-D to finish)
implicit class Elseable (x: Any) {
def ELSE (elval: => Any): Any = if (x == ()) return(elval) else return(x)
}
def IF(cond: Boolean)(ifval: => Any): Any = if (cond) return(ifval)
// Exiting paste mode, now interpreting.
defined class Elseable
IF: (cond: Boolean)(ifval: => Any)Any
scala> IF (true) { "T" } ELSE { "F" }
res0: Any = T
scala> IF (false) { "F" } ELSE { "T" }
res1: Any = T
scala> IF (true) { "T" }
res2: Any = T
scala> IF (false) { "F" }
res3: Any = ()
[ вернуться к оглавлению ]
#14. Scala – классы прецедентов и сопоставление шаблонов
Сопоставление с образцом похоже на регулярные выражения, но для структуры объектов, а не для структуры строк. Сопоставление с образцом можно найти во всем профессиональном коде Scala, но одно из наиболее очевидных мест, где его можно увидеть, находится в выражениях match .
Подумайте о выражении совпадение как о сверхмощном переключателе . Принимая во внимание, что в Java мы определяем переключатель с содержит несколько регистров , разделенных разрывом s, в Scala мы определяем выражение совпадение с несколькими регистрами , но компилятор вставляет для нас разрывы всякий раз, когда он сталкивается с новым регистром ключевое слово. Это означает, что в выражениях Scala math нет сбоев.
Простое, похожее на Java выражение match может выглядеть так:
scala> :paste
// Entering paste mode (ctrl-D to finish)
val x = 3
x match {
case 1 => "x is one"
case 2 => "x is two"
case 3 => "x is three"
}
// Exiting paste mode, now interpreting.
x: Int = 3
res4: String = x is three
……но это даже не касается того, что можно сделать с помощью выражений Scala match . Вы можете добавить фильтры в свой регистр (и совпадения по умолчанию, обозначенные регистром _ => ):
scala> :paste
// Entering paste mode (ctrl-D to finish)
val x = 3
x match {
case _: Int if x < 0 => "x is negative"
case _: Int if x > 0 => "x is positive"
case _ => "x is zero" // default
}
// Exiting paste mode, now interpreting.
x: Int = 3
res11: String = x is positive
Вы можете сопоставлять по определенным типам:
scala> def whatIsIt (x: Any) = x match {
| case _: Double => "x is a Double"
| case _: Int => "x is an Int"
| case _: String => "x is a String"
| }
whatIsIt: (x: Any)String
scala> whatIsIt(3)
res8: String = x is an Int
scala> whatIsIt(3.4)
res9: String = x is a Double
scala> whatIsIt("3")
res10: String = x is a String
Вы даже можете использовать сопоставление с образцом для деконструкции объектов, извлекая аргументы, которые использовались для их создания:
scala> :paste
// Entering paste mode (ctrl-D to finish)
sealed trait Shape
case class Rect(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case class Square(size: Int) extends Shape
// Exiting paste mode, now interpreting.
defined trait Shape
defined class Rect
defined class Circle
defined class Square
scala> def whatIsIt (s: Shape) = s match {
| case Rect(h, w) => s"a Rect with width $w and height $h"
| case Circle(r) => s"a Circle with radius $r"
| case Square(s) => s"a Square with side length $s"
| }
whatIsIt: (s: Shape)String
scala> val r = Rect(3, 4)
r: Rect = Rect(3,4)
scala> val c = Circle(5)
c: Circle = Circle(5)
scala> val s = Square(9)
s: Square = Square(9)
scala> whatIsIt(r)
res15: String = a Rect with width 4 and height 3
scala> whatIsIt(c)
res16: String = a Circle with radius 5
scala> whatIsIt(s)
res17: String = a Square with side length 9
В этом последнем примере я использовал запечатанный признак , который был расширен несколькими классами случаев эс. Scala признак ы аналогичны Java интерфейсу и классу case es аналогичны Типы записей Уайли .
совпадение выражений и класса падежей действительно совпадение сделано на небесах [подождите аплодисменты]. Почему? Ну, потому что, используя тип объекта для управления потоком программы, компилятор помогает проверить правильность вашего кода.
Запечатанный признак , как указано выше, означает, что любые классы, которые его расширяют , должны отображаться в том же месте (обычно в том же файле исходного кода), в котором определен запечатанный признак . Это означает, что в нашем примере Rect , Круг , и Квадратные являются единственными классами , которые расширяют Форму , поэтому всякий раз, когда мы сопоставляем на Форма , компилятор может перепроверить , что мы охватили все наши базы, предоставив шаблоны для Rect , Круг и Форма , и только эти три класса.
Мы получим ошибку/предупреждение во время компиляции, если у нас будет неисчерпывающее совпадение (если мы пропустили класс случая ) или если мы попытаемся сопоставить класс, который s: Форма никогда не мог соответствовать (a Двойной , например). |/математика выражения и класс случая es это действительно мощный дуэт в Scala.
[ вернуться к оглавлению ]
#15. Scala – неявное программирование
Одним из руководящих принципов языка программирования Python ( Дзен Python ) является фраза “явное лучше, чем неявное” . Эта цитата от Тима Питерса, одного из первых авторов Python и изобретателя алгоритма сортировки Timsort , должно быть, не дошло до ушей Мартина Одерского до того, как он начал работать над Scala всего несколько коротких лет спустя. Scala изобилует неявными.
Справедливости ради, Scala была построена поверх Java, и для расширения Java любым значимым способом – например, для добавления функциональности в класс, определенный в исходном коде Java, – вам нужно преодолеть некоторые препятствия. Это еще более усложняется для таких классов, как Строка , которая является неизменяемой и окончательный (по веским причинам ). Scala использует концепцию под названием неявные преобразования для автоматического преобразования классов Java в классы Scala (и наоборот), незаметно перепрыгивая через эти препятствия для вас.
Если вы программировали на Java раньше, вы на самом деле уже использовали неявные преобразования. Когда вы пишете что-то вроде
jshell> 1 + 3.0 $1 ==> 4.0
… Java преобразует int 1 в double 1.0 автоматически и неявно (ничто в коде не говорит вам прямо, что это то, что происходит). Но целочисленные числа и числа с плавающей запятой имеют очень разные внутренние представления , , так что дело не только в повторной маркировке int как double
Как мы знаем из Java, даже явные примитивные преобразования могут быть опасными. Если вы попытаетесь преобразовать long в int , вам лучше убедиться, что long не превышает Целое число. МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ
jshell> long l = Integer.MAX_VALUE + 2000000000L l ==> 4147483647 jshell> int i = l | Error: | incompatible types: possible lossy conversion from long to int | int i = l; | ^ jshell> int i = (int) l i ==> -147483649
Java будет только неявно расширять примитивные типы. Чтобы сузить их ( двойной -> плыть или длинный -> int -> символ -> байт ), вы должны явно объявить преобразование с помощью приведения типов, как указано выше. Но это может привести к неожиданным результатам, таким как длинный выше, обернутый вокруг отрицательного int .
Чтобы избежать чрезмерно подробных преобразований между типами Java и Scala, Scala использует неявные преобразования для преобразования примитивов Java в типы-оболочки Scala (которые расширяют AnyVal ), Java String s в строковые вспомогательные классы в Scala и многое другое.
Но это может быть так же опасно в Scala, как и в Java, так что будьте осторожны!
Неявное программирование в Scala бывает разных форм и размеров, но два наиболее распространенных способа использования:
неявныепараметры инеявныепреобразования
Неявные параметры в Scala – это аргументы, которые явно не передаются функции. Эти параметры должны быть объявлены неявно в списке параметров, и в области видимости должна быть неявная переменная того же типа (определяется некоторыми немного сложными правилами области видимости):
scala> implicit val ii: Int = 42
ii: Int = 42
scala> def answer (to: String)(implicit num: Int) {
| println(s"The answer to $to is... $num!")
| }
answer: (to: String)(implicit num: Int)Unit
scala> answer("life, the universe, and everything")
The answer to life, the universe, and everything is... 42!
Обратите внимание, что в приведенном выше фрагменте кода мы явно не отправили значение 42 к процедуре ответ . Вместо этого он знал, что ему нужен неявный Int , и он искал – и нашел его – в пределах области видимости. Существуют правила для обработки коллизий между неявными параметрами, передачи нескольких неявных аргументов и указания неявных параметров с помощью ключевого слова неявно .
неявные параметры обычно используются, когда у вас много методов, передающих аргументы типа “среда” друг другу. Вместо того, чтобы засорять свой код множеством аргументов, сделайте все неявным!
scala> implicit val bb = false
bb: Boolean = false
scala> implicit val dd = 19.0
dd: Double = 19.0
scala> implicit val ss = "my string"
ss: String = my string
scala> def myBool(implicit myb: Boolean) {
| println(s" my boolean is $myb")
| }
myBool: (implicit myb: Boolean)Unit
scala> def myDub(implicit myd: Double) {
| println(s" my double is $myd")
| }
myDub: (implicit myd: Double)Unit
scala> def myStr(implicit mys: String) {
| println(s" my string is $mys")
| }
myStr: (implicit mys: String)Unit
scala> def myThings() {
| println("These are my things:")
| myBool
| myDub
| myStr
| }
myThings: ()Unit
scala> myThings
These are my things:
my boolean is false
my double is 19.0
my string is my string
Неявные преобразования немного сложнее (и немного опаснее). Они отключены по умолчанию, но могут быть включены с помощью import scala.язык.Имплицитные преобразования .
При импорте вышеуказанного пакета, когда объект в Scala пытается получить доступ к методу или значению, к которым у него обычно нет доступа, компилятор Scala будет искать (следуя тем же неявным правилам определения области) неявный def , который принимает один аргумент типа этого объекта и возвращает объект любого типа, в котором определен этот отсутствующий метод или значение:
scala> "hey".exclaim():12: error: value exclaim is not a member of String "hey".exclaim() ^ scala> import scala.language.implicitConversions import scala.language.implicitConversions scala> class Exclaimable (s: String) { | def exclaim(): String = s + "!" | } defined class Exclaimable scala> implicit def string2Exclaimable (s: String): Exclaimable = new Exclaimable(s) string2Exclaimable: (s: String)Exclaimable scala> "hey".exclaim() res1: String = hey!
Неявные преобразования кажутся отличными на первый взгляд (и они широко используются самим языком для преобразования Java <=> Scala), но – я не могу этого достаточно подчеркнуть – они опасны . Начиная с Scala 2.10 , неявные преобразования не поощряются и должны быть заменены явными преобразователями или неявный класс исп. вместо:
scala> "hey".exclaim():12: error: value exclaim is not a member of String "hey".exclaim() ^ scala> implicit class Exclaimable (s: String) { | def exclaim(): String = s + "!" | } defined class Exclaimable scala> "hey".exclaim() res1: String = hey!
[ вернуться к оглавлению ]
#16. Groovy – частичное применение и состав функций
Язык программирования Apache Groovy предлагает множество удобных способов работы с замыканиями , определяемых в Groovy как “открытый, анонимный, блок кода, который может принимать аргументы, возвращать значение и присваиваться переменной”.
Закрытия Groovy принимают форму
{ [closureParameters -> ] statements }
Они представляют собой блоки кода, разделенные фигурными скобками, с нулем или более параметров, разделенных запятыми, преобразованных в некоторый вывод. Они могут возвращать значение, вызывать побочные эффекты, такие как печать на терминале, и то, и другое, или ни то, ни другое. Они также могут получать доступ к переменным, выходящим за рамки их определяющего блока. Ниже приведены все допустимые замыкания в Groovy:
c0 = { x++ }
c1 = { x, y -> x + y }
c2 = { -> println("sup") }
c3 = { a, b, c -> }
Замыкания можно вызывать со списком параметров, разделенных запятыми, как и обычные функции, поэтому вышеуказанные замыкания можно использовать как:
[in]> c1(3, 4) [ou]> 7 [in]> x = 42 [ou]> 42 [in]> c0() [ou]> 43 [in]> c1(-3, 6) [ou]> 3 [in]> c2() sup [ou]> null [in]> c3(19, "popty-ping", false) [ou]> null
Groovy позволяет вам играть с закрытиями интересными способами. Например, Groovy допускает частичное применение замыканий – что он называет “каррированием”, признавая, что на самом деле это не каррирование в строгом смысле:
[in]> x_pow_y = { x, y -> x**y }
[ou]> groovysh_evaluate$_run_closure1@15130002
[in]> x_pow_y(2, 3)
[ou]> 8
[in]> x_pow_3 = x_pow_y.rcurry(3)
[ou]> org.codehaus.groovy.runtime.CurriedClosure@1f6f0fe2
[in]> x_pow_3(2)
[ou]> 8
[in]> two_pow_y = x_pow_y.curry(2)
[ou]> org.codehaus.groovy.runtime.CurriedClosure@2591d23a
[in]> two_pow_y(3)
[ou]> 8
Как видно выше, мы можем отправить только крайний левый параметр (с помощью curry() ) или только крайний правый параметр (с помощью curry() ) и получить обратно новую функцию, которая принимает на один аргумент меньше. ( Вот хорошее объяснение различий между каррированием и частичным применением, если вам интересно.)
Вы также можете задать произвольный параметр с помощью карри :
[in]> sum_abc = { a, b, c -> a + b + c }
[ou]> groovysh_evaluate$_run_closure1@26d028f7
[in]> sum_abc(2, 3, 4)
[ou]> 9
[in]> sum_a3c = sum_abc.ncurry(1, 3)
[ou]> org.codehaus.groovy.runtime.CurriedClosure@45c90a05
[in]> sum_a3c(2, 4)
[ou]> 9
Groovy предлагает множество других интересных функций для замыканий, в том числе прыжки на батуте для оптимизации конечных вызовов, запоминания и преобразования обычных функций в замыкания с помощью указателей методов, но еще одна небольшая функция, на которую я хочу взглянуть здесь, – это композиция функций.
Вы, вероятно, сталкивались с составлением функций на уроках математики в средней школе или при использовании оператора pipe | |/в терминале. В принципе, набор функций работает следующим образом:
(f << g)(x) == f(g(x))
В Groovy << является оператором композиции функций и передает вывод функции g на вход функции f :
[in]> f = { x -> x + 2 }
[ou]> groovysh_evaluate$_run_closure1@5db9f51f
[in]> g = { x -> x * 3 }
[ou]> groovysh_evaluate$_run_closure1@12f8682a
[in]> (f << g)(5)
[ou]> 17
[in]> f(g(5))
[ou]> 17
Вы также можете создавать функции в обратном порядке с помощью >> :
[in]> (f >> g)(5) [ou]> 21 [in]> g(f(5)) [ou]> 21
Композиция функций позволяет легко объединять функции в цепочки, создавая многоразовые конвейеры обработки данных. Плюс, это аккуратно !
[ вернуться к оглавлению ]
#17. Заводной – простые регулярные выражения
На мой взгляд, одно из мест, где Groovy наиболее явно затмевает Java, заключается в его поддержке регулярных выражений . Это даже не соревнование. В Groovy есть всевозможные дополнительные биты синтаксиса , которые помогут вам определять и использовать регулярные выражения.
Если вы новичок в регулярных выражениях или хотите пройти переподготовку, почему бы не ознакомиться с моим интерактивным руководством “20 небольших шагов, чтобы стать мастером регулярных выражений”
В Java определение и использование регулярного выражения – как и большинство вещей Java – очень подробное :
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Example {
public void run() {
String example = "The President of the United States of America";
Pattern pattern = Pattern.compile("\\b[a-zA-Z]{1,3}\\b");
Matcher matcher = pattern.matcher(example);
while (matcher.find())
System.out.println(matcher.group());
}
}
Выполнение приведенного выше кода в оболочке даст:
jshell> (new Example()).run() The of the of
Существует множество церемоний для настройки и запуска регулярного выражения над строкой Java. (И я сделал пример настолько коротким, насколько мог.) В Groovy все намного проще:
example = "The President of the United States of America"
pattern = ~/\b[a-zA-Z]{1,3}\b/
matcher = example =~ pattern
Затем мы можем найти все совпадения с
[in]> matcher[0..-1] [ou]> [groovier, better]
Насколько это просто? Выше мы определили шаблон , используя косую строку , которая представляет собой строку , окруженную // а не "" . Строки с косой чертой – это необработанные строки , в которых escape-последовательности игнорируются, поэтому нам не нужно “удваивать” обратную косую черту, чтобы получить границу слова регулярного выражения последовательность (просто \перед вместо \\b ).
Мы также использовали оператор шаблона ~ |/перед строкой, который преобразует эту строку в Шаблон способом, аналогичным Pattern.compile() . Затем мы запустили выражение в нашей строке примера с помощью оператора find | =~ вместо запуска pattern.matcher() , как мы делали в Java.
В Groovy вы также можете использовать наличие или отсутствие совпадения в качестве логического значения…
[in]> if ("The password is... PorkChop" =~ /(?i)porkchop/) {
[in]> println "you're in"
[in]> } else {
[in]> println "scram!"
[in]> }
you're in
…легко извлекайте и назначайте несколько переменных одновременно, используя множественное назначение Groovy…
[in]> (ghost1, ghost2, ghost3, ghost4) = ("Inky, Pinky, Blinky, and Clyde" =~ /[A-Z][a-z]*/)
[ou]> java.util.regex.Matcher[pattern=[A-Z][a-z]* region=0,30 lastmatch=Clyde]
[in]> println "Oh no, it's $ghost2, $ghost1, $ghost4, and uh... $ghost3?"
Oh no, it's Pinky, Inky, Clyde, and uh... Blinky?
…и многое другое ! Делая регулярные выражения менее подробными, Groovy делает их более доступными и менее тревожными для новых разработчиков.
[ вернуться к оглавлению ]
#18. Заводной – JSON-для-классов
Нравится вам это или нет, но JSON быстро становится стандартом де-факто для передачи данных в Интернете. Этот простой формат текстового файла, который описывает произвольно вложенные объекты основных типов данных и массивов, быстро приобрел широкую популярность, потому что его легко писать, легко читать и легко анализировать .
Несмотря на то, что язык был вдохновлен и назван в честь языка, объектная нотация JavaScript (JSON) не была строго подмножеством JavaScript до 2019 года, когда была выпущена новая версия ECMAScript . (JSON использовался, в частности, для разрешения символов, которые были запрещены в объектах JavaScript.) Объекты JSON теперь можно быстро, легко и безопасно анализировать из текстовых файлов и автоматически переводить в объекты на JavaScript.
Groovy предоставляет ту же функциональность, но на JVM. Заводной встроенный JsonSlurper класс позволяет автоматически анализировать данные JSON из простого строкового формата в полноценный объект Groovy:
[in]> slurper = new groovy.json.JsonSlurper()
[ou]> groovy.json.JsonSlurper@bea5941
[in]> result = slurper.parseText('{"person":{"name":"Guillaume","age":33,"pets":["dog","cat"]}}')
[ou]> [person:[name:Guillaume, age:33, pets:[dog, cat]]]
[in]> result.person.age
[ou]> 33
[in]> result.person.pets
[ou]> [dog, cat]
[in]> result.person.name
[ou]> Guillaume
Конечно, это можно сделать на простой Java, но по мере того, как JSON становится все более популярным, появляется все больше и больше аргументов в пользу включения его в стандартную библиотеку языка, что Groovy уже сделал! И это “просто работает”, прямо из коробки – очень просто!
[ вернуться к оглавлению ]
#19. Котлин — транспиляция JavaScript
Пока мы обсуждаем тему Интернета, разработчикам бэкэнда иногда бывает трудно продемонстрировать свою работу или легко создать портфолио. Разработчики интерфейсов имеют в своем распоряжении огромный холст – веб-браузер, доступ к которому может быть действительно затруднен, если вы не работаете в JavaScript или TypeScript.
Kotlin пытается закрыть разделение интерфейса/бэкэнда немного благодаря своей способности компилироваться не только в байт-код Java через JVM или в более десятка различных собственных архитектур через LLVM, но также путем переноса непосредственно на JavaScript , где вы можете запустить Kotlin в своем браузере!
Просто напишите простую функцию Kotlin как
import kotlin.browser.* import kotlin.dom.* fun main(args: Array) { document.getElementById("tooltip")?.appendText("The first step") }
…транспилируйте, и вы получите вывод JavaScript, который выглядит примерно так:
if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'KotlinFunWeb'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'KotlinFunWeb'.");
}
var KotlinFunWeb = function (_, Kotlin) {
'use strict';
var appendText = Kotlin.kotlin.dom.appendText_46n0ku$;
function main(args) {
var tmp$;
(tmp$ = document.getElementById('tooltip')) != null ? appendText(tmp$, 'The first step') : null;
}
_.main_kand9s$ = main;
main([]);
Kotlin.defineModule('KotlinFunWeb', _);
return _;
}(typeof KotlinFunWeb === 'undefined' ? {} : KotlinFunWeb, kotlin);
Продемонстрировать свои навыки в Kotlin еще никогда не было так просто! Вот шаблон , чтобы вы начали, и несколько более крупных примеров (менеджера паролей , цифровой книжной полки и отслеживания людей, находящихся в настоящее время в космосе ) для вдохновения!
[ вернуться к оглавлению ]
#20. Котлин — сопрограммы
Параллельное программирование на Java – это ужасающая вещь для новичка. Когда я должен использовать атомарный ? И изменчивый s ? И должен ли я синхронизировать в этом классе? Или этот блок? Или это ?
В лучшем случае вы можете получить приложение, которое в настоящее время не выходит из строя или не блокируется, но которое может неожиданно сломаться в любой момент, без какого-либо простого способа даже отследить, что пошло не так. В худшем случае вы можете столкнуться с условиями тихой гонки, которые повредят ваши данные, не предупредив вас о каких-либо проблемах вообще!
Котлин пытается немного облегчить эту боль с помощью своих сопрограмм . Сопрограммы были предложены в конце 1950-х годов как своего рода аналог подпрограмм , вместо отношений родитель-потомок, где управление переходит от (более центрального) родителя к (более периферийному) потомку, а затем обратно к родителю, сопрограммы вводят симметричные отношения. Данная сопрограмма может уступить другой в любое время. Простая (псевдокодовая) сопрограмма может выглядеть так
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
В приведенном выше примере сопрограмма product заполняет очередь до тех пор, пока не останется свободного места, затем выдает s для потребления , который очищает очередь. Эти две сопрограммы отскакивают назад и вперед, создавая поток программы.
Главное преимущество сопрограмм заключается в том, что они асинхронны . В приведенном выше примере сопрограмма производитель не “ожидает” или “спит” во время выполнения сопрограммы потребитель , она просто остановлена. Он не использует никаких ресурсов. Эти две сопрограммы могут выполняться в разных потоках или в одном потоке.
Поскольку сопрограммы и потоки операционной системы не имеют отношения 1: 1, сопрограммы очень легкие. Хотя ваша система может начать отказывать, если вы попытаетесь создать более нескольких тысяч – или даже нескольких сотен – потоков, вы можете легко создать миллионы сопрограмм Kotlin одновременно. Сопрограммы обеспечивают простое параллельное асинхронное разделение труда, равномерно распределенное между ресурсами вашего компьютера.
Чтобы узнать больше о различных подходах к параллельному программированию в Kotlin, ознакомьтесь с этим сообщением SO о различиях между потоками и сопрограммами и этим коротким сообщением в блоге, в котором сравниваются и сравниваются актеры и сопрограммы .
[ вернуться к оглавлению ]
Знаете ли вы о каких-либо других интересных функциях, о которых я забыл упомянуть выше? Доступны ли какие-либо из вышеперечисленных функций также на выбранном вами языке? Дайте мне знать в комментариях!
Люди, которые просмотрели вышеприведенную статью, также просмотрели:
- я вымогаю деньги у незнакомых людей в Интернете
- промытый кислотой Facebook
- мой другой Разработчик. Чтобы статьи
- 𝐭 𝓲 Ⓜ 𝑒 ⓒгибрид
Спасибо за чтение!
20 Списков на 2020 год (серия из 7 частей)
Оригинал: “https://dev.to/awwsmm/20-coolest-features-of-non-java-jvm-languages-2p4h”