Русский
Русский
English
Статистика
Реклама

Языки программирования

Две недели с F

06.03.2021 16:12:44 | Автор: admin


А вы когда-нибудь записывали свои впечатления от изучения нового языка? Записывали все, что вам не понравилось, чтобы через пару недель изучения понять, насколько недальновидными и тупыми они были?

КАРТИНКА ДО КАТА

На днях я понял F#, и попытаюсь описать словами мысль, стоящую за языком.

Почему ты не Powershell?


Первым делом, как только уселся за F#, ознакомившись со стайл гайдом, начал переносить команды из Powershell, которые использую чаще всего. В языке есть пайп оператор, ну, можно программировать как на Powershell. Да?

Вот сейчас возьму и функционально получу корневую директорию файла, не используя богомерзкие классы классы из .net.

Все очень просто, берем путь, преобразовываем строку в массив помощью split, по System.IO.Path.DirectorySeparatorChar, берем последний элемент из массива и делаем .trim.

Да, на F# есть весь .net а в .net всё есть, но я не за этим сел. Вот так этот велосипед выглядит на Powershell:

$Path=C:\users\test\folder$Trimer=$Path.Split(\)[$Path.Split(\).Count-1]$Path.Trim($Trimer)

В этом коде много проблем, он просто ужасен, но именно его я и буду переписывать.

Сейчас просто перепишу, ну что может пойти не так?

Не такой уж и умный компилятор


let splitPath inputObject: string =    let q = inputObject.Split(System.IO.Path.DirectorySeparatorChar)     q

Написав две строки кода, сразу получаю ошибку:

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

На разборку у меня ушло минут 30, я не мог поверить, оказывается, компилятор не смог определить, что имеет дело со строкой из типа входного объекта.

let splitPath inputObject: string =    let mutable inputObject : string = inputObject    let q = inputObject.Split(System.IO.Path.DirectorySeparatorChar)     q

Пришлось задавать типы прямо внутри функции лишний раз копируя входные данные.

LInq


Компилятор не делает всю работу за меня ну и ладно. Такие сложности не остановят меня от написания своего собственного костыля.

Часть моей гениальной задумки лежала на Linq, на Trim и Last. Но Trim не работает со string, он работает c Char, то есть нужно переворачивать последний элемент листа и откусывать от строки по символу.

Нет ++


Linq работает не так, как я хочу ну и не надо. Я посчитаю количество элементов в массиве и выберу нужный, а потом переверну его, разобью на char[] и обрежу таки стрингу!

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

На этом месте я понял, что совсем ничего не понимаю и начал изучать язык.

F#, ну зачем?


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

Printf, printfn, нейминг


Это покоробило меня еще в самом начале, функция printf выводит символы в той же строке, а printfn в новой строке. В этом весь F#.

Меня, как человека знакомого с концепцией функционального программирования из Powershell это покоробило, после Powershella любой другой язык кажется каким-то куцым.

Если бы я делал F#, я бы сделал какую-то такую функцию:

Out-HostInputstring -Newline

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

Napespaces и ленивый Open


Сразу после своего собственного костыля я попытался сделать сайт на основе шаблона ASP NET MVC. К сожалению, из MVC там только С, но контроллеры действительно получаются очень красивые и компактные.

В F# все файлы в F# ведут себя как скрипты. Переменная или функция не объявленные выше не могут использоваться ниже.

С помощью директивы Open мы открываем неймспейсы и модули.Это аналог Using и Import-Module. По аналогии с Powershell, я могу импортнуть файл в котором есть коллекция со всеми её функциями, вставить её в середину файла и все заработает прям как в павершелле? Нет.

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

||>, <|, почему не | ?


Оператор |> нужен чтобы передавать значение в функцию.

||> существует чтобы передавать кортежи в функцию.

|||> а этот монстр передает кортеж из трёх в функцию.

Работа с кортежами выглядит так:

(1,2)||>someFunction

А с единичной переменной вот так:

1|>someFunction

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

Эта ошибка была совершена из-за другой ошибки, <| Pipe back оператора. Он был введен, чтобы при композиции в некоторых случаях можно было избавиться от скобочек. К примеру это:

printfn%s(stringValue)

Можно написать так:

printfn%s<|stringValue

Дон Сайм, архитектор языка как раз говорил об этом тут.

Почему ты не F#?


Помните о лекциях от крутых мужиков? Я прослушал лекцию от Скотта Влашина и на этом моменте меня пробило, я понял эту гигантскую мысль, осознание накрыло со всех сторон, это совсем другая парадигма. Я мог только сидеть на стуле и ухать.

Some, None


Скажем, мы пытаемся прочитать файл на двух языках. В F# и С#. К примеру, пытаемся прочитать txt файл и что-то сделать с его содержимым. Если что-то пойдет не так, код написанный на C# упадет сразу в двух местах, потому что StreamReader не может прочитать файл, которого нет, да и обработчик не умеет работать с нулём.

Вся задумка состоит в том, что даже если мы не возвращаем Value, мы всегда возвращаем что-то, у нас есть тип, у нас есть Value of None. И если мы не получили Some of Value, то получили None.

Как пример, работа с дотнетовскими коллекциями в F#:

  let dictionary = Dictionary<string, string> ()       let getFromDictionary key =        match dictionary.TryGetValue (key) with        | true, value -> Some (value)        | false, _ -> None

Кстати, этот же метод можно реализовать и на C# с помощью расширений, например для этого есть LanguageExt.Core и Maybe монады, но на C# все это выглядит просто ужасно.

Discriminated union aka алгебраические типы


Чтобы прочитать файл на C# мы должны писать защитный код как минимум в 2 местах. Сначала мы должны проверить, что файл существует и что файл соответствует формату, чтобы не упал streamreader.

Чтобы не падал наш процессор, нужно проверить, что файл не пустой и что он тоже правильного формата. Это абсолютно легитимный способ писать код на C#, но не на F#.

К примеру, возьмем пример, где наша программа может работать только с txt и ini файлами.

type ValidInput =    | Txt of string    | Ini of string type InvalidInput =    | WrongFormat    | FileDoesNotExists    | FileIsEmpty    | OtherBadFile type Input =    | ValidInput of ValidInput    | InvalidInput of InvalidInput

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

let input = testInputObject request match input with| ValidInput (x) -> invokeAction x | InvalidInput (x) -> writeReject x 

Непробиваемый дизайн языка


Непробиваемый ни нулями, ни багами. И гениальность состоит из нескольких компонентов:

  • Нет return. Вернуть значение из функции можно только в конце после отработки всей логики.
  • Нет if без else. Потому, что if без else обычно применяется там, где будет возвращен null.
  • Type of Value. В F# всегда возвращается либо тип, либо значение какого-то типа, но никогда не Null.

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

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


Это вытекает из особенности языка, все файлы в F# ведут себя как скрипты. Переменная или функция не объявленные выше не могут использоваться ниже.

Что с одной стороны, это не дает писать код в вольной спагетти манере, но с другой, становится ясно, куда смотреть. Если функция используется ниже, то она объявлена выше.

Особенно прекрасно это смотрится на бизнес-логике связанной с ASP .NET. Все типы и все функции, связанные с определенной страницей на сайте все на одном листе.

Имутабельность по умолчанию


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

Так же я заметил, что чаще всего работаю с коллекциями, стрингбилдерами и т.п., зачем мне мутабельные ссылки на них?

DDD


Domain Driven Design в F# это абсолютно нативная вещь и пожалуй, лучший способ разработки. Если вы начнете писать на F#, то сможете и не заподозрить, что начали так делать.

Скажем, мы храним в базе данных данные о пользователях, где часть из них кошки, а другая попугаи и нам нужно понять, с кем мы имеем дело. У пользователя есть поле с его ID и булёвое поле HaveWings.

То вот это не F# и не DDD:

let getUserType key =    let user = getFromDatabase key    if user.HaveWings = true then "Parrot"    else "Cat"

В этом случае мы не используем паттерн матчинг, что делает его нерасширяемым и мы не используем типы, поэтому компилятор нам больше не помощник.

А это уже и F# и DDD:

let getUserType key =    let user = getFromDatabase key    if user.HaveWings = true then "Parrot"    else "Cat"

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

Плюс мы используем паттерн матчинг, что в будущем, когда модель данных станет сложнее, позволит нам избавиться от вложенных else if и длинных свитчей.

В целом, можно программировать на F# и без DDD, но если можно сделать код человекопонятным, пот почему бы и нет?



Все то время, что я не знал, что пытался писать на смеси C# и Powershell даже не понимая того, что в F# то, как ты пишешь код так же важно, как и соблюдать синтаксис.

Я понял в чем суть имутабельности по дефолту, я понял DDD, я понял, в чем главная задумка языка.

Так я полюбил F# и мне больше не бомбит.

Скрытый текст
Монстр-велосипед был добавлен в статью в юмористических целях, но я таки его доделал. И в нем не меньше (если не больше) проблем, чем в коде выше, но тем не менее.

Если знаете, как сделать его еще лучше свисните.

let splitPath inputObject =     let mutable inputObject : string = inputObject    let stringArray = inputObject.Split(System.IO.Path.DirectorySeparatorChar)       let mutable outString = ""    for i in stringArray do        outString <- i      let chararray = outString |> Seq.toList |> List.rev    for c in chararray do        inputObject <- inputObject.TrimEnd(c)     printfn "%s" inputObject splitPath @"C:\users\test\folder"


Подробнее..

Перевод Почему я считаю Haskell хорошим выбором с точки зрения безопасности ПО?

31.05.2021 18:12:13 | Автор: admin


Команда Typeable понимает ценность безопасности. Мы любим Haskell, но стоит ли его выбирать, если ваша цель создание защищенного программного обеспечения? Хотелось бы сказать да, но как и для большинства эмпирических вопросов о разработке ПО, здесь просто нет объективного доказательства, подтверждающего, что Haskell или ещё какой-нибудьй язык программирования обеспечивает большую безопасность, чем любой другой. Нельзя сказать, что выбор языка в Typeable не имеет значения для безопасности, но какое именно значение он имеет, еще нужно подумать.


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


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


   Чисто техническая            Уязвимость, относящаяся        уязвимость                исключительно к предметной области                                                                                           Инструментарий Инструментарий  Нужно     должен исправить может помочь  подумать

На оси выше показан источник различных уязвимостей программного обеспечения. На крайнем правом конце мы видим ошибки, связанные исключительно с доменной областью, то есть ошибки, совершенно не зависящие от используемого инструментария. Примером такой ошибки являются контрольные вопросы, которые в начале 2000-х использовались многими веб-сервисами для восстановления паролей. Зачастую это были вопросы типа Девичья фамилия вашей матери?. Позднее, примерно в 2009-2010 годах, возникло такое явление, как социальные сети, и неожиданно девичья фамилия матери перешла в категорию общедоступной информации. Неважно, какую технологию вы используете для реализации такой схемы с контрольными вопросами. Эта схема все равно не работает.


На крайнем левом конце шкалы у нас представлены ошибки, имеющие чисто техническую причину. Они абсолютно не зависят от области задачи. Хорошим примером такой проблемы является печально известное переполнение буфера. Совершенно неважно, что хранится в буфере если буфер переполнился, злоумышленник получает возможность беспрепятственно вмешаться в работу поддерживающих структур вашей программы. В данной ситуации переполнения буфера можно избежать, по крайней мере, теоретически, используя набор инструментальных средств, в котором отсутствует неконтролируемая запись в буфер.


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


В таких сервисах обычно есть соблазн записать файл пользователя непосредственно в файловую систему сервера. Однако под каким именем файла? Использовать непосредственно имя файла пользователя верный путь к катастрофе, так как оно может выглядеть как ../../../etc/nginx/nginx.conf, ../../../etc/passwd/ или любые другие файлы, к которым сервер имеет доступ, но не должен их изменять.


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


Использование шкалы


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


В идеале современный инструментарий должен практически полностью устранять чисто технические уязвимости. Например, большинство современных языков, таких как Haskell, C# и Java, по большей части обеспечивают защиту содержимого памяти и в целом предотвращают переполнение буфера, попытки дважды освободить одну и ту же ячейку, а также другие технические проблемы. Однако от правильного инструментария можно получить еще больше пользы. Например, легко представить себе систему, в которой имеется техническая возможность разделить абсолютный и относительный пути к файлу, что упрощает контроль атак с обходом каталога (path traversal), таких как загрузка пользователем файла поверх какого-нибудь важного конфигурационного файла системы.


Haskell нижняя часть шкалы


Как и большинство современных языков, Haskell хорошо работает с техническими уязвимостями низкого уровня. Во-первых, Haskell обеспечивает безопасный доступ к памяти, что делает огромный пласт возможных уязвимостей, особенно связанных с массивами данных и переполнением буфера, недосягаемым для потенциальных злоумышленников. Во-вторых, Haskell имеет статическую типизацию, что также защищает от целого семейства ошибок, например, от известных манипуляций с типами в PHP:


// From imaginary CSRF token protection:if ($tokenHash == $hashFromInternet->{'tokenHash'}) {  echo "200 OK - Request accepted", PHP_EOL;}else { echo "403 DENIED - Bad CSRF token", PHP_EOL;};

Видите, в чем здесь проблема? В большинстве динамических языков, таких как PHP, тип записи JSON определяется в процессе выполнения и зачастую основывается на структуре входных данных. Кроме того, в объектно-ориентированном программировании тип используется для выбора поведения с помощью динамической диспетчеризации, что фактически позволяет злоумышленнику выбирать, какой код будет исполнен. Наконец, в языке PHP равенство, выраженное через ==, зависит от типа вводимых данных, и в приведенном выше примере атакующий может полностью обойти защиту.


Аналогичная проблема возникла с Java (и другим языками, см. https://frohoff.github.io/appseccali-marshalling-pickles/). Java предложил исключительно удобный способ сериализации любого объекта на диск и восстановления этого объекта в исходной форме. Единственной досадной проблемой стало отсутствие способа сказать, какой объект вы ждете! Это позволяет злоумышленникам пересылать вам объекты, которые после десериализации в вашей программе превращаются во вредоносный код, сеющий разрушения и крадущий данные.


Это не значит, что вы не можете создать безопасный код на PHP или не можете получить такие же ошибки в Haskell, однако по своей природе Haskell не склонен к таким уязвимостям. Если переложить приведенный выше пример кода на Haskell, он будет выглядеть примерно так:


data Request = Request {csrfToken :: Token, ... other fields}doSomething :: Session -> Request -> Handler ()doSomething session request  | csrfToken session == csrfToken request = ... do something  | otherwise = throwM BadCsrfTokenError

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


Haskell середина шкалы


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


Прежде всего, в Haskell имеется возможность моделировать данные более точно по сравнению с такими языками, как как C, Javascript или даже Java. В основном это обусловлено удобством его синтаксиса и наличием типов-сумм. Точное моделирование данных имеет значение для безопасности, поскольку код домена в основном представляет собой модель некоторого реального явления. Чем меньше ее точность, тем больше возможностей имеют злоумышленники.


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


data SSN = Unknown | Redacted | SSN Text

А теперь сравним моделирование той же идеи с использованием строковых величин "", "<REDACTED>" и "191091C211A". Что произойдет, если пользователь введет "<REDACTED>" в поле ввода SSN? Может ли это в дальнейшем привести к проблеме? В Haskell об этом можно не беспокоиться.


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


storeFileUpload :: Path Abs File -> ByteString -> IO ()storeFileUpload path = ...

вероятность того, что вы создадите ситуацию, при которой пользователи могут сделать запись поверх ваших системных файлов, значительно снижается. Этот код скомпилируется, если только отсутствует практическая возможность, при которой путь к файлу содержит атаку с обходом каталога. Точно так же если после того, как пользователь потерпел неудачу при входе в систему, пользовательские данные просто недоступны для программы, или если вы просто не можете внедрить данные непроверенного пользователя в страницы HTML, вероятность ошибки снижается.


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


Haskell и ошибки домена


Чисто доменные ошибки выше описывались как ошибки, не зависящие от используемых инструментов. Это не совсем верно. Инструменты не выбираются случайным образом. Сообщества, объединяющие единомышленников, зачастую образуются вокруг различных инструментов. При этом такие сообщества могут иметь несхожие взгляды на безопасность.


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


Однако это все догадки. Сообщество Haskell до сих пор достаточно мало, чтобы не быть объектом атак, а специалисты по Haskell в общем случае еще не так сильно озабочены проблемами безопасности, как разработчики на Javascript или Python.


Заключение


Конечно, Haskell имеет свои недостатки, и я не утверждаю, что другие языки не могут похвастаться такими же достоинствами. А в некоторых случаях, например, в случае атак по времени и прочих атак по сторонним каналам, другие инструменты могут обеспечить даже лучшую защиту. Кроме того, некоторые языковые сообщества в большей степени ориентированы на безопасность, чем Haskell. Но лично я считаю, что по сравнению с существующими универсальными языками программирования, доступными для выбора, Haskell предлагает очень хороший набор функций для разработки безопасного программного обеспечения.

Подробнее..

Самые популярные языки программирования. Хабратоп 2020 года

04.12.2020 18:08:20 | Автор: admin
Мы тут решили подбить предварительные итоги года и проанализировать рейтинги популярности языков программирования. Как менялась популярность ЯП и какие языки в 2020 году считаются топовыми, читайте в этой статье.


Ситуация с ЯП очень похожа на события этого фильма. Узнали же из какого?



Рейтинги субъективны


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

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

Индекс TIOBE


Индекс TIOBE один из самых известных рейтингов языков программирования. Для анализа используются запросы в поисковиках, которые включают название языка. Например, python для новичков. Но уже здесь имеется несколько нюансов, которые потенциально могут повлиять на место ЯП в рейтинге.

Рейтинг не учитывает качество и количество написанного кода на конкретном языке. По сути для анализа используются косвенные данные.

TIOBE не учитывает поиск Яндекса. То есть как минимум половина запросов из русскоговорящих стран вылетает в трубу. Также не учитывается китайский поисковик Baidu. Тем не менее рейтинг остается одним из самых популярных.

Теперь непосредственно о языках программирования.


На первом месте оказался С. С начала года он стабильно занимает примерно 16% рынка.

Java, который ещё в январе был лидером, стремительно обваливается. Он потерял треть пунктов и опустился на третье место. Эксперты считают, что падение Java и подъем Python, который поднялся на второе место, это следствие пандемии.

Популярность Python также подстёгивает значительный рост сферы Data Science, где Python считается приоритетным языком. К разочарованию поклонников R, Python требуют в 81% вакансий на позицию аналитика больших данных (читайте об этом в нашем материале), а R (без Python) нужен только в 3% случаев.

Впрочем, активное изменение рейтинга в 2020 году могло быть вызвано и майскими изменениями алгоритмов Google. Ведь именно в мае начались резкое падение рейтинга Java и рост Python.

Немного странной кажется низкая позиция JavaScript. 2 % рейтинга, и 7-е место слишком заниженный результат, по нашему мнению. Ведь по количеству пулреквестов в GitHub JavaScript уверенно лидирует. Больше 20 % всех пулреквестов это JavaScript. Для сравнения: Python на втором месте с показателем 15,9 %.

Активность сообщества разработчиков говорит куда больше, чем количество ключей в поисковой выдаче. И, несмотря на крепкую позицию JavaScript на GitHub, в TIOBE JavaScript за всю историю существования не поднимался выше 6-й позиции.

Вот, например, сравнение рейтинга JavaScript и C даже без учёта С++:


По рейтингу TIOBE, все шансы стать языком 2020 года есть у С. Если за декабрь не случится никаких серьезных изменений, то этот титул будет у языка второй год подряд таким ранее мог похвастаться лишь Objective-C.

PYPL рейтинг от GitHub


PYPL это ещё один рейтинг популярности языков программирования. И он использует совершенно другую систему анализа.

TIOBE учитывает общее количество страниц, которые относятся к конкретному языку. Собственно, это одна из причин, почему С стабильно находится в лидерах, ведь многие популярные ЯП появились значительно позже.

Рейтинг PYPL использует для анализа Google-тренды. С их помощью оценивают, насколько часто пользователи ищут туториалы по конкретному языку. Ведь, по мнению создателей рейтинга, именно поиск туториалов показывает, насколько популярен язык конкретно сейчас.


PYPL показывает удивительную стабильность. Первая десятка позиций за последний год абсолютно не изменилась. А ведь именно на них приходится 85,94% всех запросов.

Уверенно лидирует Python, индекс которого почти в 2 раза выше, чем у Java с его вторым местом. При этом отрыв второго от третьего места тоже почти в 2 раза. Так что в сумме Python и Java занимают почти половину всех запросов по туториалам. А это значит, что именно их активнее всего изучают в 2020 году.



По всем рейтингам Python в топе (хоть и на втором месте после Java). Сохранится ли тенденция? Какие у языка перспективы?

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

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

Семён Березовский, ментор курса SkillFactory Python для веб-разработки

Среди других ЯП активный рост демонстрируют Go, Rust, Ada, Lua и Dart. Их можно рассматривать как перспективные языки, но пока что интерес к ним слишком мал, чтобы с ними считаться в глобальных масштабах. Отметку в 1% преодолел только Go.


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

Но при этом он не показывает, на каких языках работают программисты. Ведь для выполнения рабочих задач совсем не обязательно искать туториалы в Интернете.

Опрос программистов на постсоветском пространстве


В начале 2020 года на DOU были опубликованы результаты опроса почти 10 000 программистов из постсоветских стран. И на основе их ответов был составлен рейтинг языков на которых сейчас пишут больше всего.

Да, выборка в 10 000 анкет это мало по сравнению с анализом миллионов запросов поисковиков. Но при этом она дает более полное видение о истинной популярности языков. Ведь используются не косвенные данные, а вполне себе прямые.

В первую очередь нас интересует вопрос На каком языке вы пишете сейчас? Ведь именно он показывает, насколько распространен язык именно в рабочей сфере, а не в обширности теории или популярности обучения.


Первая пятёрка с небольшими изменениями повторяет рейтинг PYPL. Разве что нет такого огромного отрыва Python от остальных ЯП. А разброс первого места с 18,4 % и пятого с 10,8 % нельзя считать слишком уж большим.

При этом первые пять позиций покрывают 71,5 % рынка, что также коррелирует с рейтингом PYPL. Но тут тоже есть изменения. По сравнению с 2019 годом JavaScript и Python растут, а вот рейтинги Java, C# и PHP постепенно падают.

Особенно активно растёт Python. Количество разработчиков, которые считают Python своим основным ЯП, выросло на 21,1% по сравнению с 2019 годом.


В целом Python растёт практически во всех аспектах. Он лидирует по личностным предпочтениям программистов. Также его планирует изучать как второй язык наибольшее количество респондентов. А в качестве дополнительного языка программирования он занимает почётное второе место.

Учитывая темпы роста, Python через несколько лет вполне может вырваться в лидеры и крепко оккупировать первое место. Тем более что разрыв между ним и лидером составляет всего чуть больше 5 %.

При этом очень активно растёт TypeScript, что в целом имеет смысл. Ведь разработка мобильного софта на TypeScript за последние несколько лет выросла многократно. Поэтому программистам, которые планируют разрабатывать софт для смартфонов, стоит обратить внимание на этот ЯП. Ну а для тех, кто уже знает JavaScript, сам Билл Гейтс велел.

Заключение


Если обобщить информацию из всех рейтингов, то складывается следующая картина.

Python, JavaScript, Java и С# и PHP это элита. Их активно изучают, на них программируют, пишут и читают туториалы. Сообщество языков живо, они развиваются и процветают. Можно посоветовать любой из этих языков новичку, который хочет войти в IT и не ошибётесь. Они действительно востребованны и популярны.

Что касается С/С++, то в Интернете просто огромное количество информации по ним. Несмотря на возраст, они все ещё сильны, хоть и понемногу теряют аудиторию.

Что касается перспективных языков программирования, которые могут хорошо выстрелить в будущем, то здесь стоит упомянуть TypeScript, Kotlin, Rust и Dart. На них стоит обратить внимание, если ищете перспективный второй язык программирования.

Но в целом стоит помнить, что любые рейтинги такого плана субъективны и во многом зависят от того, как именно проводится анализ данных. Так что если хочется учить С++ вперед. И даже если интересен Whitespace (тьфу-тьфу-тьфу), то можно и его. Успехов вам в ваших начинаниях! Тем кто надумает изучать язык мы всегда готовы в этом помочь. А промокод HABR, добавит 10 % к скидке на баннере.

image



Рекомендуемые статьи


Подробнее..

Отпусти меня, PHP

14.12.2020 02:16:30 | Автор: admin

Всем привет, меня зовут Коля и я 10 лет пишу на php. Хлоп-хлоп-хлоп, привет, Коля.

Впервые я попробовал PHP в 11 классе, на тот момент были очень популярны сайты для мобилок, и меня это затянуло. Я писал на php на уроках и переменах со своего nokia n73, разбирался с одной из широко известных в узких кругах CMS, и грезил светлым будущим. Вот же оно, я теперь программист, я все понимаю и у меня получается.

Затем была первая работа, брошенный универ, ООП, фреймворки, и первые попытки соскочить. Сначала Java, затем плюсы. Я делал простенькие программки-помощники (трекер времени с автосохранением в jira, десятистрочные плагины для IDE), и среди этих for, if, switch я без проблем плавал как рыба в воде.Но как только требовалось что-то посложнее таймера, сразу начиналось избиение клавиатуры, стола и прочих подручных предметов.

Я искренне не понимал, какого черта ошибка выглядит как третий том Войны и Мира, с кучей непонятного текста и полным отсутствием человекопонятного пэхапэшникопонятного описания ошибки. Таких вещей было много, а дядя требовал закрыть насущные таски, и я просто забрасывал слегка начатое изучение нового языка в долгий ящик.

Время шло, опыта становилось все больше, паттерны, SOLID, SQRS, DDD. PHP стал совсем понятным, любая ошибка при выполнении скрипта вызывала лишь усмешку и заряженный xdebug, а в мозгу стучало "нет ничего невозможного". Хороший программист может легко разобраться в любом незнакомом языке, а я считал себя хорошим программистом. Java Android, первые table view с данными из интернета, и снова те же грабли. Куча непонятных вещей, вроде передачи параметров между экранами (постоянные краши в этом месте с полотном непонятных ошибок). Сам java уже давно не пугал, классы похожи на PHP, вместо parent - super, а для переопределения нужно писать @override. Приватные классы, переопределение метода прямо при создании объекта wtf??? Снова дядя, задачи, кросс ревью, скрам-митинги, спринты. Снова долгий ящик.

Strict types в php, теперь уже совсем по взрослому, в новых RFC обещают дженерики. Вот это уже настоящий прорыв, теперь мы заживем не хуже всяких java.
Новая попытка, swift, kotlin. И тут, знаете, тот самый момент, когда ты впервые увидел JavaScript.

JavaScript

(код и выводы отсюда)

const f = () => 42;

Скобки, стрелочки, да что тут вообще происходит, как это, черт возьми, работает? Но у JS свой путь, тут важна краткость, и они нашли выходы - тот же TypeScript выглядит понятно и лаконично.

Swiftbook выглядел многообещающе просто: if, for, switch, class struct, enum, tuple(офигеть удобно, почему в php такого нет?). Идея пет-проекта, быстро накиданная на symfony json API.
Красиво написанные в XCode структуры для entities, какая-то либа для дерганья апишки и конвертации json в объекты. И эпик фейл. Я настолько привык к отсутствию проблем с json в php, обкололся symfony serializerами, что полностью зашел в тупик со строгой типизацией в свифт. Моя, как я думал, великолепная апишка возвращала

{success: true, result: {SomeObjectOrArray}} или{success:false, errors: [listErrors]}


Как конвертировать ответ в объект, когда либа на вход принимает строку и название класса, а в самом классе должны быть указаны типы, а в моем случае в result - могло быть все что угодно. Да, можно было бы переписать апи так, чтобы метаинформация содержалась в коде ответа, а тело содержало только нужные объекты, но это был тупик, потому что такой вариант ни разу не приближал меня к большему пониманию swift. Психанул, подпер костылями, пошел дальше.

Еще одна попытка написать что-то, по привычке берем пример из интернетов и настраиваем под себя (на первых порах с PHP всегда прокатывало). Но вы только взгляните на параметры метода (долго искал подходящий пример, нашел не самый сложнопонимаемый, но вот. Код отсюда)

open func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath?, withSender sender: Any?) -> Bool {        guard let indexPath = indexPath else { return false }        return self.presenterForIndexPath(indexPath).canPerformMenuControllerAction(action)    }

Что тут вообще происходит, нижнее подчеркивание, какие-то ключевые фразы перед названием параметров.

Или вот:

if let JSONObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],    let username = (JSONObject[0]["user"] as? [String: Any])?["name"] as? String {        // There's our username}

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

И синтаксис - это далеко не самая главная проблема, месяц-другой и такой код будет выполняться в мозгу за считанные секунды. Проблема в понимании механики выполнения программы, асинхронности и многопоточности, и других вещей, которых PHP попросту лишен за ненадобностью. Все это придется учить с начала.

Те вещи, которые делались за считанные минуты и несколько строк кода (давайте возьмем JS, таблица, по клику на строку которой нужно эту строку удалить) теперь почему-то выглядят как пачка классов с непонятного назначения методами и, на первый взгляд, какой-то слишком усложненной логикой. Я видел отрисовку грида на UIKit, и это выглядит как мини-фреймворк. И эта реализация должна быть такой большой, ведь у нас нет умного браузера, который сделает все для вас (сейчас уже есть SwiftUI, который позволяет делать это гораздо короче, но с ним еще не все так просто). Тут нужно глубинное понимание работы, и это не просто "запустился скрипт, построчно выполнился и умер".

Я рад, что жена в свое время выбрала первым языком swift. Знаете, когда я ради интереса сделал за нее небольшое домашнее задание, первым вопросом преподавателя было "что за phpшник это писал?"

Если меня кто-нибудь спросит, какой язык ты посоветуешь для изучения новичку, едва ли я отвечу PHP. PHP язык с нулевым порогом вхождения и нет никаких проблем с его изучением. Десяток ключевых фраз, несколько конструкций, еще щепотка сложных вещей типа генераторов, стрима или замыканий. Пару минут - Hello World, пара часов - ToDo list, пара дней - мини блог. Пара лет, и ты уже Middle/Senior с запросами покруче архитектора, но без всякого понимания, как это работает внутри вида "почему мой алгоритм такой медленный". Но порог "выхода" слишком велик. Можно хорошо писать скрипты, но "взрослые" языки придется постигать почти "с нуля".

Вместо послесловия

Я люблю PHP, а точнее те вещи, которые я могу делать на нем. Люблю чистый и понятный код, который в целом легко пишется на этом языке. Как-то давно на хабре была статья о том, что хороший код и выглядит красиво, все эти отступы и скобочки, лаконичные функции без грамма лишнего. И PHP позволяет писать такой код. На мой взгляд в своей области (создание web-приложений) этот язык отлично подходит, и он должен быть именно таким (ну, или почти таким). Но являюсь ли я хорошим программистом после десятка лет разработки на PHP? Наверное нет.

Подробнее..

Так ли токсичен синтаксис Rust?

31.12.2020 18:20:33 | Автор: admin
fn main() {  println!("Hello, Rust!");  println!("... Goodbye!");}

Синтаксис это первое, на что обращают внимание разработчики, впервые столкнувшись с кодом на Rust. И сложно найти что-то другое, что вызывало бы больше негодования у новичков, и при этом такое, к чему совершенно спокойно относятся уже "понюхавшие пороха" Rust-программисты. Посмотрите, например, сколько эмоций вызывает синтаксис Rust у комментаторов одной из типичных новостей про Rust на OpenNET:


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

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

Самое интересное в этой истории то, что примеры на сайте раста типа "привет мир", почему то намного более читабельные чем то, что реально используется и лежит на гитхабе, а там адовый синтаксис с кучей спец символов и какая то ещё жуткая муть.

Сначала хотел указать вам на ошибку, но потом понял, что "синтоксис" в данном контексте, нужно понимать как "токсичный синтаксис". Вот это именно то, чего мне не хватало что бы охарактеризовать раст.
Теперь я точно знаю: Раст это ЯП с ТОКСИЧНМ СИНТАКСИСОМ!

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

Да вообще не понимаю, чего все ополчились на Rust. Нормальный язык с нормальным синтаксисом, надо просто мозгом думать.

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


Как ни печально, но встречают язык по голому синтаксису. И синтаксис Rust тут не предлагает чего-то революционного. По сути это обычный C-подобный синтаксис, который имеют и многие другие языки, но с различными современными улучшениями. И они, как ни странно, делают его лучше других, в ряде случаев выразительнее.


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


Так давайте рассмотрим подробнее, чем именно синтаксис Rust не угодил своим критикам и почему никто из них не может предложить ничего лучше, когда их просят это сделать.


Фигурные скобки


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


Выделение блоков кода одними лишь отступами имеет свои преимущества, но чаще всего оно требует серьезной дисциплины от программиста. Особенно при перемещении фрагментов кода из одного места в другое, из одного уровня вложенности в другой. Читаемость кода улучшается, но возрастает напряжение внимания при работе с кодом, возникает почва для появления досадных ошибок.


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


if (test(x))    foo(x);    bar(x);

Должен ли bar вызываться только тогда, когда test завершился успешно? По правилам языка нет, так как отсутствуют фигурные скобки, обозначающие блок. Но такое форматирование кода затрудняет для человека понимание этого. Все потому, что имеется лишняя избыточность в обозначении блоков: скобками для парсера, отступами для человека, и они могут не совпадать. Поэтому в Python решили отказаться от этой избыточности:


if test(x):    foo(x)    bar(x)

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


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


Даже без автоформатирования, Rust решает проблему случайных отступов радикальным образом:


if test(x) {    foo(x);}bar(x);

В конструкциях if, else, for, while и так далее, блок требуется обязательно! Сокращенная, бесскобочная запись тела у данных операторов в языке просто отсутствует. С одной стороны, это "загрязняет" фигурными скобками простые условия, но отчасти это компенсируется тем, что теперь круглые скобки вокруг условного выражения становятся избыточными и их можно убрать.


Вообще, в Rust блоки очень важны, они являются самостоятельными программными объектами с особыми свойствами.


Во-первых, блоки обозначают область видимости переменных. По достижении конца блока все переменные, введенные внутри блока, уничтожаются и происходит освобождение занятых ими ресурсов:


...{    let mut data = data.lock().expect("Mutex should be lockable");    data.read();    data.write();}...

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


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


let value = {    let a = 1;    let b = 2;    a + b};

Очень удобно вводить локальные блоки и компоновать их с выражениями вовне. Каким бы ни было простым это свойство блоков, оно кардинально меняет способ работы с потоком выполнения: оно позволяет не только локализовать связанные инструкции, но и приносит в поток вложенную структурность. Благодаря этому, в частности, становится ненужен специальный тернарный условный оператор (что также избавляет от ряда проблем, с ним связанных):


let value = if x < 42 { -1 } else { 1 };

А многие функции и замыкания избавляются от лишнего синтаксиса с оператором возврата:


fn add_two(a: isize) -> isize {    a + 2}

вместо


fn add_two(a: isize) -> isize {    return a + 2;}

Для специальных блоков, таких как unsafe и async, появляется возможно оборачивать ими только часть выражения, что в ряде случаев удобно и делает код нагляднее:


let a = 5 + unsafe { an_unsafe_fn() };

Сложно представить, как можно обеспечить удобную (и свободную от известных ошибок) поддержку блоков такой же функциональности, не используя скобок для их обозначения.


Точка с запятой


Отдельного рассмотрения заслуживает то, как работает точка с запятой в Rust. Многим не нравятся языки, которые требуют окончания инструкций точкой с запятой: зачем, если и так перевод строки может означать завершение инструкции? Отказ от точки с запятой оправдан, если в языке любая строка по-умолчанию является инструкцией, как в Python. Если же язык использует концепцию "все есть выражение", которая расширяет его выразительные возможности, то без специального терминатора, по которому можно отличить строку-инструкцию от строки-выражения, удобным в использовании может быть только язык с динамической типизацией, такой как Ruby. Да и в таком языке в ряде случаев подход "все есть выражение" приводит к неудобствам:


class Foo  def set_x val    @val = val    nil  endend

Здесь возникает необходимость вставлять nil в конце блока, чтобы set_x возвращал пустое значение, а не результат выражения @val = val, то есть не значение val. Если это еще терпимо, то ситуация становится совершенно неприемлемой в случае статических языков и использования ветвлений:


if x < 42 {    foo(x)} else {    bar(x)}

Здесь foo возвращает число, но мы его не используем, а bar возвращает "пусто". Какого типа должно быть значение всего условного выражения?


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


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


if x < 42 {    foo(x);} else {    bar(x)}

Здесь foo возвращает число, но мы его не используем, и нам не нужно, чтобы оно возвращалось из данной ветки условия, поэтому мы завершаем выражение точкой с запятой. При этом bar возвращает "пусто", а значит мы не обязаны ставить точку с запятой после его вызова (но можем и поставить, для симметрии), ибо типы возврата обеих веток уже совпали.


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


let message = "Hello!".to_string();message;println!("{}", message); // error[E0382]: borrow of moved value: `message`

Так что инструкция a; становится аналогом drop(a). Это нужно помнить, чтобы не удивляться, когда компилятор откажется компилировать инструкцию, в которой производится попытка сохранить ссылку на перемещенное внутрь нее значение.


В итоге подобное концептуальное расширение функций точки с запятой весьма удобно в использовании. В подавляющем большинстве случаев оно ни к каким проблемам не приводит, благодаря контролю соответствия типов и времен жизни со стороны компилятора. Что же касается "загрязнения" кода точками с запятой, то благодаря тому, что теперь она не ставится в ряде распространенных случаев, доля непустых строк в программах на Rust, содержащих точку с запятой, составляет около 20%. Не так уж и много.


Постфиксная запись типа


Иногда простота синтаксиса является кажущейся. Например, частенько задается вопрос: зачем в Rust используется постфиксная запись типа, ведь это усложняет запись, еще и приходится отделять имена от типа с помощью двоеточия, тогда как префиксная запись, принятая в C/C++, этого не требует и выглядит чище. На самом деле префиксная запись чище только в ряде простых случаев использования, но в более сложных случаях она изрядно запутывает и парсер, и человека.


Вообще, какой следует выбрать объективный критерий простоты синтаксиса? Мне видится, что немаловажно для оценки простоты синтаксиса является простота его парсера. Простой парсер может стать важным преимуществом языка программирования, так как он ускоряет время компиляции, разбор исходных файлов в IDE и утилитах анализа и автоматического преобразования кода (статические анализаторы, автоформатеры, авторефакторы и пр.). Кроме того, простота разбора синтаксиса также важна и для человека: во-первых, человеку тоже приходится "парсить" текст программы при ее чтении, а во-вторых, однозначность синтаксиса упрощает поиск нужных строк по кодовой базе, например, среди множества проектов на GitHub.


Рассмотрим пример объявления переменной определенного типа.


В Rust:


let i: u64;

В C++:


uint64_t i;

Первый вариант это "паскалевский" способ декларации типа. Хотя он дополнительно "загрязнен" символом двоеточия, однако в общем случае он проще, как для разбора парсером, так и для восприятия человеком.


Прочитав i в i: u64, парсер уже знает, что i это имя переменной, прочитав : он знает, что дальше идет имя типа, прочитав u64 он знает, что это имя типа, даже не сверяясь со списком имеющихся типов (то есть не заглядывая в семантику). Такой подход избавляет от необходимости декларировать тип до того, как будет объявлена переменная этого типа. В C/C++ из-за этого приходится отдельно делать объявления, отдельно определения, и иногда изменение порядка деклараций может изменить семантику.


Для человеческого восприятия преимущества "паскалеской" записи раскрываются в сложных случаях:


int (*fns[16])(int * const * p);

Проблема данного кода в том, что в нем синтаксис типа перемешан с синтаксисом объявляемой переменной. Чтобы в этом разобраться, нужно применять особое правило чтения по спирали.


Синтаксис же, принятый в Rust, отделяет обозначение типа от имени переменной:


let fns: [fn(*const *mut isize) -> isize; 16];

Обозначение типа при этом всегда одинаково, вне зависимости от того, указано ли оно в аннотации типа в инструкции let, в вызове size_of или в объявлении функции как тип возвращаемого значения.


Ну и стоит сказать, что "паскалевская" декларация упрощается в случае автовыведения типа:


let a = foo();

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


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


Зачем вообще нужен let?


Иногда let ругают за его избыточность и ненужность. Но обычно это делают те, кто не идет дальше элементарных примеров объявления переменных в Rust. Дело в том, что инструкция let занимается не объявлением переменных, а сопоставлением выражения с образцом (паттерном):


let PATTERN: EXPR_TYPE = EXPR;let PATTERN = EXPR;

В аннотации типа, если она требуется, указывается тип значения выражения справа, а не тип отдельных переменных, которые могут вводиться внутри паттерна (а могут и не вводиться). При этом let также используется в условиях if и while, если вместо истинности логического выражения там необходимо проверить соответствие выражения образцу:


if let PATTERN = EXPR {    ...}while let PATTERN = EXPR {    ...}

Сам синтаксис образца PATTERN общий для всех мест, где он может использоваться: помимо указанных let, if let и while let, это также оператор match, for и аргументы функций и замыканий (там уже нет надобности в отдельном слове let, так как ничего другого, кроме образца, использовать в этих местах нельзя).


struct Foo {    a: i32,}let Foo { a: mut b } = Foo { a: 25 };

В примере выше производится деструктуризация значения типа Foo и вводится новая переменная с именем b, которая получает свое значение из поля a и в дальнейшем может измениться.


В простых случаях паттерн может состоять только из одного имени новой переменной, но это только частный случай. К тому же Rust не требует, чтобы значения с новыми именами были связаны именно в той же инструкции, где эти имена были объявлены:


let a;a = 25;

Наконец, Rust поддерживает затенение переменных:


let a = 25;// `a` имеет числовой типlet a = "hello";// теперь `a` имеет строковый тип

Во всех этих случаях нужно явно отличать let a или let a = 25 от просто a или a = 25, и ключевое слово let прекрасно с этим справляется.


Сокращения в ключевых словах


Уж наверное только совсем ленивые критики синтаксиса Rust не "проехались" по сокращениям в ключевых словах. Зачастую дело выставляют таким образом, что Rust форсирует сокращения и сплошняком только из них и состоит. Но на самом деле из 40 ключевых слов языка сокращениями являются только 7:


fn, mut, mod, pub, impl, dyn, ref

При этом dyn и ref используются крайне редко. Дополнительно есть еще 5 коротких ключевых слов (в две и три буквы), которые не являются сокращениями:


if, as, let, for, use

Все остальные слова длиннее. Некоторые из них сделаны длинными намеренно, например continue и return: они используются редко и должны быть хорошо заметны в коде, хотя первоначальные версии языка вместо них использовали слова cont и ret.


Тем не менее, больше всего претензий возникает к ключевому слову fn. Предлагают обычно либо его убрать совсем, либо заменить на function.


На самом деле fn весьма неплохой выбор для достижения компромисса между двумя крайностями. Наличие длинного слова function особо не помогает при чтении кода, обычно определение функции и так заметно по остальной ее сигнатуре и обязательному блоку с ее телом. Оно только оттягивает на себя внимание от имени и аргументов функции. К тому же оно занимает слишком много места, что плохо сказывается не только на скорости набора, но в ряде случаев и на чтении кода, например, в мессенджерах.


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


let a: fn(i32, String); // `a` - указатель на функцию с двумя аргументамиlet b: (i32, String);   // `b` - кортеж из двух элементов

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


Хорошо, но почему бы не выбрать сокращение подлиннее, такое как fun или func? Это довольно спорный вопрос даже в Rust-сообществе, но fn выглядит более нейтральным, так как не является другим самостоятельным словом и не имеет никаких посторонних значений по звучанию, так что оно хорошо ассоциируется с function и является минимально возможным его сокращением.


Говоря о коротких ключевых словах и спецсимволах, стоит заметить, что код с ними становится плохо читаемым в том случае, если программист также выбирает короткие имена для объектов программы. В таком случае глаз "спотыкается", становится сложно отличить пользовательские имена от элементов языка. В случае же использования длинных имен, дискомфорта не возникает, наоборот, глазу становится проще отделять имена от синтаксических конструкций, что улучшает восприятие кода. Поэтому такие слова как fn, mut, mod, pub, impl, dyn и ref, после которых идут пользовательские имена, не затрудняют, а улучшают чтение программы, если при этом программист выбирает длинные осмысленные имена для своих объектов, а не увлекается сокращениями.


Стрелка в сигнатуре функций


Другая частая претензия к объявлению функций в Rust, это использование символа "стрелки" для разделения блока параметров от типа возвращаемого функцией результата:


fn foo(x: i32) -> bool {  ...}

Кажется, что последовательнее тут тоже использовать двоеточие:


fn foo(x: i32): bool {  ...}

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


let foo: fn(x: i32) -> bool;

Против


let foo: fn(x: i32): bool;

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


В случае если функция не должна возвращать никакого значения, в Rust считается, что она возвращает значение "пусто" (), и тип возвращаемого значения можно не указывать. То есть


fn foo() -> () {    ...}

равнозначно


fn foo() {    ...}

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


Замыкания


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


foo().map(|x| x + 2)

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


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


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


foo().map(|_| ())

Конструкция |_| () выглядит некрасиво. Она означает, что мы игнорируем входной аргумент замыкания и возвращаем из замыкания пустое значение. Подобный map полезен, когда нужно преобразовать одно значение типа Result в другое с заменой положительного значения возврата на "пусто". Однако добиться этого можно и более наглядным образом, просто передав функцию drop вместо замыкания:


foo().map(drop)

Двоеточия в пути


В Rust используется двойное двоеточие :: в качестве квалификатора пространств имен, который разделяет сегменты в логических путях к элементам (items) языка, при том, что для доступа к полям и методам структур используется точка .. Это часто раздражает людей, привыкших к языкам, где для доступа к полям и методам используется тот же синтаксис, что и для доступа к элементам внутри пространства имен. Им кажется, что синтаксически разделять эти два обращения совершенно избыточно, и Rust использует подобное разделение либо в силу скудоумия своих создателей, либо из-за желания подражать C++.


Однако на самом деле, предоставляемые Rust возможности требуют явного синтаксического разделения статических обращений xx::yy::zz и динамических xx.yy.zz, где результат будет зависеть от значения объектов xx, yy и zz во время выполнения. Возможны ситуации, когда программист должен использовать канонический путь для обращения к методу, когда ему нужно явно указать тип или типаж, на котором будет вызван данный метод:


let foo = Foo;foo.bar();      // вызов метода `bar` из `Foo`Bar::bar(&foo); // вызов метода `bar` из реализации типажа `Bar` для `Foo`

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


Foo::bar(&foo); // вызов метода `bar` из `Foo` у объекта `foo`Foo.bazz(&foo); // вызов метода `bazz` у созданного на месте объекта `Foo`

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


Дженерики


Хотя код с использованием угловых скобок для обозначения параметров обобщенных типов <T> выглядит более "шумно", чем код с использованием квадратных скобок [T], тем не менее в Rust используется вариант с угловыми скобками, потому что он однозначно отделяет тип от контекста его использования, тогда как конструкция [T] сама является обозначением типа (среза).


fn parse<F: FromStr>(&self) -> Result<F, F::Err> {    // ...}

Обобщенный метод parse на вход принимает тип F, реализующий типаж FromStr, и ссылку на экземпляр объекта &self, для которого реализуется данный метод. Возвращает же он обобщенный тип Result, в который передаются в качестве параметров типа F и ассоциированный с ним тип F::Err.


Угловые скобки используются для работы с обобщенными типами как в контексте объявления обобщенных элементов, так и в контексте выражения при подстановке уже конкретных типов. И с этим связана, пожалуй, самая уродливая конструкция языка жуткий мутант, внушающий первобытный страх любому, кто случайно заглядывает в глубины "синтОксиса" кода на Rust. Имя этому монстру Турбофиш:


"2021".parse::<usize>()

Вот этот страшный зверь ::<> и есть Турбофиш. К сожалению оказалось, что от него совсем не просто избавиться: конструкция A<B>() выглядела бы логичнее и лаконичнее, но в контексте выражения парсер не может отличить ее начало от операции сравнения A < B. Отсюда и возникает необходимость дополнить угловую скобку разделителем сегментов в пути ::. Турбофиш очень знаменит в сообществе разработчиков на Rust, и если он когда-нибудь все-таки будет устранен (что вряд ли), то этот факт опечалит многих людей. По-своему он прекрасен, и к нему уже все привыкли.


Времена жизни


Имена времен жизни в Rust указываются с префиксом в виде одинарной кавычки: 'name. Не всем это нравится, но данный синтаксис самый лаконичный из всего, что было предложено. До его введения использовался синтаксис /&name и &name/, который гораздо хуже читается. Идея синтаксиса 'name была навеяна тем фактом, что близким аналогом параметра времени жизни является параметр типа, то есть указание времени жизни по-факту делает элемент обобщенным:


struct StringReader<'a> {    value: &'a str,    count: uint}

Идентификаторы с префиксом или суффиксом в виде одинарной кавычки используются в языках семейства ML. В частности, в OCaml запись 'name используется для обозначения переменной типа в обобщенных конструкциях. Rust продолжает эту традицию.


Кроме того, в Rust нашлось еще одно интересное применение подобного синтаксиса для задания меток:


'outer: loop {    loop {        break 'outer;    }}

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


'a: {    let x: &'a T = ...;}

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


Макросы


Синтаксис описания декларативных макросов справедливо критикуют за плохую читаемость, хотя сам по себе он довольно простой и понятный. Требование смешения обычного кода с командами макроса налагает серьезные ограничения на синтаксис команд и не в пользу повышения их читаемости. Проблема эта известна, и довольно давно уже ведутся работы по улучшению системы макросов в рамках реализации "macros 2.0". Но пока приходится довольствоваться тем, что есть. Хотя синтаксис макросов действительно часто выглядит токсично, спасает то, что он крайне минималистичен и прост: имеются идентификаторы фрагментов $name и конструкции повторения $(..)+, $(..)* и $(..)?. По сути это все.


Отдельно стоит упомянуть синтаксис обращения к макросу name!. Он проектировался с целью сделать имена макросов заметнее в потоке кода, чтобы визуально можно было сразу отличить вызов макроса от вызова обычной функции. Это важно, так как макросы расширяют синтаксис языка и принимают в качестве параметра код с пользовательским синтаксисом, то есть синтаксис самого выражения, а не его вычисленное значение. Восклицательный знак хорошо справляется со своей задачей, вызов становится заметным, но не настолько, чтобы перетянуть на себя все внимание (как происходит с другими, широкими символами, вроде @). Интуитивно восклицательный знак можно воспринимать как команду активного действия: макрос разворачивается во время компиляции, тогда как обычная функция остается в этом отношении пассивной. В похожем отношении восклицательный знак также используется в языке D при инстанциации шаблонов.


Собираем все вместе


Хорошо, допустим, каждый из элементов синтаксиса относительно неплох, но как они сочетаются все вместе? Разве их комбинация не превращает Rust-код в нечитаемое месиво из спецсимволов? Например:


fn foo<'a, T: FromStr, I: IntoIterator<Item = T>, F: Fn(T) -> bool>(    self: &'a Self,    first: T,    callable: F,    iter: I,) -> Result<&'a T, T::Err> {    // ...}

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


fn foo<T, I>(&self, first: T, callable: impl Fn(T) -> bool, iter: I) -> MyResult<T>where    T: FromStr,    I: IntoIterator<Item = T>,{    // ...}

Сигнатура метода foo уже не выглядит настолько страшно, как в исходном варианте. Она не идеальна, но сравнивая с тем, что было, все же заметен явный прогресс в улучшении читаемости. Видно, что алиасы, автовывод времен жизни, сахар для указания self, impl Trait в позиции аргумента, блок where и прочее, введены в язык не случайно и они сильно упрощают чтение действительно сложных мест, с непростой семантикой.


Заключение


Как видите, синтаксис Rust не так уж и плох. По крайней мере любой элемент синтаксиса продуман глубже, чем кажется при поверхностном взгляде и выбран именно таким по веским причинам соблюдения баланса простоты, единообразия и выразительности. Отчего же столько негодования и яростных воплей разносится по сети, насчет "токсичности" синтаксиса Rust? Я думаю главной причиной является объективная сложность и непривычность концепций, которые скрываются за этим синтаксисом, а также высокая информативная плотность кода на Rust. Новичку это вселяет ужас, а опытному Rust-программисту облегчает жизнь, так как расширяет его возможности, обеспечивает высокую степень явности и локализованности кода.


Источники


Подробнее..

Перевод Rust 1.49.0 aarch64 и улучшения во фреймворке тестирования

02.01.2021 16:06:38 | Автор: admin

Команда Rust рада сообщить о выпуске новой версии, 1.49.0. Rust это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.49.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что вошло в стабильную версию 1.49.0


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


64-bit ARM Linux перешёл в Tier 1


Компилятор Rust поддерживает широкий спектр платформ, но команда Rust не может поддерживать все их на одном уровне. Для более явного указания уровня поддержки каждой платформы, мы используем многоуровневую систему:


  • Tier 3 технически, платформы поддерживаются компилятором, но мы не проверяли собирается ли на них код или проходят ли тесты, и мы не предоставляем каких-либо бинарных артефактов, как часть наших релизов.
  • Платформы из Tier 2 гарантированно собираются, но мы не запускаем для них тесты: предоставляемые артефакты могут быть не рабочими или быть с багами.
  • Для платформ из Tier 1 предоставляются наибольшие гарантии и мы запускаем все тесты для этих платформ для каждого изменения, влитого в компилятор. Также мы предоставляем для них собранные артефакты.

Начиная с Rust 1.49.0 платформа aarch64-unknown-linux-gnu передвигается на уровень поддержки Tier 1, предоставляя наши наибольшие гарантии для пользователей Linux, запущенных на 64-bit ARM системах! Мы ожидаем, что данное изменение принесёт пользу для всех: от встраиваемых систем до обычных компьютеров и серверов.


Это важный этап для нашего проекта, поскольку это первая не-x86 платформа, получившая Tier 1 поддержку: мы надеемся, этот шаг откроет путь для большего количества платформ, которые смогу достичь нашего высшего уровня поддержки.


Обратим внимание, что Android не затрагивается данным изменением, так как он представлен другой платформой, находящейся в Tier 2.


64-bit ARM macOS и Windows переходят в Tier 2


В Rust 1.49.0 ещё две платформы достигли Tier 2:


  • Платформа aarch64-apple-darwin предоставляет поддержку Rust на системах Apple M1.
  • aarch64-pc-windows-msvc предоставляет поддержку Rust на 64-bit ARM устройствах, работающих под Windows.

Теперь разработчики могут установить обе эти платформы при помощи rustup! Команда Rust не запускает тесты для данных платформ, так что они могут содержать баги или быть нестабильными.


Фреймворк для тестирования захватывает вывод из потоков


Встроенный в Rust фреймворк для тестирования довольно минималистичен, но это не значит, что его нельзя улучшить! Представьте, что тест выглядит примерно так:


#[test]fn thready_pass() {    println!("fee");    std::thread::spawn(|| {        println!("fie");        println!("foe");    })    .join()    .unwrap();    println!("fum");}

Вот как выглядел запуск этого теста до Rust 1.49.0:


 cargo +1.48.0 test   Compiling threadtest v0.1.0 (C:\threadtest)    Finished test [unoptimized + debuginfo] target(s) in 0.38s     Running target\debug\deps\threadtest-02f42ffd9836cae5.exerunning 1 testfiefoetest thready_pass ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out   Doc-tests threadtestrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Вы можете заметить, что вывод из потоков напечатался и смешался с выводом самого фреймворка. Было бы неплохо, если бы каждый println! работал также, как тот, что напечатал "fum"? Что ж, таково поведение в Rust 1.49.0:


 cargo test   Compiling threadtest v0.1.0 (C:\threadtest)    Finished test [unoptimized + debuginfo] target(s) in 0.52s     Running target\debug\deps\threadtest-40aabfaa345584be.exerunning 1 testtest thready_pass ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s   Doc-tests threadtestrunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Но не расстраивайтесь, если тест упадёт, вы увидите всё, что он хотел вывести. Добавив в конец теста panic!, вы можете увидеть примерно такую ошибку:


 cargo test   Compiling threadtest v0.1.0 (C:\threadtest)    Finished test [unoptimized + debuginfo] target(s) in 0.52s     Running target\debug\deps\threadtest-40aabfaa345584be.exerunning 1 testtest thready_pass ... FAILEDfailures:---- thready_pass stdout ----feefiefoefumthread 'thready_pass' panicked at 'explicit panic', src\lib.rs:11:5

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


Изменения в стандартной библиотеке


В Rust 1.49.0 были стабилизированы следующие 3 функции:



И ещё две функции стали const:



Смотрите подробные примечания к выпуску для более детальной информации.


Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.49.0


Множество людей собрались вместе, чтобы создать Rust 1.49.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели andreevlex, funkill, ozkriff, blandger и fan-tom.

Подробнее..

Перевод Rust 1.50.0 улучшение индексации массивов, безопасность полей объединений и усовершенствование файловых дескрипторов

12.02.2021 12:08:58 | Автор: admin

Команда Rust рада сообщить о выпуске новой версии 1.50.0. Rust это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.50.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что вошло в стабильную версию 1.50.0


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


Константные обобщения при индексации массива


Продолжая движение к стабилизации константных обобщений, этот выпуск добавляет реализации ops::Index и IndexMut для массивов [T; N] любой длины const N. Оператор индексации [] уже работал с массивами с помощью встроенной магии компилятора, но на уровне типа массивы до сих пор фактически не реализовывали библиотечные типажи.


fn second<C>(container: &C) -> &C::Outputwhere    C: std::ops::Index<usize> + ?Sized,{    &container[1]}fn main() {    let array: [i32; 3] = [1, 2, 3];    assert_eq!(second(&array[..]), &2); // срезы работали ранее    assert_eq!(second(&array), &2); // теперь это работает напрямую}

const повторение значений массива


Массивы в Rust могут быть записаны как в форме списков [a, b, c], так и в форме повторений [x; N]. Повторения разрешены для длины N большей, чем один, только для x, реализующих типаж Copy, и в рамках RFC 2203 мы стремились разрешить любые const выражения. Однако пока эта функциональность была нестабильна для произвольных выражений, лишь начиная с Rust 1.38 её реализация случайно позволила использовать const значения в повторениях массивов.


fn main() {    // Это не разрешено, так как `Option<Vec<i32>>` не реализует `Copy`.    let array: [Option<Vec<i32>>; 10] = [None; 10];    const NONE: Option<Vec<i32>> = None;    const EMPTY: Option<Vec<i32>> = Some(Vec::new());    // Однако повторения с `const` значениями разрешены!    let nones = [NONE; 10];    let empties = [EMPTY; 10];}

В Rust 1.50 эта возможность признана официально, так что вы можете использовать такие конструкции без опасений. В будущем для того, чтобы избежать "временных" именований констант, вы можете использовать встроенные выражения const согласно RFC 2920.


Безопасные присвоения полям объединения ManuallyDrop<T>


В Rust 1.49 появилась возможность добавлять поля ManuallyDrop<T> в union, позволяющая таким образом использовать Drop для объединений. Однако объединения не сбрасывают старые значения во время присваивания полей, так как не знают, какой из вариантов ранее был действителен. Поэтому из соображений безопасности Rust ранее ограничивался только типами Copy, которые не используют Drop. Разумеется, ManuallyDrop<T> не требует Drop, поэтому теперь Rust 1.50 расценивает присваивания и этим полям как безопасные.


Ниша для File на Unix платформах


У некоторых типов в Rust есть определённые ограничения на то, какое значение считать допустимым, поскольку они могут выходить за границы допустимых значений диапазона памяти. Мы называем любое оставшееся недопустимое значение нишей (niche), и это пространство может быть использовано для оптимизации схемы размещения типов. Например, в Rust 1.28 мы представили целочисленные типы NonZero, такие как NonZeroU8, где 0 ниша. Это позволило Option<NonZero> использовать 0, чтобы представить None без использования дополнительной памяти.


На Unix-платформах File это просто системный целочисленный файловый дескриптор. Это значит, что у нас есть возможная ниша ведь он никогда не может быть -1! Системные вызовы, которые возвращают файловый дескриптор, используют -1 для обозначения того, что произошла ошибка (проверяется errno), так что -1 никогда не будет действительным файловым дескриптором. Начиная с Rust 1.50 эта ниша добавлена в определение типа и тоже может быть использована для оптимизации размещения значения в памяти. И следовательно Option<File> теперь имеет такой же размер, как и File!


Изменения в стандартной библиотеке


В Rust 1.50.0 были стабилизированы следующие 9 функций:



И довольно много существующих функций стало const:


  • IpAddr::is_ipv4
  • IpAddr::is_ipv6
  • Layout::size
  • Layout::align
  • Layout::from_size_align
  • pow для всех целочисленных типов
  • checked_pow для всех целочисленных типов
  • saturating_pow для всех целочисленных типов
  • wrapping_pow для всех целочисленных типов
  • next_power_of_two для всех беззнаковых целочисленных типов
  • checked_power_of_two для всех беззнаковых целочисленных типов

Смотрите подробные примечания к выпуску для более детальной информации.


Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.50.0


Множество людей собрались вместе, чтобы создать Rust 1.50.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey, blandger, nlinker и funkill.

Подробнее..

Перевод Планирование редакции Rust 2021

05.03.2021 22:04:25 | Автор: admin

Рабочая группа Rust 2021 Edition рада сообщить, что следующая редакция Rust Rust 2021 запланирована на этот год. Пока что формально описывающий её RFC остаётся открытым, но мы ожидаем, что в скором времени он будет принят. Планирование и подготовка уже начались, и мы идём по графику!


Если вам интересно, какие новшества появятся в Rust 2021 или когда эта редакция выйдет в стабильной версии, читайте нашу статью!


Что входит в эту редакцию?


Конечный список нововведений, которые войдут в Rust 2021, ещё не определён до конца. В целом мы планируем, что выпуск Rust 2021 будет намного меньше, чем Rust 2018, по следующим причинам:


  • Ритм выпусков стал регулярным Это значит, что мы будем активно использовать плюсы "цепочечной" модели на уровне редакций Rust.
  • Редакция Rust 2018 выбилась из модели "минимального стресса" выпусков.
  • Сейчас просто нужно меньше фундаментальных изменений, чтобы язык продолжал развиваться.

Более подробно о развитии концепции редакций вы можете почитать в RFC.


Решение, войдёт ли та или иная функциональность в Rust 2021, является частью процесса RFC поэтому список ожидаемых функций может и будет меняться. Это будет происходить до самого момента выпуска, но тем не менее, уже сейчас мы можем рассмотреть список функций, которые, скорее всего, в неё войдут.


Изменения в прелюдии


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


Сейчас в редакцию Rust 2021 предложено включить следующие трейты:


  • TryFrom/TryInto
  • FromIterator

RFC с этими изменениями можно найти тут. Обратите внимание, что RFC ещё не принят состав новой прелюдии активно обсуждается.


Новые правила захвата


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


Новый распознаватель функциональности в Cargo по умолчанию


В Rust 1.51 будет стабилизирован новый распознаватель функциональности в Cargo, который разрешит зависимостям пакета использовать разную функциональность в разных контекстах. Например, пакет с #[no_std] сможет использовать одну и ту же зависимость и во время сборки (build-dependencies с включённым std), и как обычную зависимость (без std). Пока что это приводит к тому, что std будет включена в обоих случаях, так как функциональность находится в глобальном пространстве имён.


В Rust 2021 этот распознаватель будет включён по умолчанию, но его можно использовать и в старых версиях, просто выбрав вручную.


Прочие изменения


Другие предложенные изменения включают унификацию работы panic в std и core и обновление уровня некоторых проверок с предупреждений до ошибок.


Полный список обсуждаемых функций вы можете найти здесь.


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


Примерный график


Итак, когда же мы планируем выпустить новую редакцию? Вот график основных этапов, к которому мы стремимся:


  • 1 апреля все релевантные редакции RFC или приняты, или в хорошем состоянии (т. е. все основные вопросы решены, и принятие RFC произойдёт в ближайшие недели).
  • 1 мая все нововведения, включённые в Rust 2021, находятся в Nightly с соответствующими feature-флагами.
  • 1 июня все проверки добавлены в Nightly.
  • 1 сентября редакция стабилизирована в Nightly.
  • 21 октября редакция полностью стабилизирована.

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


Приглашаем к участию


Если вы хотите помочь в выпуске редакции Rust 2021 пожалуйста, свяжитесь с нами. Помимо работы над функциональностью и планированием выпуска, у будет ещё очень много других важных дел. Вот только часть того, что необходимо для успешного выпуска:


  • rustfix миграции для всех соответствующих функций,
  • тестирование всех функций и путей их миграции,
  • сообщения в блогах и другие маркетинговые материалы.

От переводчиков


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


Данную статью совместными усилиями перевели blandger, TelegaOvoshey, funkill и andreevlex.

Подробнее..

Перевод Rust 1.51.0 const generics MVP, новый распознаватель функциональности Cargo

26.03.2021 20:17:43 | Автор: admin

Команда Rust рада сообщить о выпуске новой версии 1.51.0. Rust это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.51.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.51.0


Этот выпуск представляет одно из наиболее крупных дополнений языка Rust и Cargo за долгое время, включающее в себя стабилизацию константных обобщений в минимально полезном варианте и новый распознаватель функциональности в Cargo. Давайте посмотрим подробнее!


Константные обобщения (Const Generics MVP)


До этого выпуска Rust позволял параметризовать типы по времени жизни или по типам. Например, если бы мы хотели получить struct, которая является обобщённой для массива, мы бы написали следующее:


struct FixedArray<T> {              // ^^^ Определение обобщённого типа.    list: [T; 32]        // ^ Где мы использовали его.}

Если затем мы используем FixedArray<u8>, компилятор создаст мономорфизированную версию FixedArray, которая выглядит так:


struct FixedArray<u8> {    list: [u8; 32]}

Этот полезный функционал позволяет писать повторно используемый код без дополнительных затрат во время выполнения. Однако до этого выпуска у нас не было возможности легко объединять значения таких типов. Это наиболее заметно в массивах, где длина указывается в определении типа ([T; N]). Теперь в версии 1.51.0 вы можете писать код, который будет обобщённым для значений любого числа, типа bool или char! (Использование значений struct и enum по-прежнему не стабилизировано.)


Это изменение позволяет создать массив, обобщённый по типу и длине. Давайте посмотрим на пример определения и то, как его можно использовать.


struct Array<T, const LENGTH: usize> {    //          ^^^^^^^^^^^^^^^^^^^ Определение константного обобщения.    list: [T; LENGTH]    //        ^^^^^^ Мы использовали его здесь.}

Теперь если мы используем Array<u8, 32>, компилятор создаст мономорфизированную версию Array, которая выглядит так:


struct Array<u8, 32> {    list: [u8; 32]}

Константные обобщения добавляют важный новый инструмент для разработчиков библиотек, чтобы создавать новые, мощные и безопасных API во время компиляции. Если вы хотите узнать больше о константных обобщениях, можете почитать статью в блоге Const Generics MVP Hits Beta для получения дополнительной информации об этой функции и её текущих ограничениях. Нам не терпится увидеть, какие новые библиотеки и API вы создадите!


Стабилизация array::IntoIter


Как часть стабилизации константных обобщений, мы также стабилизировали использующее их новое API std::array::IntoIter. IntoIter позволяет вам создать поверх массива итератор по значению. Ранее не было удобного способа итерироваться по самим значениям, только по ссылкам.


fn main() {  let array = [1, 2, 3, 4, 5];  // Раньше  for item in array.iter().copied() {      println!("{}", item);  }  // Теперь  for item in std::array::IntoIter::new(array) {      println!("{}", item);  }}

Обратите внимание, что это было добавлено вместо .into_iter() как отдельный метод, так как сейчас оно ломает текущее соглашение о том, что .into_iter() относится к срезам по ссылочному итератору. Мы изучаем возможности в будущем сделать такой код более эргономичным.


Новый распознаватель функциональности Cargo


Управление зависимостями сложная задача, и одна из самых сложных её частей выбор версии зависимости, когда от неё зависят два разных пакета. Здесь учитывается не только номер версии, но и то, какая функциональность была включена или выключена для пакета. По умолчанию Cargo объединяет функциональные флаги (features) для одного пакета, если он встречается в графе зависимостей несколько раз.


Например, у вас есть зависимость foo с функциональными флагами A и B, которые используются пакетами bar and baz, но bar зависит от foo+A, а baz от foo+B. Cargo объединит оба флага и соберёт foo как foo+AB. Выгода здесь в том, что foo будет собран только один раз и далее будет использован и для bar, и для baz.


Но у этого решения есть и обратная сторона. Что, если функциональный флаг подключён как build-зависимость, но не совместим с конечной целью сборки?


Общим примером этого из экосистемы может служить опциональная функциональность std во многих #![no_std] пакетах, которая позволяет этим пакетам предоставить дополнительную функциональность, если она включена. Теперь представим, что вы хотите использовать #![no_std] версию foo в вашей #![no_std] программе и использовать foo во время сборки в build.rs. Так как во время сборки вы зависите от foo+std, то и ваша программа тоже зависит от foo+std, а значит более не может быть скомпилирована, так как std не доступна для вашей целевой платформы.


Это была давняя проблема в Cargo, и с этим выпуском появилась новая опция resolver в вашем Cargo.toml, где вы можете установить resolver="2", чтобы попробовать новый подход к разрешению функциональных флагов. Вы можете ознакомиться с RFC 2957 для получения подробного описания поведения, которое можно резюмировать следующим образом.


  • Dev dependencies когда пакет используется совместно как обычная зависимость и dev, возможности dev-зависимости включаются только в том случае, если текущая сборка включает dev-зависимости.
  • Host Dependencies когда пакет совместно используется как обычная зависимость и зависимость сборки или процедурный макрос, features для нормальной зависимости сохраняются независимо от зависимости сборки или процедурного макроса.
  • Target dependencies когда у пакета включены зависимые от платформы features, и он присутствует в графе сборки несколько раз, будут включены только features, подходящие текущей платформе сборки.

Хотя это может привести к компиляции некоторых пакетов более одного раза, это должно обеспечить гораздо более интуитивный опыт разработки при использовании функций с Cargo. Если вы хотите узнать больше, вы также можете прочитать раздел Feature Resolver в Cargo Book для получения дополнительной информации. Мы хотели бы поблагодарить команду Cargo и всех участников за их тяжёлую работу по разработке и внедрению нового механизма!


[package]resolver = "2"# Или если вы используете workspace[workspace]resolver = "2"

Разделение отладочной информации


Хоть это и нечасто освещается в релизах, команда Rust постоянно работает над сокращением времени компиляции. В этом выпуске вносится самое крупное улучшение за долгое время для Rust на macOS. Отладочная информация исходного кода содержится в собранном бинарнике, и за счет этого программа может дать больше информации о том, что происходит во время исполнения. Раньше в macOS отладочная информация собиралась в единую директорию .dSYM при помощи утилиты dsymutil, что могло занимать много времени и дискового пространства.


Сбор всей отладочной информации в эту директорию помогал найти её во время выполнения, особенно если бинарник перемещался. Однако у такого решения есть и обратная сторона: если вы сделали небольшое изменение в вашей программе, то dsymutil необходимо запустить над всем собранным бинарником, чтобы собрать директорию .dSYM. Иногда это могло сильно увеличить время сборки, особенно для крупных проектов, поскольку надо перебирать все зависимости, но это важный шаг, без которого стандартная библиотека Rust не знает, как загружать отладочную информацию на macOS.


Недавно обратная трассировка в Rust была переключена на другой бэкенд, который поддерживает загрузку отладочной информации без запуска dsymutil. Она была стабилизирована, чтобы убрать запуск dsymutil. Это может значительно ускорить сборки, которые включают отладочную информацию, и уменьшить размер занимаемого пространства. Мы не проводили обширных тестов, но у нас есть множество отчётов сборок, которые стали с таким поведением на macOS намного быстрее.


Вы можете включить новое поведение, установив флаг -Csplit-debuginfo=unpacked при запуске rustc или задав опцию split-debuginfo в unpacked раздела [profile] в Cargo. С опцией "unpacked" rustc будет оставлять объектные файлы (.o) в директории сборки вместо их удаления и пропустит запуск dsymutil. Поддержка бэктрейсов Rust достаточно умна, чтобы понять, как найти эти .o файлы. Такие инструменты, как lldb, также знают, как это делается. Это должно работать до тех пор, пока вам не понадобится переместить бинарники в другое место и сохранить отладочную информацию.


[profile.dev]split-debuginfo = "unpacked"

Стабилизированные API


Итого: в этом выпуске было стабилизировано 18 новых методов для разных типов, например slice и Peekable. Одним из примечательных дополнений является стабилизация ptr::addr_of! и ptr::addr_of_mut!, которая позволяет вам создавать сырые указатели для полей без выравнивания. Ранее это было невозможно, так как Rust требовал, чтобы &/&mut были выровнены и указывали на инициализированные данные. Из-за этого преобразование &addr as *const _ приводило к неопределённому поведению, так как &addr должно быть выровнено. Теперь эти два макроса позволяют вам безопасно создать невыровненные указатели.


use std::ptr;#[repr(packed)]struct Packed {    f1: u8,    f2: u16,}let packed = Packed { f1: 1, f2: 2 };// `&packed.f2` будет создана ссылка на невыровненную память, таким образом это неопределённое поведение!let raw_f2 = ptr::addr_of!(packed.f2);assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);

Следующие методы были стабилизированы:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.51.0


Множество людей собрались вместе, чтобы создать Rust 1.51.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey, blandger, nlinker и funkill.

Подробнее..

Вышел язык программирования Crystal версии 1.0 достоинства, возможности и немного истории

29.03.2021 16:17:23 | Автор: admin

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

Его создатели утверждают, что язык является простым, как Ruby, и быстрым, как С. Назвать его новым нельзя, поскольку разрабатывается он с 2011 года, именно как альтернатива Ruby. Авторы проекта заявляют, что язык теперь готов к использованию в обычных условиях, и является стабильным.

Что это за язык такой?


Если вы не слышали о проекте раньше, то вот краткое его описание. Crystal это высокоуровневый объектно-ориентированный язык программирования. Его синтаксис очень похож на синтаксис Ruby, но отличается от своего конкурента тем, что компилируется в машинный код посредством LLVM.

По словам разработчиков и сторонних компаний, которые уже протестировали язык, производительность приложений, написанных на Crystal сравнима с приложениями на С. Последний считается более сложным для освоения, чем Ruby. Таким образом, создатели Crystal убивают сразу двух зайцев дают возможность писать быстрые приложения, и обеспечивают низкий порог входа в разработку.

Что особенного в Crystal?


В первую очередь, то, что это язык со статической типизацией. Этим он отличается от Ruby. На практике это означает, что ошибки несоответствия типов переменных выявляются компилятором уже на стадии переработки исходного кода в машинный, а не в процессе его выполнения интерпретатором.

При этом язык не нуждается в указании конкретных типов переменных либо аргументов метода. Дело в том, что компилятор выводит их самостоятельно при помощи специализированного механизма. Разработчики предусмотрели проблему автоматического управления памятью за счет использования консервативного сборщика мусора (garbage collector) Boehm GC. Язык поддерживает как макросы, так и дженерики, плюс способен работать с перегрузкой методов и операторов.

Преимущество Crystal в том, что он, как и Ruby, предлагает независимую от ОС реализацию многопоточности. Легковесные потоки в Crystal называются волокнами (fibers). Потоки, как и в языках Go и Clojure, могут взаимодействовать друг с другом посредством каналов, без необходимости прибегать к использованию общей памяти либо же блокировкам.

В Crystal реализован интерфейс вызова функций из библиотек языка С. При этом синтаксис взаимодействия простой соответственно, с использованием Crystal можно создавать библиотеки-обертки, без необходимости писать код с нуля.

Стандартная библиотека языка представляет широкий спектр типовых функций, включая средства для обработки CSV, YAML, и JSON, компоненты для создания HTTP-серверов и поддержки WebSocket. В процессе разработки предлагается использовать команду crystal play которая формирует web-интерфейс (по умолчанию localhost:8080) для интерактивного выполнения кода на языке Crystal.

Что изменилось с релизом 1.0?


Изменений не так много, но их можно назвать критически важными.

Во-первых, в кортежах (tuples) теперь можно обращаться к элементам отличающихся типов по индексу. Компилятор в состоянии определить, к какому типу каждый из них относится.

Во-вторых, стандартную библиотеку очистили от устаревших и не рекомендованных к использованию определений. Это, например, методы:

  • HTTP::Request,
  • HTTP::WebSocket,
  • HTTP::LogHandler,
  • URI#full_path,
  • Time::Span#duration.

В-третьих, разработчики изменили принцип обработки cookie. В финальном релизе метод HTTP::Cookies.from_headers разделен на отдельные серверную и клиентскую версии. Соответственно, значения и имена файлов не кодируются/декодируются из соображений безопасности.

Есть и изменения, которые уже внесены, но пока что не поддерживаются официально. В их числе многопоточность, которая активируется в среде с многоядерным процессором при помощи флага -Dpreview_mt, поддержка ОС Windows и процессоров с архитектурой ARM.

Есть и несколько менее значительных изменений:

  • В строковых и символьных литералах запрещено использование суррогатных сокращений в escape-последовательностях Unicode, таких как "\uD834". Для кодирования произвольных значений следует использовать нотацию "\x".
  • Метод округления по умолчанию изменен на TIES_EVEN (округление банкира, до ближайшего целого, а для пограничных ситуаций до ближайшего четного числа). В Number#round добавлен параметр RoundingMode, позволяющий выбрать метод округления. Среди доступных методов: TIES_EVEN, TIES_AWAY, TO_ZERO, TO_POSITIVE, TO_NEGATIVE.
  • В коллекциях обеспечена работа Enumerable#flat_map и Iterator#flat_map с элементами смешанных типов.
  • При сериализации последовательностей Enum теперь используется представление в форме строк с подчеркиванием.
  • Типы, определенные в модуле XML, переведены с использования struct на class.

Ну и немного истории


Как и говорилось выше, разработка языка началась еще 10 лет назад. Авторы проекта основатели аргентинской компании Manas Technology Solutions. Изначально язык назывался Joy, а компилятор для него был создан на Ruby. Чуть позже, в 2013 году, он был переписан уже на Crystal.

Официальная версия языка была выпущена в 2014 году. С течением времени Crystal стал проектом с открытым исходным кодом, который распространяется на условиях лицензии Apache License 2.0.

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

Подробнее..

Перевод Однобуквенные языки программирования

17.04.2021 10:20:32 | Автор: admin
image

image

Список языков программирования, названных одной буквой.

А


Язык программирования A+ является потомком APL, как и другие языки на этой странице, потому что сообщество APL любит однобуквенные имена. Артур Уитни (создатель многих диалектов APL) создал A, затем Морган Стенли расширил его до A+.

B


Язык программирования B является предшественником C и в наши дни больше не используется.

C


Нет необходимости представлять C. Если эта статья вас интересует, то вы знаете C. Мы также можем считать C++ и C #, поскольку разрешены небуквенные и нецифровые символы.

D


D это улучшенный C++. В настоящее время это мой самый любимый язык, поэтому все будет очень предвзято. Если у вас есть возможность выбирать языки, считайте это рекомендацией проверить D!

E


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

Существует также Amiga E, который часто называли просто E. Воутер ван Оортмерссен задумывал его как язык сценариев для игр и описывает его как огромный успех, он стал одним из самых популярных языков программирования на amiga. Он доступен как бесплатная программа.

F


F# относительно хорошо известен. По сути, O'Caml портировали на .NET.

Также существует F, который является подмножеством Fortran. Он должен быть проще в обучении, использовании и откладке, чем полный Fortran.

F* это функциональный язык программирования типа ML, предназначенный для проверки программ. Основным текущим вариантом использования F* является создание проверенной и удобной замены всего стека HTTPS.

G


G-код также называется языком программирования G, поэтому он подходит. Это язык программирования с числовым программным управлением, который в основном используется для программирования станков с ЧПУ. Похоже на ассемблеры.

Внутри LabView есть настоящий язык программирования G. Это язык графического потока данных.

H


H текстовый язык со слабой типизацией. О нём известно не так много.

Есть еще один H, который не менее полезен.

Одна из вещей, которые я хочу делать с компьютерами, это создавать искусство ради искусства. H один из таких проектов. H не является продуктивным инструментом. Вы не можете создать ничего полезного с помощью H. Это упражнение по созданию компилятора и среды выполнения с нуля, основанное на моем прошлом опыте синтаксического анализа ложбана, WebAssembly на сервере и разочаровывающего маркетинга инструментов программирования. Я хотел создать что-то, что намеренно выявляет все распространенные способы рекламы языков программирования и инструментов.


I


I это язык, вдохновленный J, который хочет расширить фокус с массивов на большее количество структур данных.

J


J еще один потомок APL и, вероятно, самый популярный. Например, в Rosetta Code J один из наиболее популярных языков.

K


K один из главных потомков APL Артура Уитни. Это коммерческий продукт, используемый в банках для финансирования и торговли.

L


L был языком, который дал синтаксис C для TCL.

L является братом E по HP Labs.

L это подмножество Common Lisp.

L это теоретический язык в книге Вычислимость, сложность и языки: основы теоретической информатики.

M


Язык M был изобретен Французским Управлением государственных финансов (DGFiP), эквивалентным IRS, для преобразования налогового кода в машиночитаемые инструкции. Это небольшой предметно-ориентированный язык, основанный на объявлениях переменных и арифметических операциях. Реверс-инжиниринг компилятора доступен здесь.

M# ориентирован на бизнес-приложения и веб-сайты .NET.

Язык программирования MUMPS также называют M.

Microsoft Power Query содержит язык формул M.

N


Из запейволленой публикации 1989 года:

Ожидая в ближайшем будущем широкого использования алгоритмов нейронных сетей, наша цель получить полную среду разработки программного обеспечения для программирования и тестирования новых приложений. Мы намерены создать язык высокого уровня для спецификации нейронных сетей как часть такой среды. Предлагаемый нами язык отличается высокой степенью модульности, основанной на параметризуемых структурах данных с функциями в виде прикрепленных к ним методов обновления. Правила построения структур и методы позволяют шаг за шагом строить более сложные конструкции из более мелких, определенных ранее. Объекты рассматриваются как автономные модули, соединенные между собой заглушками для связи. Мы особенно позаботились о распараллеливании методов, работающих одновременно на разных объектах сети. Синтаксис во многом похож на синтаксис языков C и C++.


Я не уверен, сработает ли это когда-нибудь, поскольку статья больше похожа на план с предложениями вроде Ожидается, что полный симулятор на C будет запущен в начале 1990-х.

О


O это стековый язык с однобуквенными командами. Например, io читает строку ввода (i), а затем выводит ее (o).

P


Язык программирования P предназначен для асинхронного программирования, управляемого событиями. Он использовался для реализации и проверки стека драйверов USB-устройств, поставляемого с Microsoft Windows 8 и Windows Phone.

P это примитивный формальный язык с 1964 года. Это был первый язык без GOTO, подтвержденного полной по Тьюрингу. Brainfuck это P плюс IO.

P# это интерпретатор Пролога для .NET.

Q


Q это оболочка вокруг K и базы данных kdb+, чтобы сделать ее более читаемой.

Другой язык Q это функциональный язык программирования, основанный на переписывании терминов. Его сменил Pure.

Существует также Q#, предметно-ориентированный язык программирования, используемый для выражения квантовых алгоритмов. Он должен использоваться для написания подпрограмм, которые выполняются на дополнительном квантовом процессоре под управлением классической главной программы и компьютера.

R


R хорошо известный язык статистического программирования. Он считается наравне с коммерческими инструментами, такими как SAS.

S


S это язык статистического программирования, а R считается реализацией. Большая часть кода S работает в R.

Т


T диалект Scheme или Lisp. Последний релиз был в 1984 году, так что его можно считать мертвым.

U


Язык программирования U личный проект Роба Апкрафта. Он хотел простой C-подобный язык для написания собственной операционной системы.

V


В сводке за 1985 год упоминается язык программирования V.

Более новый V с огромными амбициями в альфа-версии.

W


W был создан Виктором Тотом в 2001 году для программирования двух старинных компьютеров от HP. Это очень простой язык, описываемый как C, без ключевых слов, типов и стандартной библиотеки.

X


X# это язык программирования низкого уровня, где-то между сборкой x86 и C. Он разработан в рамках Cosmos, набора инструментов для операционной системы с открытым исходным кодом.

X++ это язык программирования, используемый в одном из программных продуктов Microsoft для планирования ресурсов предприятия. Он является производным от C++ и добавляет сборщик мусора и синтаксис запросов SQL.

Y


Y Programming Language и Y.

Z


Z-нотация это формальный язык спецификаций, стандартизированный как ISO/IEC13568:2002.

Другой Z крошечный, строгий, нечистый, каррированный, частично прикладной язык программирования с довольно своеобразным синтаксисом.

Заключение


Если вы ищете свободное имя, его нет. Однако вы, вероятно, можете переписать H, I, T, V или W.

С другой стороны, зачем давать языку имя, недоступное для Google?

Подробнее..

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

26.04.2021 18:04:48 | Автор: admin

При разработке клиент-серверного приложения, у меня всегда появляется вопрос, а как я его буду разворачивать на сервере, упаковать его в jar/war/docker после написания кода, а потом еще надо передать на сервер, и еще много сделать телодвижений чтоб просто засунуть кусок кода на сервере.

Было бы хорошо просто передать на сервер код, как лямбду, так же как мы передаем лямбду в функцию, так же на сервер ее передать.

Но у меня появилась мысль как сделать этот процесс проще, и у меня что-то получилось.

1 | var query = TcpQuery2 |    .create(IEnv.class).host("myserver.com").port(9988)3 |      .build();4 |5 |  query.apply( 6 |      env -> env.processes().stream().filter(7 |          p -> p.getName().contains("java")8 |      )9 |      .collect(Collectors.toList())10|  ).forEach(System.out::println);

Вот код на java,

  • Строки с 1 по 5 включительно и 10 - работают на клиенте

  • А строки с 6 по 9 (тело лямбды env->...) работают на сервере

И это не псевдокод, это реальный рабочий код на Java (11).

Действительно код строк с 6 по 9 (само тело лямбды) передаются на сервер и исполняются на сервере, при том сервер о этом куске кода ничего не знает, он принимает этот код исполняет у себя, и отдает обратно клиенту - а я дальше продолжаю писать код без всякого deploy, не выходя из IDE (Idea/Eclipse/etc...).

Статья будет о том, что такое Serializable Lambda в Java, и как передавать байт-код таких лямбд на сервер, без перезапуска сервера, т.е. как можно в теории отказаться от привычных схем deploy приложений и писать более удобный api (это моя фантазия).


Допустим у нас есть такой интерфейс IEnv:

public interface IEnv {List<OsProc> processes();}

Который возвращает список процессов

public class OsProc implements Serializable {public Optional<Integer> getPpid(){ return ... }public int getPid(){ return ... }public void setPid(int pid){ ... }public String getName(){ return ... }public Optional<String> getCmdline(){ return ... }}

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

var query = TcpQuery   .create(IEnv.class).host("myserver.com").port(9988) .build();  query.apply( env -> env.processes().stream().filter(     p -> p.getName().contains("java") ) .collect(Collectors.toList()) ).forEach(System.out::println);

Введение - как это работает ?

При разработке клиентского приложения на Java

  1. Мы создаем набор исходных файлов, допустим Client.java

  2. Компилятор генерирует байт-код - файл Client.class

  3. При вызове query.apply() - мы передаем ссылку на лямбду env -> env.proc...toList())

  4. Реализация query.apply():

    1. Принимает ссылку на лямбду

    2. Для лямбды обнаруживает имя класса (например Client) и метода (например lambda1) реализующего лямбду

    3. Отыскивает среди ресурсов программы соответствующий байт-код класса (Client.class) и его метода

    4. Загружает байт-код реализации лямбды и отправляет его на сервер

      1. Сервер принимает байт-код лямбды

      2. Генерирует в памяти класс в который вставляет принятый байт-код

      3. Загружает этот класс в память и через рефлексию получает доступ к лямбде

      4. Возвращает идентификатор этого метода обратно

    5. Принимает идентификатор метода и делает вызов его на сервере

      1. Сервер выполняет ранее скомпилированный класс/метод/лямбду

      2. Возвращает результат выполнения

    6. Принимает результат серверного вызова и возвращает его как результат локального вызова

  5. Возврат результата вызова query.apply()

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

Как воспользоваться?

Что бы с этим по играть, воспользуйтесь моим pet project-ом:

v 1.0 https://github.com/gochaorg/trambda/releases/tag/1.0

maven

<dependency>  <groupId>xyz.cofe</groupId>  <artifactId>trambda</artifactId>  <version>1.0</version>  <type>pom</type></dependency><dependency>  <groupId>xyz.cofe</groupId>  <artifactId>trambda-core</artifactId>  <version>1.0</version></dependency><dependency>  <groupId>xyz.cofe</groupId>  <artifactId>trambda-tcp</artifactId>  <version>1.0</version></dependency>

Чтобы воспользоваться, у вас должна быть какая нибудь библиотека к которой вы хотите обращаться по сети (TCP)

В моем примере это будет очень простая библиотека, в ней всего три файла:

  • OsProc.java - описывает процесс ОС (описание было выше)

  • IEnv.java - Интерфейс получения списка процессов для ОС (описание было выше)

  • LinuxEnv.java - Получение списка процессов для ОС Linux - реализация IEnv

Библиотека из примера работает так:

package xyz.cofe.trambda.demo.api;import org.junit.jupiter.api.Test;public class LinuxEnvTest {   @Test   public void test(){       var env = new LinuxEnv();       env.processes().stream()           .filter(p->p.getName().equalsIgnoreCase("java"))           .forEach(System.out::println);   }}

LinuxEnv - это простой класс, он устроен так:

package xyz.cofe.trambda.demo.api;import java.util.ArrayList;import java.util.List;import xyz.cofe.io.fs.File;public class LinuxEnv implements IEnv {   @Override   public List<OsProc> processes(){       ArrayList<OsProc> procs = new ArrayList<>();       File procDir = new File("/proc");       procDir.dirList().stream()           .filter( d -> d.getName().matches("\\d+") && d.isDir() )           .map(OsProc::linuxProc)           .forEach(procs::add);       return procs;   }}

Код его тривиален, он сканирует каталог /proc и находит описание процесса ОС, (для Linux все процессы сервера отображаются в виде файлов/подкаталогов /proc)

Скомпилируем библиотеку (или возьмем свою)

Клонируем репозиторий (git commit 67ec260)

> git clone https://github.com/gochaorg/trambda.gitКлонирование в trambda...remote: Enumerating objects: 978, done.remote: Counting objects: 100% (978/978), done.remote: Compressing objects: 100% (464/464), done.remote: Total 978 (delta 308), reused 862 (delta 195), pack-reused 0Получение объектов: 100% (978/978), 715.70 KiB | 559.00 KiB/s, готово.Определение изменений: 100% (308/308), готово.

Собираем библиотеку для демонстрации

user@user-Modern-14-A10RB:22:10:35:~/Загрузки/sample-tr:> cd trambda/trambda-demo/tr-demo-api/user@user-Modern-14-A10RB:22:10:49:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api:> mvn clean package install...[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:  13.933 s[INFO] Finished at: 2021-04-18T22:11:11+05:00[INFO] ------------------------------------------------------------------------

В каталоге target должен оказаться файл jar с нашей библиотекой

user@user-Modern-14-A10RB:22:13:13:~/Загрузки/sample-tr/trambda/trambda-demo/tr-demo-api:> ll target/итого 48drwxrwxr-x 10 user user 4096 апр 18 22:11 ./drwxrwxr-x  4 user user 4096 апр 18 22:11 ../drwxrwxr-x  3 user user 4096 апр 18 22:11 classes/drwxrwxr-x  3 user user 4096 апр 18 22:11 generated-sources/drwxrwxr-x  3 user user 4096 апр 18 22:11 generated-test-sources/drwxrwxr-x  2 user user 4096 апр 18 22:11 maven-archiver/drwxrwxr-x  3 user user 4096 апр 18 22:11 maven-status/drwxrwxr-x  4 user user 4096 апр 18 22:11 site/drwxrwxr-x  2 user user 4096 апр 18 22:11 surefire-reports/drwxrwxr-x  3 user user 4096 апр 18 22:11 test-classes/-rw-rw-r--  1 user user 6337 апр 18 22:11 tr-demo-api-1.0-SNAPSHOT.jar

Запуск сервера

Теперь когда библиотека собрана копируем сервер с github и распаковываем архив

user@user-Modern-14-A10RB:22:37:25:~/Загрузки/sample-tr:> wget https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zip--2021-04-18 22:37:31--  https://github.com/gochaorg/trambda/releases/download/1.0/trambda-tcp-serv-cli.zipРаспознаётся github.com (github.com)... 140.82.121.4Подключение к github.com (github.com)|140.82.121.4|:443... соединение установлено.HTTP-запрос отправлен. Ожидание ответа... 302 FoundАдрес: https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-stream [переход]--2021-04-18 22:37:31--  https://github-releases.githubusercontent.com/350075998/47380d00-9b40-11eb-90a4-4e353f42e67c?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20210418%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210418T173731Z&X-Amz-Expires=300&X-Amz-Signature=97ade1f58bfbe1eaa320805179987e8c4df730b9f5eddf24c05662fb676caafe&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=350075998&response-content-disposition=attachment%3B%20filename%3Dtrambda-tcp-serv-cli.zip&response-content-type=application%2Foctet-streamРаспознаётся github-releases.githubusercontent.com (github-releases.githubusercontent.com)... 185.199.111.154, 185.199.108.154, 185.199.109.154, ...Подключение к github-releases.githubusercontent.com (github-releases.githubusercontent.com)|185.199.111.154|:443... соединение установлено.HTTP-запрос отправлен. Ожидание ответа... 200 OKДлина: 12107487 (12M) [application/octet-stream]Сохранение в: trambda-tcp-serv-cli.ziptrambda-tcp-serv-cli.zip                                    100%[========================================================================================================================================>]  11,55M  3,75MB/s    за 3,1s    2021-04-18 22:37:35 (3,75 MB/s) - trambda-tcp-serv-cli.zip сохранён [12107487/12107487]user@user-Modern-14-A10RB:22:37:35:~/Загрузки/sample-tr:> llитого 11836drwxrwxr-x  3 user user     4096 апр 18 22:37 ./drwxr-xr-x 11 user user     4096 апр 18 22:00 ../drwxrwxr-x 10 user user     4096 апр 18 22:10 trambda/-rw-rw-r--  1 user user 12107487 апр 12 03:36 trambda-tcp-serv-cli.zipuser@user-Modern-14-A10RB:22:37:42:~/Загрузки/sample-tr:> unzip trambda-tcp-serv-cli.zip Archive:  trambda-tcp-serv-cli.zip   creating: trambda-tcp-serv-cli/   creating: trambda-tcp-serv-cli/jars/  inflating: trambda-tcp-serv-cli/jars/asm-9.1.jar    inflating: trambda-tcp-serv-cli/jars/jline-2.14.6.jar    inflating: trambda-tcp-serv-cli/jars/iofun-1.0.jar    inflating: trambda-tcp-serv-cli/jars/groovy-swing-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/groovy-console-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/groovy-xml-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/trambda-tcp-serv-cli-1.0.jar    inflating: trambda-tcp-serv-cli/jars/ecolls-1.10.jar    inflating: trambda-tcp-serv-cli/jars/trambda-core-1.0.jar    inflating: trambda-tcp-serv-cli/jars/slf4j-api-1.7.25.jar    inflating: trambda-tcp-serv-cli/jars/asm-tree-9.1.jar    inflating: trambda-tcp-serv-cli/jars/asm-util-9.1.jar    inflating: trambda-tcp-serv-cli/jars/fs-1.2.jar    inflating: trambda-tcp-serv-cli/jars/logback-classic-1.2.3.jar    inflating: trambda-tcp-serv-cli/jars/trambda-tcp-1.0.jar    inflating: trambda-tcp-serv-cli/jars/groovy-groovysh-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/groovy-templates-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/asm-analysis-9.1.jar    inflating: trambda-tcp-serv-cli/jars/text-1.0.jar    inflating: trambda-tcp-serv-cli/jars/logback-core-1.2.3.jar    inflating: trambda-tcp-serv-cli/jars/groovy-3.0.7.jar    inflating: trambda-tcp-serv-cli/jars/cbuffer-1.3.jar     creating: trambda-tcp-serv-cli/bin/  inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv.bat    inflating: trambda-tcp-serv-cli/bin/trambda-tcp-serv  user@user-Modern-14-A10RB:22:37:50:~/Загрузки/sample-tr:> rm trambda-tcp-serv-cli.zip 

После копируем нашу библиотеку в каталог trambda-tcp-serv-cli/jars

ser@user-Modern-14-A10RB:22:40:47:~/Загрузки/sample-tr:> cp -v trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar trambda-tcp-serv-cli/jars/'trambda/trambda-demo/tr-demo-api/target/tr-demo-api-1.0-SNAPSHOT.jar' -> 'trambda-tcp-serv-cli/jars/tr-demo-api-1.0-SNAPSHOT.jar'

Нам понадобиться подготовить скрипт запуска, скрипт на языке groovy (не беспокойтесь, это не обязательно именно так запускать, все можно сделать на голой Java)

Возьмем скрипт из примера:

> cat trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groov
// Сервис xyz.cofe.trambda.demo.api.LinuxEnv // будет запущен на порту 9988, и будет доступен с любого IPapp.service( "0.0.0.0:9988", new xyz.cofe.trambda.demo.api.LinuxEnv() ) {    daemon false    // Указываем настройки безопасности    security {        // Какие API/Методы будут доступны извне        allow {            // method("System") {            //     methodOwner ==~ /java.lang.System/ && methodName in ['gc']            // }            // field( "System.out" ) {            //     fieldOwner ==~ /java.lang.System/ && fieldName in ['out','in','err'] && readAccess            // }            invoke( 'Java compiler' ){                methodOwner ==~ /java\.lang\.invoke\.(LambdaMetafactory|StringConcatFactory)/            }            invoke( 'Java collections' ){                methodOwner ==~ /java\.util\.(stream\.(Stream|Collectors)|(List))/            }            invoke( 'Java lang' ){                methodOwner ==~ /java\.lang\.String/            }            invoke( 'Api '){                methodOwner ==~ /xyz\.cofe\.trambda\.demo\.api\.(IEnv|OsProc)/            }        }        // Для всех остальных случаев - запрещаем вызов        deny {            any("ban all")        }    }}

Запускаем сервер

user@user-Modern-14-A10RB:22:56:08:~/Загрузки/sample-tr:> bash ./trambda-tcp-serv-cli/bin/trambda-tcp-serv -s trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy# [main] INFO  x.c.t.tcp.serv.cli.TcpServerCLI - starting xyz.cofe.trambda.tcp.serv.cli.TcpServerCLI # [main] INFO  x.c.t.tcp.serv.cli.TcpServerCLI - executeScript( "trambda/trambda-tcp-serv-cli/src/test/samples/sample-1.groovy", UTF-8 ) # [main] INFO  x.c.t.tcp.serv.cli.TcpServerCLI - registry class xyz.cofe.trambda.demo.api.LinuxEnv on 0.0.0.0:9988 # [main] INFO  x.c.t.tcp.serv.cli.TcpServerCLI - starting service xyz.cofe.trambda.demo.api.LinuxEnv@55e7a35c on /0.0.0.0:9988 # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - create server socket # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - bind server socket /0.0.0.0:9988 # [main] DEBUG x.c.t.tcp.serv.cli.TcpServerCLI - server started 

Все, сервер запущен, теперь можем им пользоваться

Запуск клиента

Клиента можно взять из примера (ClientTest.java)

package xyz.cofe.trambda.demo.client;import java.util.stream.Collectors;import org.junit.jupiter.api.Test;import xyz.cofe.trambda.demo.api.IEnv;import xyz.cofe.trambda.tcp.TcpQuery;public class ClientTest {   @Test   public void test01(){       var query = TcpQuery           .create(IEnv.class).host("localhost").port(9988)           .build();       query.apply(           env -> env.processes().stream().filter(p ->               p.getName().contains("java"))           .collect(Collectors.toList())       ).forEach(System.out::println);   }}

Запустим и посмотрим, что он выведет, часть логов я опущу - т.к. для пояснения работы они не важны

Что происходит на сервере ?

часть логов я опущу - т.к. для пояснения работы они не важны

Настройка безопасности сервера

Совсем не обязательно использовать сервер через скрипты запуска, сервер можно встроить как часть своего приложения и настраивать так:

// Создание TCP сокетаServerSocket ssocket = new ServerSocket(port);// Настраиваем сокетssocket.setSoTimeout(1000*5);// Создаем серверserver = new TcpServer<IEnv>(    // Передаем сокет    ssocket,        // Передаем функцию получения сервиса для новой сессии    s -> new LinuxEnv(),        // Настраиваем безопасность    SecurityFilters.create(s -> {                // Разрешаем вызовы строго - определенных методов         s.allow( a -> {                        // Публикуемый API нашего сервиса             a.invoke("demo api", c->                c.getOwner().matches(                    "xyz\\.cofe\\.trambda\\.tcp\\.demo\\.([\\w\\d]+)"));                        // Работа с коллекциями            a.invoke("java collections api", c->c.getOwner().matches(                "java\\.util\\.(List)|java\\.util\\.stream\\.([\\w\\d]+)"));                        // Работа с Java строками            a.invoke("java lang api", c->                c.getOwner().matches("java\\.lang\\.(String)"));                        // Методы которые использует компилятор Java            a.invoke("java compiler", c->                c.getOwner().matches(                    "java\\.lang\\.invoke\\.(LambdaMetafactory|StringConcatFactory)"));        });                // Все остальное запрещаем        s.deny().any("by default");    }));// Указываем что Thread сервера будет запущен как фоновыйserver.setDaemon(true);// Запускаем серверserver.start();// Создание TCP сокетаServerSocket ssocket = new ServerSocket(port);// Настраиваем сокетssocket.setSoTimeout(1000*5);

Код приведенный выше вроде кажется понятным, но наверно следует дать несколько пояснений

  • Правила проверки безопасности указываются в виде списка фильтров

  • Список фильтров обрабатывается последовательно и соответственно первые правила имеют более высокий приоритет, нежели последние

  • В списке могут быть как разрешающие, так и запрещающие правила

  • Java компилятор генерирует

    • вызовы LambdaMetafactory когда вы используете лямбды в своем коде

    • вызовы StringConcatFactory когда вы применяете оператор + к строкам

  • Вы также можете контролировать обращения к полям класса на чтения/запись - см. SecurityFilters.java / PredicateBuilder#field

  • Для доступа к стандартным функция, там даже как Object.toString() то, же надо будет задать правила доступа

Более подробную информацию о работе можно найти на странице проекта, например на github или git pages

Чем это может быть полезно?

Резонный вопрос, чем может быть полезно и чем оно отличаеться от уже существующих, например RMI, gRPC ? Давайте рассмотрим отличия

Протоколов передачи данных и вызова процедур много, будут рассмотрены некоторые из известных автору. Эти протоколы можно разделить на несколько категорий/свойств:

  • Передача данных

    • Строго ограниченные форматы данных / типы данных

    • Гибкие форматы данных / комбинированные/структурированные типы данных

  • Передача программного кода

    • Ограничения на алгоритмы

      • Простые выражения

      • Циклы/ветвления

      • Процедуры/функции/объекты

      • Ограничения на интерпретацию алгоритмов

      • Слабая типизация

      • Строгая типизация

    • Безопасность

      • авторизация и т.д.

      • время исполнения

      • и т.д.

  • Поддержка существующий решений

    • Потребность в перезапуске серверов, что бы опробовать новые решения

    • Возможность на ходу опробовать новые решения

    • Профилирование выполнения

Фича

Java-RMI

SOAP

REST-JSON

SQL

GraphQL

Hadoop

Передача данных

+

+

+

+

+

+

Строго ограниченные форматы данных

+/-

+/-

-

-

+

-

Гибкие форматы данных

+/-

+/-

+

+

-

+

Передача программного кода

-

-

-

+

-

+

Простые выражения

-

-

-

+

-

+

Циклы/ветвления

-

-

-

+

-

+

Процедуры/функции

-

-

-

+

-

+

программные объекты

-

-

-

?

-

?

Слабая типизация

-

-

+

-

-

+

Строгая типизация

+

+

-

+

+

-

авторизация

+

+

+

+

+

+

время исполнения

?

?

?

+/-

?

?

Потребность в перезапуске серверов

+

+

?

-

+

-

Возможность находу опробывать новые решения

-

-

?

+

-

+

Профилирование выполнения

+

+

?

+

+

?

  • Большинство протоколов ориентированы только на передачу данных (Java-RMI, SOAP, REST-JSON, GraphQL)

    • Часть из них работают со строго типизированными данными (Java-RMI, SOAP, GraphQL)

    • Другие (REST-JSON, Hadoop) со слабо типизированными

  • Небольшое кол-во протоколов поддерживают еще передачу программного кода (SQL, Hadoop)

Наличие строгой типизации и передачу программного кода из рассмотренных есть только в SQL

В предлагаемом проекте есть следующие возможности, с оговорками

  • Передача данных (*)

    • Гибкие форматы данных / комбинированные / структурированные типы данных

  • Передача программного кода

    • Простые выражения

    • Циклы/ветвления

    • Процедуры/функции/

    • программные объекты (**)

  • Ограничения на интерпретацию алгоритмов

    • Строгая типизация

    • Безопасность (***)

  • Поддержка существующий решений

    • Возможность на ходу опробовать новые решения

    • Профилирование выполнения (****)

Оговорки

  • (*)

    • передаваемые типы должны быть Serializable

    • требуется апробация Proxy для интерфейсов

  • (**)

    • требуется апробация Proxy для объектов - очень неоднозначный вопрос

  • (***)

    • реализована проверка байт-кода, без учета текущего пользователя

    • не реализован механизм аутентификации, см план реализации

  • (****)

Область применения

Поскольку проект только начат, говорить о реальном применении рано, можно говорить о потенциальном применении

Возможны следующий области применения

  • фильтрация данных в программах написанных на Java по аналогии SQL WHERE (уже есть)

  • выполнение серверных процедур по аналогии RPC/RMI/SOAP/ (уже есть)

  • подписка клиента на события сервера

  • масштабирование нагрузки (как частный случай реализации сетевого протокола)

При дальнейшем развитии возможно автоматическая прозрачная трансляция JAVA/Kotlin/Scala кода в целевые системы (SQL, MongoDB, REST, )

Данное возможно при условии развития функции декомпиляции байт-кода в код AST/Java/, по факту такая функция реализована в декомпиляторе JAD

Что собственно ведет к уменьшению издержек при разработке ПО.

Как же оно внутри работает ?

Идея была проста, весь код Java который компилируется, обычно сохраняется в виде байт-кода который является файлом с расширением .class, а для любого объекта java можно узнать класс, обычно это выглядит так: obj.getClass()

Через объект Class можно узнать его название, и соответственно через Class.getResource(имя класса) можно получить URL ссылку на данный файл.

Гладко было на бумаге, но забыли про овраги

Такова была идея, но есть нюансы

Процесс разработки, как найти байт код?

Допустим у нас есть такой код

package xyz.cofe.trambda.l1;import java.util.function.Function;import org.junit.jupiter.api.Test;public class SimpleLambdaTest {   @Test   public void javaLambda01(){       Function<Function<String,String>,String> test = (f) -> {           System.out.println("f="+f.getClass());           return null;       };       test.apply( x -> x.repeat(4) );   }}

При выполнении теста будет вот это:

f=class xyz.cofe.trambda.l1.SimpleLambdaTest$$Lambda$235/0x0000000800142040

По идее у нас в каталоге test-classes/ должен быть файл SimpleLambdaTest$$Lambda$235, но такого файла не видно

user@user-Modern-14-A10RB:00:41:32:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l1:> llитого 12drwxrwxr-x 2 user user 4096 апр 25 00:40 ./drwxrwxr-x 5 user user 4096 апр 25 00:40 ../-rw-rw-r-- 1 user user 2162 апр 25 00:40 SimpleLambdaTest.class

Тогда посмотрим байт код

> javap -p SimpleLambdaTest.class Compiled from "SimpleLambdaTest.java"public class xyz.cofe.trambda.l1.SimpleLambdaTest {  public xyz.cofe.trambda.l1.SimpleLambdaTest();  public void javaLambda01();  private static java.lang.String lambda$javaLambda01$1(java.lang.String);  private static java.lang.String lambda$javaLambda01$0(java.util.function.Function);}

По факту где-то lambda$javaLambda01$1 или lambda$javaLambda01$0 находиться код нашей лямбды, но можно долго гадать, но это не наш подход

По факту сопоставить class и лямбду в Java с разбегу не удастся, а гадать это сразу путь на костыли.

В Java есть интересный интерфейс Serializable, и этим интерфейсом можно пометить лямбду

Например так

package xyz.cofe.trambda.l2;import java.io.Serializable;import java.util.function.Function;public interface Fn<A,Z> extends Function<A,Z> , Serializable {}

или вот так

Runnable r = (Runnable & Serializable)() -> System.out.println("Serializable!");

Фактически это заставит компилятор Java сгенерировать дополнительный код, давайте возьмем код теста выше и немного его модифицируем

package xyz.cofe.trambda.l2;import java.lang.invoke.SerializedLambda;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.function.Function;import org.junit.jupiter.api.Test;public class SerialLambdaTest {   @Test   public void serLambda01(){       Fn<Fn<String,String>,String> test = (lambda) -> {           System.out.println("lambda="+lambda.getClass());           Method writeReplace = null;           try{               writeReplace = lambda.getClass().getDeclaredMethod("writeReplace");               writeReplace.setAccessible(true);               SerializedLambda sl = (SerializedLambda) writeReplace.invoke(lambda);               System.out.println(sl);           } catch( NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) {               e.printStackTrace();           }           return null;       };       test.apply( x -> x.repeat(4) );   }}

Теперь результат будет таким, его разбор будет ниже.

lambda=class xyz.cofe.trambda.l2.SerialLambdaTest$$Lambda$235/0x0000000800142040SerializedLambda[capturingClass=class xyz.cofe.trambda.l2.SerialLambdaTest, functionalInterfaceMethod=xyz/cofe/trambda/l2/Fn.apply:(Ljava/lang/Object;)Ljava/lang/Object;, implementation=invokeStatic xyz/cofe/trambda/l2/SerialLambdaTest.lambda$serLambda01$3fed5817$1:(Ljava/lang/String;)Ljava/lang/String;, instantiatedMethodType=(Ljava/lang/String;)Ljava/lang/String;, numCaptured=0]

А при просмотре байт кода мы увидим

user@user-Modern-14-A10RB:00:51:48:~/code/trambda/trambda-core/target/test-classes/xyz/cofe/trambda/l2:> javap -p SerialLambdaTest.class Compiled from "SerialLambdaTest.java"public class xyz.cofe.trambda.l2.SerialLambdaTest {  public xyz.cofe.trambda.l2.SerialLambdaTest();  public void serLambda01();  private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);  private static java.lang.String lambda$serLambda01$3fed5817$1(java.lang.String);  private static java.lang.String lambda$serLambda01$47b6c34$1(xyz.cofe.trambda.l2.Fn);}

Первое что мы можем заметить - у нас появился метод $deserializeLambda$ пока не важно что делает этот метод, есть факт что добавление интерфейса Serializable меняет поведение компилятора.

java.lang.invoke.SerializedLambda - это final класс, который содержит ряд интересных свойств, а именно

  • String getImplClass() - Имя класса, содержащего метод реализации.

  • String getImplMethodName() - название метода реализации.

В stdout можно заметить фрагмент

implementation=invokeStatic xyz/cofe/trambda/l2/SerialLambdaTest.lambda$serLambda01$3fed5817$1:(Ljava/lang/String;)Ljava/lang/String;

И в байт коде:

private static java.lang.String lambda$serLambda01$3fed5817$1(java.lang.String);

Т.е. можно однозначно установить байт-код для лямбды

Сериализация байт кода

Теперь, на данном этапе прочесть байт код лямбды - дело техники, а именно так:

SerializedLambda sl = (SerializedLambda)writeReplace.invoke(lambda);var implClassName = sl.getImplClass()var implClassUrl =labmda.getClass().getResource("/"+implClassName.replace(".","/")+".class");

implClassUrl - указывает на файл класса содержащего байт код

получаем байт код из URL как массив байтов

byte[] classByteCode = null;try{   classByteCode = IOFun.readBytes(implClassUrl);} catch( IOException e ) {   throw new IOError(e);}

После этого передаем этот набор байтов в библиотеку ASM для прочтения байт кода

var classReader = new ClassReader(classByteCode);

и читаем байт код нужного метода

ClassVisitor cv = new ClassVisitor(Opcodes.ASM9) {   @Override   public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {       if( methName.equals(name) && descriptor!=null && descriptor.equals(methSign) ){           mdef0.set(new MethodDef(access,name,descriptor,signature,exceptions));           return dump(byteCodes::add);       }       return null;   }};cr.accept(cv, 0);

MethodVisior - это класс который получает байт код конкретного метода. Методы этого класса можно переопределить, рассмотрим часть реализации MethodDump extends MethodVisitor:

package xyz.cofe.trambda;public class MethodDump extends MethodVisitor implements Opcodes {...@Overridepublic void visitParameter(String name, int access){   emit(new MParameter(name,access));}@Overridepublic void visitInsn(int opcode){   emit(new MInsn(opcode));}...}

Данный класс принимает байт код через вызовы методов visitXXXX(...) - их много методов (по этому показа только часть)

При каждом вызове метода генерируется объект который описывает вызов, например для visitInsn( op ) генерируется new MInsn(op), а потом этот объект передается выше emit(..) где уже этот объект передается в сеть

Восстановление классов из байт кода

После этого как код передан по сети в виде набора объектов, эти объекты обратно собирается в байт код (с дополнительными проверками безопасности)

Для этого в моей библиотеке есть класс xyz.cofe.trambda.MethodRestore его работа заключается примерно в следующем

public synchronized byte[] generate(){  // генерируем имя целевого класса  binClassName = className.replace('.', '/');// Создаем ClassWriter (часть ASM) // в котором будем вызывать методы visitXXXX( op )var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);cw.visit(Opcodes.V11,   Opcodes.ACC_PUBLIC|Opcodes.ACC_SUPER,   binClassName,null,   "java/lang/Object", null);// генерация методаvar mv = cw.visitMethod(  acc, // флаги как static public  name, // имя метода  desc, // параметры метода  sign, // сигнатура если есть Generic параметры  excepts); // исключения которые может генерировать метод// Потом в цикле для каждого переданного объекта // генерируем соответствующий вызов visitXXXX()// генерируем код в той же последовательности, что и был прочитанfor( var bc : byteCodes ){   if( bc instanceof MCode )build((MCode) bc);   else if( bc instanceof MEnd )build((MEnd) bc);   else if( bc instanceof MLabel )build((MLabel) bc);   else if( bc instanceof MLineNumber )build((MLineNumber) bc);   else if( bc instanceof MVarInsn )build((MVarInsn) bc);  ...}// Получаем байт кодreturn cw.toByteArray();}// Вызов visitCode() - начало методаprotected void build(MCode code){ mv.visitCode(); }// Вызов visitEnd() - конец методаprotected void build(MEnd end){ mv.visitEnd(); }protected void build(MTypeInsn tinst){   mv.visitTypeInsn(tinst.getOpcode(), tinst.getOperand());}

Теперь когда у нас есть на сервере байт код, остается его загрузить и вызвать соответствующий метод

Для этого создаем свой ClassLoader

var byteCode = new MethodRestore()   .className(clName)   .methodName("lambda1")   .methodDef(mdef)   .generate();ClassLoader cl = new ClassLoader(ClassLoader.getSystemClassLoader()) {   @Override   protected Class<?> findClass(String name) throws ClassNotFoundException{       if( name!=null && name.equals(clName) ){           return defineClass(name,byteCode,0,byteCode.length);       }       return super.findClass(name);   }};

Загружаем класс

System.out.println("try read class "+clName);Class c = null;try{   c = Class.forName(clName,true,cl);   System.out.println("class found "+c);} catch( ClassNotFoundException e ) {   e.printStackTrace();   return;}

Ищем нужный нам метод класса

Method m = null;System.out.println("methods");for( var delMeth : c.getDeclaredMethods() ){  System.out.println(""+delMeth);  if( delMeth.getName().equals(methName) ){    m = delMeth;  }}

и вызываем его с параметрами

try{   Object arg0 = "abc";   System.out.println("call with "+arg0);   Object res = m.invoke(null, arg0);   System.out.println("result "+res);} catch( IllegalAccessException | InvocationTargetException e ) {   e.printStackTrace();}

Ограничения

Конечно у данного решения есть ряд ограничений

  1. Не для любого языка подойдет

    1. например для Kotlin придется дописывать реализацию, т.к. Kotlin по другому компилирует лямбды, такая же ситуация с Scala

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

  2. Гарантировать типо безопасность - особенно когда разные версии библиотек на сервере и на клиенте - задача непростая, но еще не означает что невозможно

  3. Потенциально на клиенте может Java более новая, с другим байт-кодом нежели на сервере, сервер может не знать о каких либо конструкциях новой Java

  4. Библиотека не весь байт код передает, это связано с тем, что не весь байт код имеет объектное представление - наверно это решаемый вопрос, вопрос доработки.

  5. Отдельно - это вопрос передачи по сети

    1. Вопрос безопасности - технически это решаемо, но требует допиливание напильником

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

Подробнее..

Как решить нестандартные задачи в Backend и не проиграть. Расскажут спикеры конференции DUMP

29.04.2021 22:15:20 | Автор: admin

Обычно участники ждут от конференций полезных докладов с цифрами, фактами и только по делу. Мы учли это, взяли трендовые темы и пригласили опытных спикеров. Так получилась четкая и концентрированная программа секции Backend. Поговорим об актуальных сейчас технологиях и методах. Разберемся в серверной разработке без привязки к языкам программирования. А также поищем оптимальное решение задач, с которыми сталкивается большинство разработчиков бэка.

Ты только посмотри, какие спикеры нам в этом помогут!

Михаил Беляев из Прософт-Системы с докладом Проблемы embedded или как мы от sqlite ушли

При embedded разработке существует очень большая проблема весьма ограниченные ресурсы устройства, на котором выполняется твой код. В ходе разработки, когда запросы клиентов растут, а ресурсы embedded устройства на исходе, очень часто стандартные и проверенные решения не подходят. Разработчик сталкивается с выбором: или сказать, что наше устройство не может справиться с требуемыми задачами, или сделать нестандартный ход и выиграть. Вот именно об этом процессе перехода от использования SQLite к собственному хранилищу данных, которое позволяет читать и писать данные значительно быстрее, Михаил поведает другим разработчикам.

Андрей Цветцих из EPAM представит доклад Чистая Архитектура на практике. Вот что он рассказал нам о своем выступлении:

С момента выхода книги Дяди Боба Clean Architecture прошло уже достаточно времени. Кто-то ее прочитал, а кто-то только смотрел доклады на youtube. Но все эти доклады идейные. У их авторов обычно нет практического опыта создания больших проектов по данной архитектуре (как и запуска этих проектов в production). А все примеры слишком простые! На практике все равно остается много вопросов. Больше года назад мы начали 2 новых проекта, в которых применяли принципы, описанные в книге. Это корпоративные приложения на C# (API, backend). Enterprise, который еще не успел стать кровавым :) Но этого вполне достаточно чтобы получить первые результаты и поделиться опытом.

Роман Неволин из Контура выступит с докладом про Функциональные языки для бизнес-разработки

Функциональные языки зачастую воспринимаются как красивые и модные игрушки посмотреть и повертеть забавно, а вот в суровом энтерпрайзе им не место. Принято считать, что здесь лучше всего подходят проверенные годами, простые и надежные языки, такие как Java. Роман же с этим согласиться никак не может и постоянно пытается применить любимый функциональный язык F# - к очередной бизнес-задаче. На выступлении обсудим, как Роман это делает, зачем оно ему (и всем остальным) нужно, и какие именно грабли он успел собрать на пути.

Кстати, Романа можно будет услышать в нашем бесплатном стриме, о котором мы подробно рассказали здесь.

Евгений Пешков из JetBrains с докладом Клиентский HTTP в .NET: дорога по граблям от WebRequest до SocketsHttpHandler

На первый взгляд кажется, что отправить HTTP запрос это очень просто. Тем не менее, даже HTTP/1.1 достаточно нетривиален RFC на него содержит более 150 страниц, кроме того браузеры уже поддерживают HTTP/2 и HTTP/3. Это не оставляет никакого выбора: стандартный клиент в платформе должен быть реализован на высоком уровне.

На пути от .NET Framework 1.0 к .NET 5 клиентские API для работы с HTTP и его реализации претерпели множество изменений. В некоторых версиях они были удачными, в некоторых же провальными и явно временными.

В докладе Евгений расскажет о истории развития клиентского HTTP API в .NET, его особенностях, о миграции приложений с Framework на Core с их учётом. Также разберет некоторые хаки, полезные при работе с ним. Заглянем в NuGet и рассмотрим представленные в нём обёртки над HTTP API с точки зрения эффективности и кроссплатформенности.

Александр Поляков из Яндекса расскажет Как Яндекс.Афиша 2 раза переезжала на GraphQL

Этот доклад о том, как мы переписали API Я.Афиша с REST на GraphQL на node.js + Python. А затем, в рамках оптимизации, избавились от node.js + Python и переписали весь GraphQL на Java.

Разберемся со следующими вопросами:

почему мы выбрали технологию GraphQL

какие проблемы и задачи решали с ее помощью

расскажем и покажем, как эволюционировала наша архитектура

для каких команд и проектов подходит наше решение, а для каких нет и почему

А также дадим несколько практических советов по тому, как лучше всего начинать работать с GraphQL

Фагим Садыков из SpectrumData представит нам свой доклад Kotlin как основной язык разработки бэкенда и обработки данных

Kotlin язык противоречивый, с одной стороны, он уже занял респектабельную нишу "популярного языка", с другой стороны - многими воспринимается, как какой-то экзотический язык для Android или просто как любопытная игрушка на "попробовать".

В компании SpectrumData Kotlin используется как основной (и по факту единственный) продуктовый язык разработки на платформе JVM для самого широкого круга задач: работа с данными, реализация микро-сервисной архитектуры, реализация бэкенда. История успешного применения Kotlin в компании длится уже 4 года с версии языка 1.1. Для SpectrumData характерно полноценное применение в своей кодобазе всех возможностей и рекомендованных практик по данному языку.

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

Про Аспектно-ориентированное программирование на C# и .NET вчера, сегодня и завтра расскажет Денис Цветцих из Invent

Аспектно-ориентированное программирование (АОП) позволяет без дублирования кода добавлять инфраструктурный функционал вроде кеширования и логгирования на разные слои вашего приложения. И все это не меняя уже написанный код! Это очень мощная, удобная а также редко используемая техника. Отчасти это оправдано, 10 лет назад инструменты для реализации аспектов были не развиты, поэтому за АОП закрепилась слава подхода, используемого только энтузиастами. Но с тех пор мир изменился и сегодня АОП можно увидеть даже в веб-фреймворках, важно только уметь его распознать.

В своем докладе Денис поделится 10-летним опытом использования АОП на C# и .NET. Расскажет о подходах к реализации АОП, а также покажет, как менялись инструменты для разработки аспектов вместе с языком программирования и платформой. Естественно, он предложит наиболее оптимальный на сегодня вариант реализации аспектов. И вместе подумаем, какими хотелось бы видеть инструменты для разработки аспекты в будущем. Примеры будут на C# и .NET, но идеи доклада будут актуальны для любой платформы.

А под занавес программный комитет поставил Антона Шишкина из SKB LAB с докладом Рекомендации и фичи первой свежести . Здесь Антон расскажет про свой опыт построения рекомендательной системы от offline расчетов до online. И отдельно про то, как обеспечивается актуальность фичей для онлайн расчетов. Будут разобраны "допущения", благодаря которым фичи сохраняют свою "свежесть", при этом обеспечивается высокая доступность хранилища признаков.

Что будет происходить в остальных секциях, смотри в основной программе.

А здесь можно выбрать билеты в онлайн и офлайн формате.

Подробнее..

Перевод Rust 1.52.0 улучшения Clippy и стабилизация API

07.05.2021 14:19:42 | Автор: admin

Команда Rust рада сообщить о выпуске новой версии 1.52.0. Rust это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.52.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.52.0


Самое значительное изменение этого выпуска не касается самого языка или стандартной библиотеки. Это улучшения в Clippy.


Ранее запуск cargo clippy после cargo check не запускал Clippy: кэширование в Cargo не видело разницы между ними. В версии 1.52 это поведение было исправлено, а значит, теперь пользователи будут получать то поведение, которое ожидают, независимо от порядка запуска этих команд.


Стабилизированные API


Следующие методы были стабилизированы:



Следующие ранее стабилизированные API стали const:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.52.0


Множество людей собрались вместе, чтобы создать Rust 1.52.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели Belanchuk, TelegaOvoshey, blandger, nlinker и funkill.

Подробнее..

Лаконичный итератор для декларативного синтаксиса

31.05.2021 14:10:41 | Автор: admin


Итератор (от англ.iterator перечислитель) интерфейс, предоставляющий доступ к элементам коллекции (массива или контейнера) и навигацию по ним. В различных системах итераторы могут иметь разные общепринятые названия. В терминах систем управления базами данных итераторы называются курсорами.

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

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

Стандартный итератор


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

Например в С++ это делается с помощью конструкций вида:
for (std::list<int>::iterator it = C.begin(),end = C.end(); it != end; ++it)

Или так:
std::for_each( C.begin(), C.end(), ProcessItem );

В Python так же любой пользовательский класс тоже может поддерживать итерацию. Для этого нужно определить метод __iter__(), который и создает итератор и метод next(), который должен возвращать следующий элемент.

for value in sequence:     print(value)it = iter(sequence)while True:    try:        value = it.next()    except StopIteration:        break    print(value)

В Java использование итераторов выглядит примерно также.

Iterator iter = list.iterator();//Iterator<MyType> iter = list.iterator(); в J2SE 5.0while (iter.hasNext())    System.out.println(iter.next());

или даже так

for (MyType obj : list)    System.out.print(obj);


Общепринятая логика у итератора следующая:

  1. После создания итератора, указатель сразу содержит актуальные данные
  2. Операции получения данных и перемещения указателя могут быть раздельными
  3. Большинство языков программирования поддерживают несколько управляющих конструкций с помощью которых может быть реализован перебор элементов коллекции с использованием итератора
  4. После завершения обхода всех элементов, итератор встает на позицию за последним элементом, тем самым делая невалидной операцию чтения данных


Концепция итератора для нового языка


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

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

В большей степени из-за необходимости работать с объектами из базы данных, так же как с локальными объектами, итоговая концепция итератора немного (или наоборот много) отличается от общепринятой классической, вынесенной в заголовок статьи.

  1. При создании итератор не содержит актуальных данных, только необходимые параметры доступа к ним. Фактически, это можно интерпретировать как положение указателя на позиции перед первым элементом. А реальная инициализация итератора происходит только при сбросе, который выполняется неявно при выполнении первого чтения данных.
  2. Перемещение указателя на следующий элемент выполняется всегда после получения новой порции данных автоматически. Если необходимо оставлять курсор на текущей позиции (т.е. считывать одни и те же данные постоянно), то после каждого чтения необходимо возвращать позицию курсора назад, на количество реально считанных данных.
  3. После завершения обхода коллекции, указатель итератора остается на последней позиции данных, а не переходит за последний элемент как в случае с классическими итераторами. Это сделано для того, чтобы операция чтения данных у завершившегося итератора всегда была валидной. Конечно, кроме того случая, когда данные отсутствуют изначально.

Для чего выбрана такая логика работы с итератором?

Создание итератора


В случае реализации доступа к локальным данным внутри приложения, нет никаких причин разделять создание итератора и получение указателя на его первый элемент. Особенно в компилируемых языках (например С++), в которых итератор может быть реализован обычной ссылкой на элемент данных. В таких случаях, как правило, проблем при создании итератора не предвидится.

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

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

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

Автоматическое перемещение итератора


Как было указано выше, перемещение внутреннего указателя на следующий элемент данных выполняется всегда и автоматически, а если необходимо оставлять курсор на текущей позиции (т.е. считывать одни и те же данные постоянно), то после каждого чтения необходимо возвращать позицию курсора назад, на количество реально считанных данных. Фактически это повторяет логику работы итератора в Python (т.е. выполнение метода next).

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

Завершение работы итератора


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

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

Другими словами, если использовать такое поведение итератора, то при получении текущего значения становится неважно, к каким данным происходит обращение и использовать интерфейс итератора можно даже при обращении к обычным переменным (не коллекциям)!

Просто в этом случае всегда будет возвращаться её актуальное значение. Конечно, последующее перемещение курсора на очередной элемент данных не произойдет, но сами данные с точки зрения логики работы итератора будут корректны.

Синтаксис лаконичного итератора


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

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

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

И приняв это за отправную точку, операция чтения данных из итератора в декларативном стиле получается следующая:

? итератор возвращает весь набор элементов из коллекции в виде массива. Коллекция возвращается полностью, независимо от текущего положения курсора. Фактически, это означает, что перед каждым выполнением данного оператора, у итератора выполняется операция сброса в начальное состояние. Так же, с помощью конструкции вида ?(...) можно считывать произвольное количество возвращаемых элементов, например для пагинации.

! итератор возвращает один текущий объект из коллекции и переводит указатель на следующий элемент (сброс/инициализация итератора выполняется неявно и автоматически при обращении к первого элементу). В данном случае, операция чтения итератора возвращает именно сам элемент, а не массив с одним элементом. Если необходимо считать элемент данных как элемент массива, то необходимо использовать конструкцию ?(1).

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

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

Примеры использования


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

Таким образом, конструкция name? будет означать создание итератора и вывод всех элементов коллекции, причем слева от итератора указываются параметры его создания. В общем случае, это будет класс терминов для чтения или имя функции/переменной. Дополнительно, в качестве аргументов можно задать и другие параметры, например для фильтрации терминов по заданным критериям: human(sex=man)?, но это уже зависит от реализации конкретного итератора.

Вообще все термины можно вывести с помощью конструкции "@term?", т.к. любой термин является производным от базового класса term, поэтому строки human(sex=man)? и "@term(class=human, sex=man)?" будут эквивалентны.

Надеюсь, сейчас стало более понятно, что делают итераторы в примере из предыдущей статьи:
Необычная концепция синтаксиса языка программирования

human::brother(test=human!) &&= $0!=$test, $0.sex==male, @intersec($0.parent, $test.parent);human.brother?

Подробнее..

Перевод Rust 1.53.0 IntoIterator для массивов, quotquot в шаблонах, Unicode-идентификаторы, поддержка имени HEAD-ветки в Cargo

18.06.2021 18:20:53 | Автор: admin

Команда Rust рада сообщить о выпуске новой версии 1.53.0. Rust это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.53.0 вам достаточно выполнить следующую команду:


rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его с соответствующей страницы нашего веб-сайта, а также посмотреть подробные примечания к выпуску на GitHub.


Что было стабилизировано в 1.53.0


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


IntoIterator для массивов


Это первый выпуск Rust, в котором массивы реализуют типаж IntoIterator. Теперь вы можете итерироваться в массиве по значению:


for i in [1, 2, 3] {    ..}

Раньше это было возможно только по ссылке, с помощью &[1, 2, 3] или [1, 2, 3].iter().


Аналогично вы теперь можете передать массив в методы, ожидающие T: IntoIterator:


let set = BTreeSet::from_iter([1, 2, 3]);

for (a, b) in some_iterator.chain([1]).zip([1, 2, 3]) {    ..}

Это не было реализовано ранее из-за проблем с совместимостью. IntoIterator всегда реализуется для ссылок на массивы и в предыдущих выпусках array.into_iter() компилировался, преобразовываясь в (&array).into_iter().


Начиная с этого выпуска, массивы реализуют IntoIterator с небольшими оговорками для устранения несовместимости кода. Компилятор, как и прежде, преобразовывает array.into_iter() в (&array).into_iter(), как если бы реализации типажа ещё не было. Это касается только синтаксиса вызова метода .into_iter() и не затрагивает, например, for e in [1, 2, 3], iter.zip([1, 2, 3]) или IntoIterator::into_iter([1, 2, 3]), которые прекрасно компилируются.


Так как особый случай .into_iter() необходим только для предотвращения поломки существующего кода, он будет удалён в новой редакции Rust 2021, которая выйдет позже в этом году. Если хотите узнать больше следите за анонсами редакции.


"Или" в шаблонах


Синтаксис шаблонов был расширен поддержкой |, вложенного в шаблон где угодно. Это позволяет писать Some(1 | 2) вместо Some(1) | Some(2).


match result {     Ok(Some(1 | 2)) => { .. }     Err(MyError { kind: FileNotFound | PermissionDenied, .. }) => { .. }     _ => { .. }}

Unicode-идентификаторы


Теперь идентификаторы могут содержать не-ASCII символы. Можно использовать все действительные идентификаторы символов Unicode, определённые в UAX #31. Туда включены символы из многих разных языков и письменностей но не эмодзи.


Например:


const BLHAJ: &str = "";struct  {    : String,}let  = 1;

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


warning: identifier pair considered confusable between `` and `s`

Поддержка имени HEAD-ветки в Cargo


Cargo больше не предполагает, что HEAD-ветка в git-репозитории называется master. А следовательно, вам не надо указывать branch = "main" для зависимостей из git-репозиториев, в которых ветка по умолчанию main.


Инкрементальная компиляция до сих пор отключена по умолчанию


Как ранее говорилось в анонсе 1.52.1, инкрементальная компиляция была отключена для стабильных выпусков Rust. Функциональность остаётся доступной в каналах beta и nightly. Метод включения инкрементальной компиляции в 1.53.0 не изменился с 1.52.1.


Стабилизированные API


Следующие методы и реализации типажей были стабилизированы:



Другие изменения


Синтаксис, пакетный менеджер Cargo и анализатор Clippy также претерпели некоторые изменения.


Участники 1.53.0


Множество людей собрались вместе, чтобы создать Rust 1.53.0. Мы не смогли бы сделать это без всех вас. Спасибо!




От переводчиков


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


Данную статью совместными усилиями перевели TelegaOvoshey, blandger, Belanchuk и funkill.

Подробнее..

Видео курсов Computer Science клуба

05.01.2021 18:18:32 | Автор: admin

Computer Science клуб это открытые лекции по компьютерным наукам в Санкт-Петербургском отделении Математического института РАН. Филиалы CS клуба действуют в Новосибирске и Казани. В связи с эпидемией все лекции осеннего семестра проходили онлайн и были доступны всем желающим вне зависимости от их местонахождения. Видеозаписи этих курсов выложены на сайт клуба и в канал на ютубе.

В осеннем семестре читались следующие курсы:

Кроме курсов, состоящих из нескольких лекций, в клубе проходят доклады Computer Science семинара - это открытые лекции от действующих учёных, популярно рассказывающие о какой-то теме. В этом семестре у нас были следующие доклады:

Также на базе Computer Science клуба проходили семинаре по курсу "Алгоритмы: продвинутые главы" от Константина Макарычева. Константин записал видеолекции на русском для курса, который он читает в Northwestern University в США. На семинарах были очень подробные и интересные обсуждения. Семинары вели Александр Шень (LIRMM, Монпелье) и Илья Разенштейн (Microsoft Research). В качестве приглашённого лектора выступал Григорий Ярославцев (Indiana University).

Более того, на сайте вы можете найти материалы и видеозаписи большинства курсов, которые когда-либо читались в клубе. Список курсов.

Следующий семестр также будет проходить в режиме онлайн. Подписывайтесь на наши новости в соцсетях, чтобы ничего не пропустить. Ссылки вы найдёте в "подвале" на сайте клуба.

Подробнее..

Векторные языки SQL интерпретатор в 100 строк

10.06.2021 16:07:13 | Автор: admin

В предыдущей статье я описал векторные языки и их ключевые отличия от обычных языков. На коротких примерах я постарался показать, как эти особенности позволяют реализовывать алгоритмы необычным образом, кратко и с высоким уровнем абстракции. В силу своей векторной природы такие языки идеально присоблены для обработки больших данных, и в качестве доказательства в этой статье я полностью реализую на векторном языке простой SQL интерпретатор. А чтобы продемонстрировать, что векторный подход можно использовать на любом языке, я реализую тот же самый интерпретатор на Rust. Преимущества векторного подхода столь велики, что даже интерпретатор в интерпретаторе сможет обработать select с группировкой из таблицы в 100 миллионов строк за 20 с небольшим секунд.

Общий план.

Конечная цель - реализовать интепретатор, способный выполнять выражения типа:

select * from (select sym,size,count(*),avg(price) into r  from bt where price>10.0 and sym='fb'  group by sym,size)  as t1 join ref on t1.sym=ref.sym, t1.size = ref.size

Т.е. он должен поддерживать основные функции типа сложения и сравнения, позволять where и group by выражения, а также - inner join по нескольким колонкам.

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

Интерпретатор будет состоять из лексера, парсера и собственно интерпретатора. Для экономии места я буду приводить только ключевые места, а весь код можно найти здесь. Так же для краткости я реализую лишь часть функциональности, но так, чтобы все важное было на месте: join, where, group by, 3 типа данных, агрегирующие функции и т.п.

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

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

Наконец, сам интерпретатор. На Q он имеет весьма скромные размеры - строк 25. На Rust, конечно, гораздо больше, но написать его проще, чем может показаться. Нужно просто по шагам повторять все операции Q, адаптируя их к специфике Rust.

Это моя первая программа на Rust и сразу хочу сказать, что слухи о его сложности сильно преувеличены. Если писать в функциональном стиле (read only), то проблем нет никаких. После того, как Rust несколько раз забраковал мои идеи, я понял, чего он хочет и уже не сталкивался с необходимостью все переделывать из-за контроллера ссылок, а явные времена жизни понадобились только один раз и по делу. Зато взамен мы получаем программу, которую можно распараллелить по щелчку пальцев. Что мы и сделаем, чтобы добиться просто феноменальной производительности для столь примитивной программы.

В завершение о производительности. Векторный подход настолько крут, что без разницы - пишите вы на интерпретируемом языке или на Rust, все равно всю работу делают векторные операции. Поэтому интерпретатор на Q работает всего в полтора раза медленнее самого Q на аналогичных данных.

Лексер

Векторные языки идеальны для написания лексеров. Пусть у нас есть функция fsa, которая принимает на вход текущее состояние лексера и входной символ и возвращает новое состояние:

fsa[state;char] -> state

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

Т.е. есть следующие этапы:

  • Кодирование. Входные символы отображаются в группы (my.var -> aa.aaa, 12.01 -> 00.00, "str 1" -> "sss 1" и т.д.).

  • Трансформация. Закодированные символы пропускаются через fsa (aa.aaa -> aAAAAA, 00.00 -> 0IFFF, "sss 1" -> "SSSSSR).

  • Разбиваем начальную строку на части по начальным состояниям (a, 0, " и т.д.). Для удобства все не начальные состояния обозначены большими буквами.

Все три этапа - это векторные операции, поэтому на Q эта идея реализуется одной строкой (все состояния закодированы так, что начальные меньше limit):

(where fsa\[start;input]<limit)cut input

Это в сущности весь лексер. Правда еще необходимо определить fsa. В подавляющем большинстве случаев в качестве fsa можно взять матрицу - таблицу переходов конечного автомата. Простой автомат можно задать и руками, но можно реализовать небольшой DSL. Отображение в группы можно организовать через небольшой массив (ограничимся ASCII символами для простоты):

cgrp: ("\t \r\n";"0..9";"a..zA..Z"),"\\\"=<>.'";c2grp: 128#0; // массив [0;128]// Q позволяет присваивать значения по индексу любой формы.// В данном случае массиву массивов. В Rust необходимы два явных цикла:// cgrp.iter().enumerate().for_each(|(i,&s)| s.iter()//   .for_each(|&s| c2grp[s as usize] = i + 1));c2grp[`int$cgrp]: 1+til count cgrp;

Для краткости я не привожу все цифры и буквы. Нас интересуют пробельные символы, цифры, буквы, а также несколько специальных символов. Мы закодируем эти группы числами 1, 2 и т.д., все остальные символы поместим в группу 0. Чтобы закодировать входную строку, достаточно взять индекс в массиве c2grp:

c2grp `int$string

Автомат задается правилами (текущее состояние(я);группа(ы) символов) -> новое состояние. Для обозначения групп и начальных состояний токенов удобно использовать первые символы соответствующих групп (для группы 0..9 - 0, например). Для обозначения промежуточных состояний - большие буквы. Например, правило для имен можно записать так:

aA А a0.

т.е. если автомат находится в состояниях "a" (начало имени) или "A" (внутри имени), и на вход поступают символы из групп [a,0,.], то автомат остается в состоянии "A". В начальное состояние "a" автомат попадет автоматически, когда встретит букву (это правило действует по умолчанию). После этого, если дальше он встретит букву, цифру или точку, то перейдет во внутреннее состояние "A" и будет там оставаться до тех пор, пока не встретит какой-то другой символ. Я запишу все правила без лишних комментариев (Rust):

let rules: [&[u8];21] =  [b"aA A a0.",                         // имена   b"0I I 0",b"0I F .",b"F F 0",        // int/float   b"= E =",b"> E =",b"< E =",b"< E >", // <>, >= и т.п.   b"\" S *",b"S S *",b"\"S R \"",      // "str"   b"' U *",b"U U *",b"'U V '",         // 'str'   b"\tW W \t"];                        // пробельные символы

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

Матрица fsa из этих правил генерируется элементарно. Схематично это выглядит так:

fsa[*;y] = y (по умолчанию для всех состояний)"aA A a0." -> "aA","A","a0."; fsa[enc["aA"];enc["a0."]] = enc["A"]...

Необходимо закодировать символы с помощью вектора states:

states: distinct " ",(first each cgrp),raze fsa[;1];limit: 2+count cgrp;enc:states?; // в Q encode - это поиск индекса элемента в векторе

Вперед поместим все начальные состояния (пробел для учета группы 0), чтобы можно было легко определить limit.

Код генерации fsa я опускаю - он следует схеме выше.

Матрица переходов у нас есть, осталось определить саму функцию lex. В парсере нам понадобится знать тип токена, поэтому вычислим и его тоже. На Rust лексер выглядит так:

let s2n = move |v| ["ID","NUM","STR","STR","WS","OTHER"][find1(&stn,&v)];move |s| {    if s.len()==0 {return Vec::<Token>::new()};    let mut sti = 0usize;    let st: Vec<usize> = s.as_bytes().iter().map(|b| { // st:fsa\[0;c2grp x]        sti = fsa[sti][c2grp[std::cmp::min(*b as usize,127)]];        sti}).collect();    let mut ix: Vec<usize> = st.iter().enumerate() // ix:where st<sta        .filter_map(|(i,v)| if *v<sta {Some(i)} else {None}).collect();    ix.push(st.len());    (0..ix.len()-1).into_iter()        .filter_map(|i|            match s2n(st[ix[i]]) {                 "WS" => None,                  kind => Some(Token{ str:&s[ix[i]..ix[i+1]], kind})             }).collect()

На Q получится значительно более кратко:

s2n:(states?"a0\"'\t")!("ID";"NUM";"STR";"STR";"WS");lex:{  i:where (st:fsa\[0;c2grp x])<limit;  {x[;where not "WS"~/: x 0]} (s2n st i;i cut x)};

Если мы запустим лексер, то получим:

lex "10 + a0" -> (("NUM";"";"ID");("10";"+";"a0"))

Интерпретатор

Интерпретатор можно разделить на две части - выполнение выражений и выполнение select. Первая часть тривиальна на Q, но требует большого количества кода на Rust. Я приведу основные структуры данных, чтобы было понятно, как в целом работает интерпретатор. В основе лежит enum Val:

type RVal=Arc<Val>;enum Val {       I(i64),    D(f64),    II(Vec<i64>),    DD(Vec<f64>),    S(Arc<String>),    SS(Vec<Arc<String>>),    TBL(Dict<RVal>),    ERR(String),}

Есть три типа данных - строки, целые и нецелые, две формы их представления - атомарная и вектор. Также есть таблицы и ошибки. Dict - это пара Vec<String> и Vec<T> одинаковой длины. В случае таблицы T = Vec<RVal>, где каждый Val - это II, DD или SS. Rust позволяет в легкую распаралелливать программу, но нужно, чтобы типы данных позволяли передавать свои значения между потоками. Для этого я обернул все разделяемые значения в асинхронный счетчик ссылок Arc. Считается, что атомарные операции более медленные, однако в программе, которая работает с большими данными, это не имеет большого значения.

Интерпретатор работает с выражениями:

enum Expr {    Empty,    F1(fn (RVal) -> RRVal, Box<Expr>), // f(x)    F2(fn (RVal,RVal) -> RRVal, Box<Expr>, Box<Expr>), // f(x,y)    ELst(Vec<Expr>),    ID(String),  // variable/column    Val(Val),    // simple value - 10, "str"    Set(String,Box<Expr>), // 'set var expr' - assignment    Sel(Sel), // select    Tbl(Vec<String>,Vec<Expr>), // [c1 expr1, c2 expr2] - create table }

ELst и Empty используются только парсером. Expr (ссылки на себя) необходимо хранить в куче (Box). Выполняются выражения функцией eval в некотором контексте, где заданы переменные (Set), а также могут быть определены колонки таблицы:

struct ECtx {    ctx: HashMap<String,Arc<Val>>,   // variables}struct SCtx {    tbl: Arc<Table>,                // within select    idx: Option<Vec<usize>>,        // idx into tbl    grp: Arc<Vec<String>>,          // group by cols}

eval сравнительно проста (self = ECtx):

type RRVal=Result<Arc<Val>,String>;fn top_eval(&mut self, e: Expr) -> RRVal {    match e {        Expr::Set(id,e) => {            let v = self.eval(*e, None)?;            self.ctx.insert(id,v.clone()); Ok(v)},        Expr::Sel(s) => self.do_sel(s),        _ => self.eval(e, None)    }}fn eval(&self, e: Expr, sctx:Option<&SCtx>) -> RRVal {    match e {        Expr::ID(id) => self.resolve_name(sctx,&id),        Expr::Val(v) => Ok(v.into()),        Expr::F1(f,e) => Ok(f(self.eval(*e,sctx)?)?),        Expr::F2(f,e1,e2) => Ok(f(self.eval(*e1,sctx)?,self.eval(*e2,sctx)?)?),        Expr::Tbl(n,e) => { self.eval_table(None,n,e) }        e => Err(format!("unexpected expr {:?}",e))    }}

Set и Sel нужен модифицируемый контекст, а его нельзя будет передать просто так в другой поток. Поэтому eval разбит на две части. Задача resolve_name - найти переменную или колонку и при необходимости применить where индекс. eval_table - собрать таблицу из частей и проверить, что с ней все в порядке (колонки одной длины и т.п.). Функции F1 (max, count ...) и F2 (+, >=, ...) сводятся к огромным match блокам, где для каждого типа прописывается нужная операция. Макросы позволяют уменьшить количество кода. Например, для арифметических операций часть match выглядит так:

(Val::D(i1),Val::I(i2)) => Ok(Val::D($op(*i1,*i2 as f64)).into()),(Val::D(i1),Val::D(i2)) => Ok(Val::D($op(*i1,*i2)).into()),(Val::I(i1),Val::II(i2)) => Ok(Val::II(i2.par_iter()    .map(|v| $op(*i1,*v)).collect()).into()),

Это может показаться неоптимальным, но когда вы обрабатываете таблицу на 100 миллионов строк, это не имеет ни малейшего значения. Видите слово par_iter выше? Это все, что нужно сделать в Rust, чтобы операция стала параллельной.

Выполнение select гораздо интереснее и сложнее. Разберем его на Q, потому что код на Rust многословно повторяет код на Q, который и сам по себе непростой.

Select состоит из подвыражений (join, where, group, select, distinct, into), каждое из которых выполняется отдельно. Самое сложное из них - join. В его основе лежит функция rename, задача которой присвоить колонкам уникальные имена, чтобы не возникло конфликта при join:

// если x это name -> найти, select -> выполнитьsget:{[x] $[10=type x;get x;sel1 x]};// в грамматике таблица определяется как '(ID|sel) ("as" ID)?'// так что x это список из 2 элементов: (ID из as или имя таблицы;ID/select)// y - уникальный префиксrename:{[x;y]  t:sget x 1; // получить таблицу: names!vals  k:(k!v),(n,/:k)!v:(y,n:x[0],"."),/:k:key t; // k - оригинальные имена,        // v - уникальные, n - с префиксом (table.name)  (k;v!value t)};

Все эти манипуляции сводятся к построению двух словарей - отображения из настоящих имен колонок и расширенных (table.name) в уникальные и из уникальных имен в сами колонки таблицы. Уникальные имена позволяют иметь в одной join таблице колонки с одинаковыми именами из разных таблиц и обращаться к ним в выражениях через нотацию с точкой.

В основе join следующая функция:

// x - текущая таблица в формате rename// y - следующая таблица в этом формате// z - join выражение, список (колонка в x;и в y)// условие join: x[z[i;0]]==y[z[i;1]] для всех ijoin_core:{[x;y;z]  // m - отображение имен в уникальные для новой таблицы x+y  // имена из x имеют приоритет  // c - переименовываем join колонки в уникальные имена  c:(m:y[0],x 0)z;  // после join z[;0] и z[;1] колонки будут одинаковыми  // поэтому колонки из y перенаправим на x  m[z[;1]]:c[;0];  // x[1]c[;0] - просто join колонки из таблицы x (подтаблица)  // y[1]c[;1] - симметрично из y  // sij[xval;yval] -> (idx1;idx2) найти индексы join в обеих таблицах  // sidx[(i1;i2);x;y без join колонок] -  //  собрать новую таблицу из x и y и индексов  (m;sidx[sij[x[1]c[;0];y[1]c[;1]];x 1;c[;1]_ y 1])}// sidx просто применяет индексы ко всем колонкам и объединяет y и z// y z - это словари, но поскольку традиционно векторные функции имеют// максимально широкую область определения, не нужно обращаться явно к value sidx:{(y@\:x 0),z@\:x 1};

Вся работа выполняется в функции sij, все остальное - это манипуляции именами, колонками и индексами. Если бы мы захотели добавить другой тип индекса, достаточно было бы написать еще одну sij. Конечно, функция выглядит непросто, но учитывая, что она покрывает 80% select, ей можно это простить.

Функция sij сводится к поиску строк таблицы x в таблице y. В Rust для этих целей можно использовать HashMap с быстрой hash функцией FNV - поместить в Map одну таблицу и потом искать в ней строки второй. В Q, судя по времени выполнения, скорее всего используется что-то подобное. В целом в Q у нас есть два варианта - использовать векторные примитивы или воспользоваться встроенными возможностями связанными с таблицами. В первом варианте все по-честному:

// x и y - списки колонокsij:{j:where count[y 0]>i:$[1=count x;y[0]?x 0;flip[y]?flip x]; (j;i j)};// или на псевдокоде// i=find_idx[tblY;tblX]; j=i where not null i; return (j,i[j])

используем функцию поиска значения в векторе (?) и транспозиции матрицы (flip). Этот вариант не такой медленный как может показаться - всего в 2.5 раза медленнее, чем оптимизированный поиск сразу по таблице (который выглядит ровно также - x?y, только x и y - таблицы, а не списки векторов). Это показывает в очередной раз силу векторных примитивов.

Наконец сам join - это просто цикл свертки по всем таблицам (fold):

// "/" это fold, rename' это map(rename)sjoin:{[v] join_core/[rename[v 0;"@"];rename'[v 1;string til count v 1];v 2]};

Остальные части select гораздо проще. where:

swhere:{[t;w] i:til count value[t 1]0;  // все строки по умолчанию  $[count w;where 0<>seval[t;i;();w];i]}; // выбрать те, которые не 0// seval такой же как eval в Rust, т.е. его сигнатура:// seval[table,index;group by cols;expr], ECtx - это сам Q

Основная функция select:

sel2:{[p] // p ~ словарь с элементами select (`j, `s, `g  и т.п.)  i:swhere[tbl:sjoin p`j;p`w]; // сходу делаем join и where  if[0=count p`s; // в случае select * надо найти подходящие имена колонкам    rmap:v[vi]!key[tbl 0] vi:v?distinct v:value tbl 0;    p[`s]:{nfix[x]!x} rmap key tbl 1];  if[count p`g; // group by    // из group колонок нужен только первый элемент, нужно знать их имена    gn:nfix {$[10=type x;x;""]} each p`g;    // sgrp вернет список индексов (idx1;idx2;..) для каждой группы    // затем нужно выполнить seval[tbl;idxN;gn;exprM] для всех idx+expr    // т.е. двойной цикл, который в Q скрыт за двумя "each"    g:sgrp[tbl;i;p`g]];    :key[p`s]!flip {x[z] each y}[seval[tbl;;gn];value p`s] each g;  // если group нет, то все элементарно - просто seval для всех select выражений  (),/:seval[tbl;i;()] each p`s };

Функция sgrp в основе group by - это просто векторный примитив group, возвращающий словарь, где ключи - уникальные значения, а значения - их индексы во входном значении:

sgrp:{[t;i;g] i value group flip seval[t;i;()] each g};

Я опускаю distinct и into части, поскольку они малоинтересны. В целом - это весь select на Q. В краткой записи он занимает всего 25 строк. Можно ли ждать хоть какой-то производительности от столь скромной программы? Да, потому что она написана на векторном языке!

Производительность

Напомню, что этот игрушечный интерпретатор может выполнять выражения типа

select * from (select sym,size,count(*),avg(price) into r  from bt where price>10.0 and sym='fb'  group by sym,size)  as t1 join ref on t1.sym=ref.sym, t1.size = ref.size

и при этом справляться с таблицами в сотни миллионов строк. В частности таблица bt в выражении выше сгенерирована выражением:

// в интерпретаторе на Rust// s = ("apple";"msft";"ibm";"bp";"gazp";"google";"fb";"abc")// i/f - i64/f64 интервалы [0-100)set bt [sym rand('s',100000000), size rand('i', 100000000),    price rand('f', 100000000)]

Т.е. содержит 100 миллионов строк. Поначалу базовый select с group by (получается 800 групп по ~125000 элементов)

select sym,size,count(*),avg(price) into r from bt group by sym,size

работал 44 секунды в программе на Rust, что совсем неплохо и само по себе. Однако после некоторых оптимизаций, а главное распаралелливания ключевых операций, мне удалось добиться скорости порядка 6 секунд. На мой взгляд весьма хороший результат для подобной таблицы.

Самое главное, программа на Rust, несмотря на свой внушительный вид, - это почти 1 в 1 программа на Q. Поэтому больших интеллектуальных усилий и даже отладки она не потребовала. Также благодаря векторности изначального языка ее ускорение путем распараллеливания не потребовало почти никаких усилий - если все операции изначально над массивами, то все что нужно - это вставить там и тут par_iter вместо iter.

Интерпретатор на Q не столь быстр, но вышеуказанный select всего на 50% медленнее аналогичного запроса на самом Q. Это значит, что по сути Q работает на больших данных почти также быстро, как и программа на компилируемом языке.

Хочу также отметить то, насколько великолепным языком проявил себя Rust. За все время разработки и отладки я не получил ни одного segfault и даже panic увидел всего несколько раз, и почти все это были простые ошибки выхода за пределы массива. Также поражает, насколько легко и безопасно в нем можно распараллелить задачу.

Парсер

Я отложил парсер на конец, поскольку он довольно объемен и не имеет прямого отношения к теме статьи. Тем не менее, может вам будет интересно ознакомиться с тем, как легко можно написать весьма серьезный парсер в функциональном стиле на Rust.

Это рекурсивный нисходящий парсер без заглядывания вперед. Из-за этого он не может предсказать следующий шаг и вынужден обходить все варианты. Такой парсер, конечно, медленный и не годится для серьезных задач, но для SQL выражений больше и не надо. Какая разница, парсится выражение 1 микросекунду или 10, если сам запрос займет минимум сотни микросекунд.

Такие парсеры часто пишут руками, и выглядят они примерно так:

parse_expr1(..) {   if(success(parse_expr2(..)) {    if (success(parse_str("+") || success(parse_str("-")) {      if(success(parse_expr1(..)) {         return <expr operation expr>      }      return Fail    }    return <expr>  }  return Fail;}

Главная идея предлагаемого парсера в том, что нет смысла писать это все руками, можно написать генератор подобных парсеров из BNF-подобной формы. Для всех сущностей BNF пишем по функции, затем генерируем из описания грамматики в виде строк набор парсящих функций, и все готово. В Rust, как строго типизированном языке, есть нюансы. В первую очередь определим типы для парсящих и post process функций:

type ParseFn = Box<dyn Fn(&PCtx,&[Token],usize) -> Option<(Expr,usize)>>;type PPFn = fn(Vec<Expr>) -> Expr;

ParseFn будет захватывать правила грамматики, поэтому она должна быть замыканием (closure) и лежать в куче. PCtx содержит другие ParseFn для рекурсивных вызовов и PPFn для постобработки дерева. Если парсинг не удался, она возвращает None, иначе Some с выражением и новым индексом в массив токенов. PPFn обрабатывает узел дерева, поэтому принимает безликий список выражений и превращает его в нужное выражение.

Для понимания процесса приведу часть грамматики, касающуюся выражений:

("expr", "expr1 ('or' expr {lst})? {f2}"),("expr1","'not' expr1 {f1} | expr2 ('and' expr1 {lst})? {f2}"),("expr2","expr3 (('='|'<>'|'<='|'>='|'>'|'<') expr2 {lst})? {f2}"),("expr3","expr4 (('+'|'-') expr3 {lst})? {f2}"),("expr4","vexpr (('*'|'/') expr4 {lst})? {f2}"),("vexpr","'(' expr ')' {2} | '-' expr {f1} | call | ID | STR | NUM |  '[' (telst (',' telst)* {conc}) ']' {tblv}"),("call", "('sum'|'avg'|'count'|'min'|'max') '(' expr ')' {call} |  'count' '(' '*' ')' {cnt} | 'rand' '(' STR ',' NUM ')' {rand}"),

Тут видны ключевые части - имя правила, само правило и PP функции в фигурных скобках. Каждая продукция правила должна заканчиваться на PP функцию, поскольку правило возвращает Expr, а не Vec<Expr>. PP функция по умолчанию возвращает последний элемент вектора, поэтому кое-где PP функций нет. ID, NUM и т.п. должны обрабатываться ParseFn функцией с соответствующим именем.

Генерируется наш парсер с помощью следующей функции:

let parse = |str| {    let t = l(str);  // add ({}) depth map    let mut lvl = 0;    pp_or(&t.into_iter().map(|v| {      match v.str.as_bytes()[0] {        b'(' | b'{' => lvl+=1,        b')' | b'}' => lvl-=1,        _ => ()};      (v,std::cmp::max(0,lvl))}).collect::<Vec<(Token,i32)>>()    , 0)};

l - это лексер, я переиспользую для этого лексер SQL. Нужно добавить карту глубины вложенности скобок, чтобы было удобно выделять BNF подвыражения. Поскольку это парсер для внутренних потребностей, то нет необходимости проверять правильность скобок, беспокоиться о глубине рекурсии и т.п.

Далее наше правило поступает в парсер BNF. Нужно реализовать следующие компоненты:

  • or правило - A | B

  • and правило - A B C

  • const правило - "(", "select".

  • token правило - NUM, STR.

  • subrule правило - expr1, call.

  • optional правило - A?

  • 0+ правило - A*

  • 1+ правило - A+

  • PP правило - {ppfn}

Это работа, требующая тщательности, но проделать ее нужно один раз. Например, or правило:

fn pp_or(t: &[(Token,i32)], lvl:i32) -> ParseFn {    if t.len() == 0 {return Box::new(|_,_,i| Some((Expr::Empty,i)))};    let mut r: Vec<ParseFn> = t      .split(|(v,i)| *i == lvl && v.str.as_bytes()[0] == b'|' )      .map(|v| pp_and(v,lvl)).collect();    if 1 == r.len() {        r.pop().unwrap()    } else {        Box::new(move |ctx,toks,idx|          r.iter().find_map(|f| f(ctx,toks,idx)))    }}

Функция должна вернуть ParseFn замыкание. В общем случае, когда pp_and вернула несколько ParseFn, нужно организовать цикл и выполнять подфункции, пока одна из них не вернет Some.

pp_and работает аналогично, только все ее подфункции должны вернуть Some. Также в случае успеха она должна вызвать нужную PPFn для обработки результата.

fn pp_and(t: &[(Token,i32)], lvl:i32) -> ParseFn {    if t.len() == 0 {return Box::new(|_,_,i| Some((Expr::Empty,i)))};    let (rules,usr) = pp_val(Vec::<ParseFn>::new(),t,lvl);    Box::new(move |ctx,toks,i| {        let mut j = i;        let mut v = Vec::<Expr>::with_capacity(rules.len());        for r0 in &rules {        if let Some((v0,j0)) = r0(ctx,toks,j) {            j = j0; v.push(v0)            } else {return None} };        Some((ctx.ppfns[&usr](v),j))    })}

pp_val рекурсивно обрабатывает круглые скобки и все базовые выражения. Вот некоторые примеры из нее:

// Token - if ok call rules[Token]move |ctx,tok,i| if i<tok.len() && tok[i].kind == s   {ctx.rules[&s](ctx,tok,i)} else {None}// Subrulemove |ctx,tok,i| ctx.rules[&s](ctx,tok,i))}// rule?move |ctx,tok,i| Some(rule(ctx,tok,i).unwrap_or((Expr::Empty,i)))// rule+move |ctx,tok,i| {    let (e,i) = plst(&rule,ctx,tok,i);    if 0<e.len() {Some((Expr::ELst(e),i))} else {None}}// где plstlet mut j = i; let mut v:Vec<Expr> = Vec::new();while let Some((e,i)) = rule(ctx,tok,j) {j=i; v.push(e)};(v,j)

Это весь код, необходимый для создания парсера. Чтобы его сгенерировать, нужно вызвать parse и положить правило в map:

let mut map = HashMap::new();map.insert("expr".to_string(), parse("expr1 ('or' expr {lst})? {f2}"));...  

Также необходимо определить PP функции. В большинстве случаев они сравнительно просты:

let mut pfn: HashMap<String,PPFn> = HashMap::new();// default rulepfn.insert("default".to_string(),|mut e| e.pop().unwrap());// set name expr выражениеpfn.insert("set".to_string(),|mut e| Expr::Set(e.swap_remove(1).as_id(),  e.pop().unwrap().into()) );

В Rust нельзя просто взять элемент из массива, поэтому необходимы функции типа swap_remove, которые делают это безопасно.

Наконец, положим правила в специальную структуру и определим для нее функцию parse:

PCtx { rules:map, ppfns:pfn}...impl PCtx {    fn parse(&self, t:&[Token]) -> Expr {        if let Some((e,i)) = self.rules["top"](&self,t,0) {            if i == t.len() {e}              else {Val::ERR("parse error".into()).into()}        } else {Val::ERR("parse error".into()).into()}    }}

Все, парсер готов. Он не быстр, но зато очень прост. Плюс он полностью динамический - можно менять правила во время выполнения программы, например, отключить какие-то возможности.

Подробнее..

Зарплаты разработчиков в первом полугодии 2020 языки и квалификации

24.12.2020 12:19:48 | Автор: admin

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

Методология и выборка, как обычно, в конце, а короткий вывод тут: больше всех зарабатывают лиды команд, пишущих на Котлине, меньше всех джуны 1C, при переходе от джуна к мидлу (во всех языках) происходит самый ощутимый скачок в зарплатах (x2).

Кстати, вы всегда можете самостоятельно проверить, выше или ниже рынка ваша зарплата в нашем зарплатном калькуляторе.


Зарплата разработчиков по квалификациям

Разработчики-стажеры в первом полугодии получили 33 000 (здесь и далее везде медиаднная зарплата). Джуны 50 000 , мидлы 100 000 , синьоры 169 000 , а лиды 200 000 . Если не боитесь сложных графиков, смотрите сюда (это данные за первое полугодие 2020 с 90, 75, 25 и 10 перцентилями).

Лиды, синьоры и интерны выросли по зарплатам (в сравнении со вторым полугодием 2019), а вот джуны и мидлы нет. Самый большой рост был у лидов 11%.

Зарплаты разработчиков по квалификациям в 2018-2020Зарплаты разработчиков по квалификациям в 2018-2020

Дальше посмотрим на зарплаты джунов, мидлов, синьоров и лидов в разрезе языков программирования. Картина такая:самая низкая зарплата среди джунов у PHP-разработчиков 40 000 . Их даже обогнали 1С-джуны у которых получают в среднем 45 000 , а самые богатые джуны в RoR-разработке 69 000 .

  • Лидеры по уровню зарплаты среди миддлов Go-разработчики (140 000 ), а аутсайдеры 1С (80 000 ).

  • У синьоров зарплаты колеблются от 150 000 (в PHP) до 200 000 руб. (в Ruby on Rails).

  • У лидов от 160 000 (в 1C, PHP) до 235 000 (в Котлине).

Зарплаты разработчиков по языкам и квалификациям в первом полугодии 2020 (ч. 1)Зарплаты разработчиков по языкам и квалификациям в первом полугодии 2020 (ч. 1)Зарплаты разработчиков по языкам и квалификациям в первом полугодии 2020 (ч. 2)Зарплаты разработчиков по языкам и квалификациям в первом полугодии 2020 (ч. 2)

Есть интересное наблюдение. Самый ощутимый рост происходит при переходе от джуна к мидлу, в среднем зарплата увеличивается в 2 раза. Дальше уже меньше: в 1,6 раз в среднем растет зарплата при переходе к синьору и в 1,2 раза от синьора к лиду.

Но есть и исключения! Обратите внимание на 1С, Go и RoR на графике.

Зарплаты разработчиков по квалификациям: в основных языках

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

Golang: всё +/- стабильно, мидлы и синьоры чуть подросли

Медианная зарплата Go-разработчиков в первом полугодии 2020 170 000 .

Если разбить по квалификациям, то у джунов 54 000 (-2% по сравнению с предыдущим полугодием), у миддлов 140 000 (+8%), синьоров 135 000 (+6%), и у лидов 233 000 (+1%).

Динамика зарплат по квалификациям в GoДинамика зарплат по квалификациям в Go

Подробный график с разбивкой по перцентилям тут

Objective-C: мидлы и синьоры просели

Медианная зарплата Objective-C разработчиков в первом полугодии 2020 150 000 .

Что по квалификациям: у мидлов 128 000 в среднем (-5% по сравнению с прошлым полугодием), у синьоров 180 000 руб. (-7%), у лидов 205 000 (+8%). По джунам пока у нас не набралось достаточно данных.

Динамика зарплат по квалификациям в Objective-CДинамика зарплат по квалификациям в Objective-C

Подробный график с разбивкой по перцентилям тут

Swift: джуны упали, лиды взлетели

Медианная зарплата свифтеров в первом полугодии 2020 130 000 (в прошлом полугодии была 144 000 !).

Что по квалификациям: у джунов 50 000 (-38% по сравнению с прошлым полугодием), у миддлов 110 000 (-8%), у синьоров 180 000 (-5%), у лидов 232 000 (фантастические +27%).

Динамика зарплат по квалификациям в SwiftДинамика зарплат по квалификациям в Swift

Подробный график с разбивкой по перцентилям тут

Kotlin: снова падение джунов и взлёт лидов

Медианная зарплата Kotlin разработчиков в первом полугодии 2020 128 000 .

Что по квалификациям: джуны получали 52 000 в среднем (-10% по сравнению с прошлым полугодием), мидлы 120 000 (+2%), синьоры 180 000 (-1%), лиды 235 000 (+27%).

Динамика зарплат по квалификациям в KotlinДинамика зарплат по квалификациям в Kotlin

Подробный график с разбивкой по перцентилям тут

Ruby on Rails: все растут, особенно джуны и лиды

Медианная зарплата рельсистов в первом полугодии этого года составила 163 000 .

Что по квалификациям: у джунов 69 000 в среднем (+13% по сравнению со вторым полугодием 2019), у мидлов 120 000 (+9%), у синьоров 200 000 (+11%), у лидов 225 000 (+13%).

Динамика зарплат по квалификациям в RoRДинамика зарплат по квалификациям в RoR

Подробный график с разбивкой по перцентилям тут

Python: уверенно выросли мидлы, а остальные чуть-чуть

Медианная зарплата питонщиков в первом полугодии 2020 120 000 .

Что по квалификациям: у джунов зарплата 55 000 (на 2% больше, чем в прошлом полугодии), у мидлов 115 000 (+15%), у синьоров 165 000 (+3%) и у лидов 200 000 (+3%).

Динамика зарплат по квалификациям в PythonДинамика зарплат по квалификациям в Python

Подробный график с разбивкой по перцентилям тут

Java: мидлы подросли заметнее всех

Медианная зарплата джавистов в первом полугодии этого года 130 000 .

Что по квалификациям: у джунов в среднем 58 000 (+7% по сравнению со вторым полугодием 2019), у мидлов 120 000 (+14%), у синьоров 180 000 (+6%), у лидов 200 000 (без изменений).

ChartChart

Подробный график с разбивкой по перцентилям тут

C++: и снова падение джунов и взлёт лидов

Медианная зарплата плюсистов в первом полугодии 2020 года 114 000 .

Что по квалификациям: у джунов 57 000 в среднем (упали на 16% по сравнению с предыдущим полугодием), у мидлов 100 000 (+5%), у синьоров 165 000 (-3%), у лидов 200 000 (+14%).

Динамика зарплат по квалификациям в C++Динамика зарплат по квалификациям в C++

Подробный график с разбивкой по перцентилям тут

C#: выросли все, кроме синьоров (они стабильны)

Медианная зарплата C#-разработчиков в первом полугодии этого года 115 000 .

Что по квалификациям: у джунов 50 000 в среднем (+11% по сравнению с предыдущим полугодием), у мидлов 100 000 (+18%), у синьоров 160 000 (без изменений), у лидов 180 000 (+20%).

Динамика зарплат по квалификациям в C#Динамика зарплат по квалификациям в C#

Подробный график с разбивкой по перцентилям тут

JavaScript: слегка выросли лиды, слегка упали синьоры

Медианная зарплата JS-разработчиков в первом полугодии 2020 года 106 000 .

Что по квалификациям: у джунов в среднем 51 000 (на 2% больше, чем во втором полугодии 2019 года), у мидлов 100 000 (+1%), у синьоров 160 000 (-2%) и у лидов 190 000 (+6%).

Динамика зарплат по квалификациям в JSДинамика зарплат по квалификациям в JS

Подробный график с разбивкой по перцентилям тут

PHP: мидлы и синьоры немного подросли

Медианная зарплата пэхэпэшников в первом полугодии 2020 года 110 000 .

Что по квалификациям: у джунов 40 000 в среднем (по сравнению с предыдущим полугодием не изменилась), у мидлов 85 000 (+6%), у синьоров 150 000 (+7%), у лидов 160 000 (тоже без изменений).

Динамика зарплат по квалификациям в PHPДинамика зарплат по квалификациям в PHP

Подробный график с разбивкой по перцентилям тут

1С: уверенный рост всех квалификаций

Медианная зарплата разработчиков 1С в первом полугодии 2020 100 000 .

Что по квалификациям: у джунов в этом полугодии 45 000 в среднем, но с предыдущим сравнить не можем не набралось достаточно данных. У мидлов 80 000 (+14%), у синьоров 150 000 (+15%), у лидов 160 000 (+19%).

Динамика зарплат по квалификациям в 1СДинамика зарплат по квалификациям в 1С

Подробный график с разбивкой по перцентилям тут


Как мы это делаем

Наша выборка 8000 зарплат. Для зарплатных исследований мы берём данные не из резюме или вакансий, а из зарплатного калькулятора Хабр Карьеры. Это реальные зарплаты, которые айтишники указывают анонимно.

Все зарплаты указываются в рублях и это именно столько, сколько спецы получают на руки, за вычетом всех налогов. При расчетах мы отсекаем 1% самых высоких и 1% самых низких зарплат, чтобы они не смазали картину. Подробнее о том, как считаются зарплаты и как пользоваться калькулятором, мы рассказали на сайте.

Все зарплаты в исследовании медианные.

Что такое медианная зарплата?

Медианная зарплата это как средняя, но точнее. Медианная зарплата это уровень, выше и ниже которого получают доход одинаковое количество работников. То есть, к примеру, медианная зарплата в группе из десяти человек 100 000 , это значит, что пять человек получают меньше 100 000 , а пять больше.

А вот ещё наши зарплатные исследования, почитайте:

и другие.


Самое основное

Для тех, кто сразу промотал в конец статьи ( )

  • Медианная зарплата джуна в первом полугодии этого года 50 000 , миддла 100 000 , синьора 169 000 , а лида 200 000 .

  • Самая большая разница между джунами и лидами в Свифте 4,6 раза, а самая маленькая в Руби 3,3 раза.

  • С ростом квалификации скачок к следующему уровню зарплаты постепенно уменьшается во всех языках.

  • Самая высокая зарплата среди джунов в RoR-разработке (69 000 ), а самая низкая в PHP (40 000 ).

  • Мидлы больше всего зарабатывают в Go-разработке (140 000 ), а меньше всего в 1С (80 000 ).

  • По синьорам картина такая: максимум в Ruby on Rails (200 000 ), а минимум в PHP (150 000 ).

  • Лидам больше всего платят в Kotlin (235 000 ), а меньше всего в 1C и PHP (160 000 ).

  • Больше всего за первое полугодие 2020 выросли зарплаты в 1С (+16%), C# (+12%) и RoR (+11%).

  • А в Objective-C и Swift зарплаты в среднем немного снизились: 2% и 6% соответственно.


Делитесь данными о своей зарплате в нашем зарплатном калькуляторе и узнавайте, сколько зарабатывают ваши коллеги по рынку.

почему бы и нет!

Подробнее..

Перевод Рейтинг языков программирования 2021 доля Python падает, а TypeScript обошел С, в лидерах JavaScript, Java, C

22.02.2021 08:15:18 | Автор: admin

Украинский профильный ресурс DOU.UA провел очередной ежегодный опрос о языках программирования, в рамках которого было собрано 7211 анкет (92% респондентов находятся в Украине). Из интересного - впервые с 2014 года у Python отрицательная динамика, наблюдается тенденция перехода с JavaScript на TypeScript, наиболее довольны пользователи Elixir, и наконец на графиках появился Rust.

Коммерческое использование

Что изменилось? Прежде всего привлекает внимание рост TypeScipt: похоже, он со временем станет основным языком в экосистеме JavaScript. В этом году он впервые обошел С ++ по популярности.

Еще из интересного: впервые за несколько лет мы увидели снижение доли Python: возможно, использование Data Science дошло до точки насыщения.

Впервые с 2012 года выросла доля C#. Использование Java продолжает уменьшаться, хотя и не так активно, как раньше. В общем видно уменьшение доли JVM: доля Kotlin стабильна (хотя он и уступил место Ruby), использование Scala возобновилось после значительного снижения в 2019-м и сегодня, если сравнивать с 2018-м, даже немного возросло. Еще стоит отметить незначительный рост Dart.

Ниже приведена диаграмма с динамикой по актуальным языкам программирования по годам. Статистически значимыми являются изменения для C ++, TypeScript, Ruby, 1C, Scala, Pascal/Delphi, T-SQL. Напомним, что изменения статистически значимы, если с вероятностью 95% мы не можем получить результаты с изменениями и без, если будем равномерно выбирать две группы респондентов с одной популяции.

Области использования

В этом году мы впервые попросили респондентов указать сферу, в которой они работают. Результаты довольно интересны.

Видим, что примерно половина разработчиков - это бэкенд 52%, далее сегменты фронтенд 19% и мобильной разработки 8%, обработка массивов данных (туда относятся и Big Data и машинное обучение) - 6%. Desktop-программирование еще существует и занимает 4% от объема, системное программирование - 3% (сейчас небольшая доля) и Full-Stack разработка - примерно 1%.

Давайте рассмотрим, какие языки используют в зависимости от области применения

Мы видим, что основные языки бэкенда - это Java, C# и PHP. Доля JavaScript и TypeScript есть, но не такая большая и сравнима с долей Ruby и Go. В десятку главных языков бэкенда также входят Scala и С++.

Фактически весь фронтенд пишут на JavaScript и TypeScript. Использование других языков сугубо маргинальное. Доля TypeScript сравнительно меньше. Интересно будет посмотреть на это соотношение через год.

В мобильной разработке основные языки - Kotlin и Swift. А доля кроссплатформенных фреймворков меньше чем кажется. Кроме того, заметно, что Dart/Flutter сразу заняли большую нишу. А после React Native и Flutter есть еще место для C# Xamarin.

Среди обработки данных безоговорочным лидером является Python, далее - языки манипуляции данными T-SQL и PL-SQL. Особое место занимают Scala (здесь она более распространена, чем Java) и R.

Личные предпочтения

Как мы уже указывали, результаты выбора программистов нельзя использовать для прогнозирования, но здесь интересно, что наконец на графиках появился Rust. Опять видим некоторый спад интереса к Python и тенденцию перехода с JavaScript на TypeScript.

Посмотрим на индекс предпочтения - это относительное количество разработчиков на языке X, которые для следующего проекта в своей сфере тоже выберут язык X.

Интересно, что здесь данные отличаются от опроса в Stack Overflow и нашего предыдущего опроса: Rust расположен заметно ниже. Наиболее довольны пользователи Elixir (возможно, это миграция Erlang-комьюнити), также обращает на себя внимание место Clojure.

Если посчитать отдельно индекс удовлетворения по сферам применения, то результаты почти совпадают.

Изучение новых языков

Фаворитом, как и в предыдущие годы, стал Python. Его собираются изучать почти 18% из тех, кто будет осваивать новый язык. Но все-таки эта доля меньше, чем год назад. Ну а наибольший рост у TypeScript и Rust.

Как и в предыдущие годы, большинство респондентов (83%) хочет изучать новый язык самостоятельно, с помощью книг и документации, 4% будут обращаться к коллегам (впечатляющая интровертность), а 12% будут использовать традиционный подход - с помощью профессиональных преподавателей (курсов или индивидуальных занятий).

Финальная таблица

Дополнительные данные

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

Tут можно сделать вывод, что, поскольку доля разработчиков с опытом в 1 год уменьшилась => в IT пришло меньше новичков, чем в прошлом году.

Но все равно в большинстве это специалисты с менее 5 лет опыта работы в ИТ.

Рассмотрим корреляцию между возрастом и языком программирования.

Здесь мы видим, что более молодая аудитория у Kotlin и JavaScript, а более старшая - ожидаемо у языков PL-SQL и Pascal/Delphi. Похожие данные показывает опыт разработчика в зависимости от языка:

Большинство разработчиков начинало программировать на Pascal и С++, но вскоре первыми языками разработчиков станут JavaScript и Python. Интересно, как это повлияет на культуру программирования ...

Данные и скрипты обработки можно найти на GitHub.

Также dou.ua недавно опубликовал опрос о зарплатах в Украине, где оказалось что средняя зарплата составляет $2500, перевод этой статьи вы можете прочитать тут.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru