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

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

Как я устал от JavaScript и создал свой собственный язык программирования

10.11.2020 14:15:42 | Автор: admin

За свою карьеру я успел поработать со множеством языков программирования. Писал flash-игры на ActionScript 3 и Android-игры на Java, сервера на Java, Scala и NodeJS (JavaScript), скрипты на Python, веб и мобильные приложения на React (JavaScript). И на каком бы языке я не писал, меня не покидало ощущение, что синтаксис этого языка слишком многословен, полон излишеств, шума и синтаксического бойлерплейта, мешающего пониманию написанного кода.

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

  • Человекочитаемость - идеальный язык должен быть читабельным и легким для понимания человеком

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

  • Внутренняя непротиворечивость - синтаксические конструкции языка должны быть внутренне согласованы и хорошо сочетаемы

  • Чистота и красота - язык должен выглядеть максимально чисто и эстетически красиво

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

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

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

Как я перестал бояться и полюбил JavaScript

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

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

Поэтому когда я открыл для себя NodeJS, я сначала не поверил, что можно писать так просто и лаконично. И чем дольше я знакомился с JavaScript, тем более гениально спроектированным и внутренне согласованным он мне казался. Система объектов и прототипов позволяет легко писать код в объектно-ориентированном стиле, а то, что в основе всего этого лежит функция, делает легким и приятным программирование в привычном мне функциональном стиле. А динамическая природа языка делает его фантастически гибким.

Кроме того в экосистеме JavaScript меня поразило обилие инструментов и хороших библиотек. В Scala в порядке вещей была ситуация, когда библиотека была написана для работы над PhD и заброшена после защиты диссертации. В Java библиотеки были в основном рабочими, но многие из них были абсолютно не дружелюбными - лежали не пойми где, не имели документации в нормальном виде, имели ужасный API и так далее. У каждого языка была своя ужасающая система сборки и управления зависимостями вроде maven, gradle или sbt.

В JavaScript же программисту доступен удобный пакетный-менеджер npm, полный прекрасно отлаженных библиотек с хорошей документацией, удобным API и дружелюбным коммьюнити. То, что кажется обыденностью в экосистеме JavaScript, в экосистемах других языков не является таковым: например, не во всех случаях является возможным понять что же делает какая-нибудь Ruby-библиотека по ее readme в гитхабе.

Отдельным преимуществом JavaScript является то, что сейчас его можно запустить где угодно. Для веб-приложений уже давно стандартом является React, на мобильных девайсах очень распространен React Native, на сервере бешено популярен NodeJS. JavaScript стал де-факто универсальным языком, на котором можно писать под что-угодно. Даже миллиарды микроволновок и утюгов, которые раньше были вотчиной Java, скоро все будут программироваться на JavaScript.

Но есть ли у JS какие-нибудь минусы?

Что не так с JavaScript

Я программирую на JavaScript, в основном, в чисто функциональном стиле. Я использую const, не мутирую объекты и массивы, использую чистые функции. Так что мой код выглядит примерно так:

const incrementNumbers = numbers => numbers.map(number => number + 1)const takeNumbersGreaterThan = threshold => numbers => numbers.filter(number => number > threshold)const func = (numbers, threshold) => {    const incrementedNumbers = incrementNumbers(numbers)    const filteredNumbers = takeNumbersGreaterThan(threshold)(incrementedNumbers)    return incrementedNumbers}

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

Собственно, именно эти недостатки и натолкнули меня на возвращение к проекту собственного языка программирования. Ну а из-за описанных мною ранее громадных преимуществ экосистемы именно JavaScript я сделал платформой для своего языка.

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

Порядок выполнения операций

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

Поэтому в моем языке выражение:

a (b c) (d e)

можно также записать как:

a  b c  d e

Ну а выражение:

a (b (c d))

может быть записано так:

a  b (c d)

или так:

a  b    c d

Присваивание

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

В Una константа объявляется так:

= name "John"= number 1

Если мы хотим присвоить константе значение вычислений, то делаем так:

= z (calculate x y)

а можно обойтись и без скобок, этот оператор позволяет сделать так:

= z calculate x y

или так

= z calclulate  x  y

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

Арифметика, сравнения и логика

В Una вы можете пользоваться стандартными JavaScript операторами арифметики, сравнения и логики.

Арифметические операции выглядят так:

= a (+ 1 2)= b (- 2 1)= c (* 3 2)= d (/ 4 2)= e (% 5 2)

Все из них могут принимать множество параметров. Например:

= sum (+ 1 2 3 4 5)

А минус может принимать и один параметр, что является отрицанием числа:

= a (- 1)= b -1

Вот пример сложных арифметических расчетов:

= a +  * 2 4  / 9 3  + (* 3 2) (/ 4 2)  *    + 1 2    / 6 3

Конечно также вы можете использовать любые функции из стандартной библиотеки JS:

= randomNumber Math.random ()

Операторы сравнения имеют некоторые отличия от JavaScript. Давайте посмотрим:

= a (== 1 1)= b (~= 1 '1')= c (!= 1 '1')= d (!~= 1 '2')= e (> 2 1)= f (>= 2 1)= g (< 1 2)= h (<= 1 2)

Оператор == и != - это точное по типу сравнение, аналог === и !== в JavaScript. Для неточного по типу сравнения нужно применять ~= и !~=.

Логические операторы тоже немного отличаются:

= a (& true false)= b (| true false)= c (! true)= d !c

Как вы видите, вместо && используется &, а вместо || используется |.

Условные операторы

Тернарный условный оператор работает также как и в JavaScript:

= message  ? (> 2 1) "Greater" "Less"

А еще существует условный оператор с возвращением значения:

?! (== number 1) "One"

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

if (number === 1) return "One"

Коллекции

В Una есть две базовые коллекции: массивы и объекты.

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

= numbers :: 1 2 3 4 5

А объект задается так:

= user :  name 'John'  age 13  passport :    id 1    country 'USA'

В Una работают все трюки JavaScript.

Можно задать элемент массива или поле объекта готовой константой:

= a 1= numbers :: a 2 3= name 'John'= user :  name  age 13

Работает троеточие:

= threeNumbers :: 1 2 3= fiveNumbers :: ...threeNumbers 45= userFields :  name 'John'  age 13= user :  id 1  gender 'm'  isAlive true  ...userFields

Работает деконструкция:

= numbers :: 1 2 3= (:: one two three) numbersconsole.log one= user : (name 'John') (age 12)= (: name) userconsole.log name

А чтобы взять определенный элемент массива или значение определенного поля объекта используется точка:

= list :: 1 2 3= object : (a 1) (b 2)console.log (. list 0)console.log (. object 'a')

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

= object : (a 1)console.log (.a object)

Этот синтаксис удобен для вызова функций, лежащих внутри объекта.

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

Стрелочная симметрия синхронных вычислений

Правая стрелка симметрии синхронных вычислений является функцией:

= sum -> (x y)  + x y= onePlusTwo -> ()  = one 1  = two 2  + one two

Последняя строчка функции всегда является возвращаемым значением. Данный код на JavaScript будет выглядить так:

const sum = (x, y) => x + yconst onePlusTwo = () => {  const one = 1  const two = 2  return one + two}

Для преждевременного прерывания выполнения функции можно использовать вышеупомянутый условный оператор:

= isSumGreaterThanFiveIfXisNotZero -> (x y)  ?! (== x 0) "X is zero"  = sum (+ x y)  ? (> sum 5)    "Greater"    "Less"

Левая стрелка симметрии синхронных вычислений является мнгновенно выполняемым блоком кода, также называемым Immediately Invoked Function Expression:

= result <-  = a 1  = b 2  + a b

Этот оператор можно использовать для чего-то вроде задания константы значением по каким-то условиям:

= result <-  ?! (== value 0) "Zero"  ?! (== value 1) "One"  ? (< value 10) "Less than ten" "More than ten"

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

= func -> number  ? (== number 0)    <-      console.log "Number is zero!"  + number 1

Стрелочная симметрия асинхронных вычислений

Правой стрелкой симметрии асинхронных вычислений является асинхронная функция:

= getUserPosts --> user  database.loadPosts user.postIds

Левой стрелкой симметрии асинхронных вычислений является await:

= checkIfUserIsAdmin --> userId  = user <-- (database.loadUser userId)  == user.role 'admin'

Стрелочная симметрия модулей

Правой стрелкой модульной симметрии является импорт:

=-> './index.css'=-> 'react' React=-> './validation' (: validateEmail)

Он превращается в import или в require в зависимости от соответствующей настройки компилятора.

Левой стрелкой модульной симметрии является экспорт.

Дефолтный - export default в JavaScript:

<-= a

Экспорт отдельной константы export const в JavaScript:

<-= = a 1

Экспорт нескольких констант - export {a, b} в JavaScript:

<-= ()  a  b

Вы можете импортировать любые JavaScript-модули в Una, и любые Una-модули в JavaScript. Все модули полностью совместимы друг с другом.

Стрелочная симметрия ошибок

Правой стрелкой симметрии ошибок является try-catch:

|->  <-    = getName null    getName ()  -> error    console.log error    'John'

В отличие от JavaScript в Una этот оператор является выражением, всегда возвращающим значение, а также этот оператор не имеет блока finally.

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

|->  <--    getNameAsync ()  --> error    console.log error    "John"

Левой стрелкой симметрии ошибок является выброс ошибки:

= addOneToNumber -> number  ?! (isNaN number)    <-| "number is not valid"  + number 1

Стрелочная симметрия чейнинга

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

=-> 'ramda' R= electronics ::  :    title ' iPhone '    type 'phone'= phones |>  electronics  R.find    R.propEq 'type' 'phone'  R.prop 'title'  R.toUpper  R.trim

Левая стрелка симметрии чейнинга подставляет выражение как первый параметр следующей за ним функции. Это удобно для работу со стандартными методами массивов и lodash:

= sum <| (:: 1 2 3)  .map (-> x (+ x 1))  .filter (-> x (> x 2))  .reduce (-> (x y) (+ x y)) 0

Без чейнинга это выражение выглядело бы уродски:

= sum .reduce  .filter    .map (:: 1 2 3) (-> x (+ x 1))    -> x (> x 2)  -> (x y) (+ x y)  0

Интерполяция строк

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

= name 'John'= count 5= fruit 'apples'= text `  'Hello, ${0}' name  'I have ${0} ${1}'    count    fruitconsole.log text

Этот пример выведет:

Hello, JohnI have 5 apples

Так же вы можете провести интерполяцию строк по какой-либо функции - чтобы сделать это пришлите функцию первым параметром. Это очень часто используется например в styled-components:

= color 'red'= Container `  styled.div  'background-color: ${0};' color

React и React Native

Для написания веб-приложения на React или мобильного приложения на React Native вы не сможете использовать JSX. Вместо этого вы можете использовать функцию createElement, лежащую в основе React.

=-> './index.css'=-> 'react' React=-> 'react-dom' ReactDOM=-> './styles' S= (: (createElement e)) React= App -> ((: name))  = (:: count setCount) (React.useState 0)  e S.Container :    e S.Hello (: (color 'green')) 'Hello, '    e S.Name : name    e S.IncrementCount      : (onClick (-> () (setCount (+ count 1))))      'Press me'    e S.Count : countReactDOM.render  e App (: (name 'John'))  document.getElementById 'root'

Первым аргументом функция всегда принимает компонент, вторым объект с параметрами (для пустого объекта в Una вы можете использовать : ), а все последующие параметры - это chilldren.

Что еще предстоит сделать

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

  • регулярные выражения

  • возможность заинстансить класс (оператор new)

  • комментарии в коде

  • плагин для Visual Studio Code с подсветкой синтаксиса

  • и многое другое...

Заключение

Язык Una устанавливается в любой JavaScript-проект простейшим babel-плагином, который компилирует все файлы с расширением .una в JavaScript. Вы можете найти инструкции по установке, подробную документацию и примеры в моем репозитории на гитхабе. Я буду жутко рад, если вы поиграетесь с языком, протестируете его и дадите фидбек, что можно было бы улучшить, а что стоит починить. Ну и от звездочек конечно не откажусь.

Спасибо за внимание!

Подробнее..

Kotlin язык программирования как продукт

03.11.2020 14:09:04 | Автор: admin

Язык программирования это тоже продукт. Он помогает разработчикам выражать свои идеи так, чтобы их мог интерпретировать компьютер. Может показаться, что развивать язык это брать последние достижения теории языков программирования, реализовывать их и из года в год выкатывать разработчикам. Это не так. Егор Толстой, Kotlin Product Manager, и Андрей Бреслав, руководитель проекта Kotlin, рассказали, зачем JetBrains бесплатный язык программирования, как он устроен и откуда приходят новые пользователи. Статья вдохновлена выпуском подкаста make sense о Kotlin.

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

Мы начали делать Kotlin десять лет назад, а первый релиз вышел зимой 2016 года. Изначально он задумывался как язык, который улучшит жизнь Java-программистов. Сейчас на Kotlin пишут даже приложения для браузеров и iOS. Современный Kotlin универсальный язык программирования с большим количеством приятных для разработчиков фич, статически типизированный, заточенный под большие проекты и поддержку крупных кодовых баз.

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

Зачем JetBrains делает бесплатный язык программирования

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

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

У JetBrains есть четыре причины заниматься созданием языка.

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

Но при этом Kotlin поддерживается в IDE от JetBrains, которые не заточены под какую-то конкретную технологию. Например, та же IntelliJ IDEA поддерживает Kotlin и какая-то часть ее продаж приходится на Kotlin-разработчиков. Это достаточно сложно отследить, но мы видим, что среди пользователей платной версии IntelliJ IDEA их немало.

Узнаваемость бренда. Если раньше JetBrains была компанией, которая делает классные IDE, то теперь мы компания, которая сделала Kotlin. А это совершенно другой уровень интереса просто потому, что за кофе люди чаще обсуждают языки программирования, чем IDE. И нам это действительно помогает. Если когда-то в самом начале JetBrains был локомотивом для Kotlin, то теперь уже оба бренда помогают друг другу и мы видим, что все больше людей знает про JetBrains благодаря Kotlin.

Kotlin как наш собственный инструмент. У нас в JetBrains много разработчиков, и от их продуктивности сильно зависит эффективность компании. Идея начать делать Kotlin вообще родилась из того, что мы не хотели продолжать использовать Java. И сейчас Kotlin используется почти во всех наших продуктах. Например, в IntelliJ IDEA на Kotlin написано 1,5 миллиона строк кода. А недавно мы запустили Space инструмент для интегрированной работы команд, который написан на Kotlin сразу для всех платформ: Android, iOS, сервер, веб, десктоп. Ребята, которые его разрабатывают, говорят, что без Kotlin такой продукт создать было бы в разы сложнее.

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

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

Как устроен язык программирования

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

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

Kotlin как продукт: схемаKotlin как продукт: схема

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

  1. Нужно дать пользователям единый понятийный аппарат, который поможет понимать идеи друг друга.

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

Интегрированная среда разработки (IDE). Правильно работать с синтаксисом позволяет поддержка языка в IDE, том самом Word для программистов. Что делает IDE:

  • подсказывает подходящие по смыслу конструкции;

  • подсвечивает некорректный код;

  • запускает код и помогает искать в нем ошибки;

  • упрощает частые преобразования.

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

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

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

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

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

Подробно и глубоко компилятор Kotlin мы разбирали в отдельном выпуске подкаста Podlodka.

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

Сообщество. Программы, правила, инструменты и все такое это замечательно, но Kotlin это в первую очередь люди. За последний год почти 6 миллионов людей работали с кодом на Kotlin, из которых 1,2 миллиона делали это регулярно.

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

Влияние языка программирования на продукт и людей

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

Итак, на что влияет язык:

  • UX продукта от него зависит размер итогового приложения, скорость его работы, платформы, доступные для запуска.

  • Надежность и стабильность продукта. Отличный пример billion dollar mistake.

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

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

Последний пункт стоит развернуть отдельно. Например, программисты на COBOL очень редко делают хипстерские сервисы, так же, как и значимая часть программистов на С++ у них фокус внимания с опыта конечного пользователя и красивого UI смещается в сторону технических деталей.

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

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

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

Эмоциональная. Это то, что мы видим своими собственными глазами. Фидбэк счастливых пользователей в социальных сетях, новые кейсы по использованию Kotlin в крупных компаниях вроде Atlassian, Adobe или Netflix, или факт, что большая часть Android-приложений, которыми мы пользуемся каждый день, написана на Kotlin. Понимание, что твои решения и их реализация напрямую влияют на миллионы разработчиков, а косвенно и на всех пользователей Android-приложений, просто безумно драйвит.

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

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

Приток новых пользователей идет из трех основных источников.

Разработчики из других экосистем. Причины у них могут быть разные. Самая простая смена места работы или проекта, вместе с которыми меняется и технологический стек. Поэтому мы сильно заинтересованы в том, чтобы Kotlin появлялся в списке используемых технологий в разных компаниях. Другая причина это решение какой-то крупной боли, от которой страдают разработчики какого-то другого языка. Именно это произошло, когда Android-разработчики получили доступ к Kotlin. Им настолько понравилась разработка на нем, что под их давлением Google сделал Kotlin официальным языком разработки. Третья причина возможность попробовать что-то новое или запрыгнуть на волну хайпа.

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

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

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

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

Классификация стадий роста продуктаКлассификация стадий роста продукта

Для анализа мы используем известную классификацию стадий роста продукта из книги Crossing the Chasm. Ранние последователи у нас сейчас есть практически во всех сегментах разработки. Люди используют Kotlin для Data Science, пишут на нем игры, решения для IoT и даже занимаются научными вычислениями физическим моделированием процессов и подобными вещами. В сегменте кроссплатформенной мобильной разработки мы только подходим к бездне, в бэкенд-разработке перешагиваем ее, а в Android вовсю захватываем Late Majority и смотрим на Laggards.

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

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

Ценность языка программирования сильно зависит от самого человека, от его бэкграунда и целей. И хотя в Kotlin заложено конечное количество ценностей, люди очень по-разному расставляют для себя субъективные приоритеты. Осознанный человек может говорить, что ему Kotlin нравится из-за возможности быстро обнаруживать ошибки или из-за того, что код получается короче. Но если его разговорить, окажется, что ему просто нравится в целом комфортно, и все. Это признак синергии. Разные аспекты, складываясь вместе, дают гораздо большее удовольствие и комфорт, чем в языке, которым программист пользовался раньше.

Мы опираемся на следующие ключевые ценности:

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

Пример типичной операции на KotlinПример типичной операции на Kotlin

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

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

Эти базовые ценности мы используем, чтобы выстроить value proposition (ценностное предложение) для любого сегмента. Например, в нашем кроссплатформенном мобильном SDK KMM, мы делаем упор на:

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

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

  • Простое встраивание в существующие большие кодовые базы.

Кто такие менеджеры продуктов в Kotlin

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

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

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

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

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

Статья вдохновлена выпуском подкаста make sense о Kotlin с Андрем Бреславом и Егором Толстым. В подкасте make sense Юра Агеев, основатель ProductSense, вместе с гостями говорит о том, что важно при создании продуктов. Еще несколько интересных эпизодов:

о выстраивании отношений с командой разработки и важности технических навыков;

об аутсорс-разработке, работе с заказчиком и развитии бизнес-мышления;

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

Подробнее..

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

18.08.2020 20:07:52 | Автор: admin
Здравствуйте, уважаемые читатели. В этой публикации речь пойдет о такой (уже ставшей привычной) вещи как ускорение работы программы путем применения параллельных вычислений. Технологии организации таких вычислений известны это и обычное многопоточное программирование, и применение специальных интерфейсов: OpenMP, OpenAcc, MPI, DVM и многих других (при этом распараллеливаются циклы, используется векторизация или конвейеризация, организуются ленивые вычисления, выделяются независимые блоки программы, которые можно запустить в параллель и т.п.).

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

Идея распараллеливания


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

for (int i = 0; i < N; i++) {x = f(y);y = g(x);}

На первый взгляд, распараллелить такой цикл невозможно. Однако мы попробуем. Попытаемся исполнять параллельно первый и второй операторы тела цикла. Проблема состоит в том, что на момент вычисления g(x) должен быть известен x, но он будет рассчитан только в конце первой части. Что же, введем некоторую схему, которая в начале второй части попытается предсказать новое значение x. Можно это сделать, например, с помощью линейной предикции, которая обучится предсказывать новое значение x, опираясь на историю его изменения. Тогда вторую часть можно считать параллельно с первой (это и есть сверхоптимизм), а когда обе будут подсчитаны, сравнить предсказанное значение x с реальным, полученным в конце первой части. Если они примерно равны, то результат вычислений обеих частей можно принять (и перейти к следующему витку цикла). А если они сильно отличаются, то потребуется пересчитать только вторую часть. При такой схеме в какой-то части случаев получим чистое распараллеливание, в остальных фактический последовательный счет. Алгоритм выполнения цикла при этом такой:

for (int i = 0; i < N; i++) {Распараллеливаем на два ядра {На ядре 1  считаем x = f(y). Далее передаем во вторую часть получение значение x;На ядре 2  предсказываем значение x* и считаем y* = g(x*). Получаем значение x из первой части и сравниваем его с x*. Если разница невелика, то y = y* и завершаем итерацию цикла. Если различие большое, повторяем вычисление с новыми данными: y = g(x). }}

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

Реализация распараллеливания сверхоптимистичные вычисления


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

for (int i = 0; i < N; i++) {Распараллеливаем на два ядра, включаем частично транзакционную память {Ядро 1 (транзакция 1):x = f(y);Предсказывающий_Канал.put(x);Ядро 2 (транзакция 2):Предсказывающий_Канал.get(x);y = g(x);}}

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

Некоторые полезные применения: нейронные сети, метод частиц в ячейках


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

Цикл обучения нейронной сети включает последовательный проход по обучающим парам, причем для каждой пары выполняется прямой ход (расчет выхода сети) и обратный ход (коррекция весов и смещений). Это и есть две части тела цикла по обучающим парам и для их распараллеливания можно применить вышеизложенный подход (кстати, его можно применить и при параллельном проходе по обучающим парам, с незначительными изменениями). В результате на типичной задаче обучения нейронной сети я получил 50% выигрыша по скорости работы.

Автоматизация распараллеливания C-программ


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

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

Исходная программа:


#include <stdlib.h>#include <stdio.h>#include <math.h>#pragma auto parallelize#pragma auto pure(malloc,fabs,free,sizeof,omp_get_wtime)#define theta 1.83#define NX 40#define NY 40#define h 0.1#define NP 15000// Собирающая электростатическая линза#define U1 200#define U2 5000#define e -1.5E-13#define m 1E-11#define e0 8.85E-12#define V (h*h)#define tau 0.000015#define T 0.09#define POISSON_EPS 0.01#define TOL_EPS 0.25int main() {        double * U  = (double *)malloc(NY*NX*sizeof(double));        double * UU = (double *)malloc(NY*NX*sizeof(double));        double * EX = (double *)malloc(NY*NX*sizeof(double));        double * EY = (double *)malloc(NY*NX*sizeof(double));double * PX = (double *)malloc(NP*sizeof(double));double * PY = (double *)malloc(NP*sizeof(double));int * X = (int *)malloc(NP*sizeof(int));int * Y = (int *)malloc(NP*sizeof(int));double ro[NY][NX];split_private double t;split_private double tm;split_private int i, j;for (i = 0; i < NY; i++)for (j = 0; j < NX; j++) {UU[i*NX+j] = j == NX-1 ? U2 : j == NX/2 && (i < NY/4 || i > 3*NY/4) ? U1 : 0.0;EX[i*NX+j] = 0.0;EY[i*NX+j] = 0.0;}for (i = 0; i < NP; i++) {int x, y;PX[i] = 0.5*NX*h*rand()/RAND_MAX;PY[i] = NY*h*rand()/RAND_MAX;x = PX[i]/h;y = PY[i]/h;if (x < 0) x = 0;else if (x > NX-1) x = NX-1;if (y < 0) y = 0;else if (y > NY-1) y = NY-1;X[i] = x;Y[i] = y;}tm = omp_get_wtime();for (t = 0.0; t < T; t += tau) {unsigned int n[NY][NX] = { 0 };double err;int ptr = 0;for (i = 0; i < NY; i++)    for (j = 0; j < NX; j++, ptr++)U[ptr] = UU[ptr];for (i = 1; i < NY - 1; i++)for (j = 1; j < NX - 1; j++) {EX[i*NX+j] = -(U[i*NX+j+1]-U[i*NX+j-1])/2.0/h;EY[i*NX+j] = -(U[(i+1)*NX+j]-U[(i-1)*NX+j])/2.0/h;}for (i = 0; i < NP; i++) {PX[i] += tau*e*EX[Y[i]*NX+X[i]]/m;PY[i] += tau*e*EY[Y[i]*NX+X[i]]/m;}for (i = 0; i < NP; i++) {int x = PX[i]/h;int y = PY[i]/h;if (x < 0) x = 0;else if (x > NX-1) x = NX-1;if (y < 0) y = 0;else if (y > NY-1) y = NY-1;Y[i] = y;X[i] = x;n[y][x]++;}for (i = 0; i < NY; i++)for (j = 0; j < NX; j++)ro[i][j] = n[i][j]*e/V;do {err = 0.0;for (i = 1; i < NY - 1; i++)for (j = 1+(i-1)%2; j < NX - 1; j+=2) {  int ptr = i*NX + j;  if (!(j == NX/2 && (i < NY/4 || i > 3*NY/4))) {double _new = (1-theta)*UU[ptr] + theta/4.0*(UU[ptr-1]+UU[ptr+1]+UU[ptr+NX]+UU[ptr-NX]-h*h*ro[i][j]/e0);double loc_err = fabs(UU[ptr] - _new);if (loc_err > err) err = loc_err;UU[ptr] = _new;  }}for (i = 1; i < NY - 1; i++)for (j = 1+i%2; j < NX - 1; j+=2) {  int ptr = i*NX + j;  if (!(j == NX/2 && (i < NY/4 || i > 3*NY/4))) {double _new = (1-theta)*UU[ptr] + theta/4.0*(UU[ptr-1]+UU[ptr+1]+UU[ptr+NX]+UU[ptr-NX]-h*h*ro[i][j]/e0);double loc_err = fabs(UU[ptr] - _new);if (loc_err > err) err = loc_err;UU[ptr] = _new;  }}for (j = 0; j < NX; j++) {UU[j] = UU[NX + j];UU[(NY-1)*NX + j] = UU[(NY-2)*NX + j];}} while (err > POISSON_EPS);}for (i = 0; i < NY; i++) {for (j = 0; j < NX; j++)printf("%lf\t", UU[i*NX+j]);printf("\n");}return 0;}

Автоматически распараллеленная программа


#include "transact.h"#define split_private /* split-private */#include <stdlib.h>#include <stdio.h>#include <math.h>#define theta 1.83#define NX 40#define NY 40#define h 0.1#define NP 15000#define U1 200#define U2 5000#define e -1.5E-13#define m 1E-11#define e0 8.85E-12#define V (h*h)#define tau 0.000015#define T 0.09#define POISSON_EPS 0.01#define TOL_EPS 0.25int  main(  ){  double * U  = (double *)malloc(NY*NX*sizeof(double));  double * UU = (double *)malloc(NY*NX*sizeof(double));  double * EX = (double *)malloc(NY*NX*sizeof(double));  double * EY = (double *)malloc(NY*NX*sizeof(double));  double * PX = (double *)malloc(NP*sizeof(double));  double * PY = (double *)malloc(NP*sizeof(double));  int * X = (int *)malloc(NP*sizeof(int));  int * Y = (int *)malloc(NP*sizeof(int));  double ro[NY][NX];  split_private double t;  split_private double tm;  split_private int i, j;  for ( i = 0; i < NY; i++ )    for ( j = 0; j < NX; j++ )      {        UU[i*NX+j] = j == NX-1 ? U2 : j == NX/2 && (i < NY/4 || i > 3*NY/4) ? U1 : 0.0;        EX[i*NX+j] = 0.0;        EY[i*NX+j] = 0.0;      }  for ( i = 0; i < NP; i++ )    {      int x, y;      PX[i] = 0.5*NX*h*rand()/RAND_MAX;      PY[i] = NY*h*rand()/RAND_MAX;      x = PX[i]/h;      y = PY[i]/h;      if ( x < 0 )        x = 0;      else        if ( x > NX-1 )          x = NX-1;      if ( y < 0 )        y = 0;      else        if ( y > NY-1 )          y = NY-1;      X[i] = x;      Y[i] = y;    }  tm = omp_get_wtime();#pragma omp parallel num_threads(2) private(t,tm,i,j)   {    int __id__ = omp_get_thread_num();    TOut<double > * out_ro = __id__ == 0 ? new TOut<double >("ro63", (NY)*(NX), 2, 0.01, -1, "63") : NULL;    TIn<double > * in_ro = __id__ == 1 ? new TIn<double >("ro63", (NY)*(NX), 2, 0.01, -1, "63") : NULL;    for ( t = 0.0; t < T; t += tau )      {        unsigned int n[NY][NX] = { 0 };        double err;        int ptr = 0;        if ( __id__ == 0 )          {            for ( i = 0; i < NY; i++ )              for ( j = 0; j < NX; j++, ptr++ )                U[ptr] = UU[ptr];          }transaction_atomic("63")        {          if ( __id__ == 0 )            {              for ( i = 1; i < NY - 1; i++ )                for ( j = 1; j < NX - 1; j++ )                  {                    EX[i*NX+j] = -(U[i*NX+j+1]-U[i*NX+j-1])/2.0/h;                    EY[i*NX+j] = -(U[(i+1)*NX+j]-U[(i-1)*NX+j])/2.0/h;                  }              for ( i = 0; i < NP; i++ )                {                  PX[i] += tau*e*EX[Y[i]*NX+X[i]]/m;                  PY[i] += tau*e*EY[Y[i]*NX+X[i]]/m;                }              for ( i = 0; i < NP; i++ )                {                  int x = PX[i]/h;                  int y = PY[i]/h;                  if ( x < 0 )                    x = 0;                  else                    if ( x > NX-1 )                      x = NX-1;                  if ( y < 0 )                    y = 0;                  else                    if ( y > NY-1 )                      y = NY-1;                  Y[i] = y;                  X[i] = x;                  n[y][x]++;                }              for ( i = 0; i < NY; i++ )                for ( j = 0; j < NX; j++ )                  ro[i][j] = n[i][j]*e/V;              out_ro->put((double  *)ro);            }          else            {              double  ro[NY][NX];              in_ro->get((double  *)ro, 0);              do                {                  err = 0.0;                  for ( i = 1; i < NY - 1; i++ )                    for ( j = 1+(i-1)%2; j < NX - 1; j+=2 )                      {                        int ptr = i*NX + j;                        if ( !(j == NX/2 && (i < NY/4 || i > 3*NY/4)) )                          {                            double _new = (1-theta)*UU[ptr] + theta/4.0*(UU[ptr-1]+UU[ptr+1]+UU[ptr+NX]+UU[ptr-NX]-h*h*ro[i][j]/e0);                            double loc_err = fabs(UU[ptr] - _new);                            if ( loc_err > err )                              err = loc_err;                            UU[ptr] = _new;                          }                      }                  for ( i = 1; i < NY - 1; i++ )                    for ( j = 1+i%2; j < NX - 1; j+=2 )                      {                        int ptr = i*NX + j;                        if ( !(j == NX/2 && (i < NY/4 || i > 3*NY/4)) )                          {                            double _new = (1-theta)*UU[ptr] + theta/4.0*(UU[ptr-1]+UU[ptr+1]+UU[ptr+NX]+UU[ptr-NX]-h*h*ro[i][j]/e0);                            double loc_err = fabs(UU[ptr] - _new);                            if ( loc_err > err )                              err = loc_err;                            UU[ptr] = _new;                          }                      }                  for ( j = 0; j < NX; j++ )                    {                      UU[j] = UU[NX + j];                      UU[(NY-1)*NX + j] = UU[(NY-2)*NX + j];                    }                }              while ( err > POISSON_EPS )                ;            }        }      }    delete in_ro;    delete out_ro;  }  for ( i = 0; i < NY; i++ )    {      for ( j = 0; j < NX; j++ )        printf("%lf\t", UU[i*NX+j]);      printf("\n");    }  return 0;}

Итоги


Итак, иногда можно пытаться распараллелить программу даже в случаях, когда она состоит из строго последовательных фрагментов, и даже получать положительные результаты по ускорению (в моих экспериментах прирост ускорения от 15 до 50%). Надеюсь, что эта небольшая статья окажется кому-нибудь полезной.
Подробнее..

G-code, потерявшийся брат Assembler-а

05.10.2020 04:08:36 | Автор: admin
Про язык управления промышленными CNC-станками и всевозможными любительскими устройствами вроде 3D-принтеров написано очень много статей, но почитать о том, какова идеология этого языка и как она связана с аппаратной реализацией почти негде. Поскольку моя работа связана непосредственно с программированием станков и автоматизацией производства, я попробую заполнить этот пробел, а также объяснить, почему выбрал такой странный заголовок.

Пару слов о себе, и почему я вообще решил написать об этом. Мои рабочие обязанности заключаются, в том числе, в том, чтобы заставить любой имеющийся в компании станок с ЧПУ делать всё, что он вообще может физически. Компания небольшая (единицы сотен сотрудников), но в арсенале вертикальные фрезерные автоматы Haas трех разных поколений, горизонтальные фрезерные автоматы DMG Mori нескольких типов, лазерный резак Mitsubishi, токарные автоматы Citizen Cincom и куча всего еще. И весь этот зоопарк управляется программами на G-code. Изучая разные реализации этого языка, я понял, что то, что пишут в учебниках и книгах по нему не всегда является правдой. В то же время, мне стали понятны многие аналогии между этим языком и Assembler-ом, который я изучал когда-то в институте, и на котором практически ничего серьезного никогда не написал.

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

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

Базовый синтаксис


Если вы хоть раз видели программу на G-code, то знаете, что это последовательность строк, которые состоят из буквенных кодов, за которыми следуют некие числа. Эти буквенные коды называются адрес. Причина такого термина очень проста: в первых контроллерах станков программа выполнялась путем записи значений в ячейки памяти, которым были даны буквенные имена. Исполнительные устройства, в свою очередь, читали значения по этим адресам и делали то, что от них требуется. Когда мне приходится обучать операторов, я объясняю им, что контроллер, на самом деле, можно условно поделить на две части: ту, что отвечает за интерфейс с пользователем, и ту, что отвечает за работу механизмов. Они часто и физически разнесены по разным платам. А общение между ними происходит все еще через ограниченный набор этих самых ячеек памяти. Другой вопрос, что со временем, к именованным адресам, которые обозначаются буквами латинского алфавита, добавились еще численные адреса (начинающиеся с символа #), через которые осуществляется доступ к портам ввода-вывода, настройкам, специальным возможностям, и так далее.

Традиционно, когда описывают синтаксис G-code, говорят, что любая команда в программе начинается с буквы G для подготовительных кодов и M для дополнительных, что номер строки начинается с буквы N, а номер программы или подпрограммы с буквы O. Это, в принципе, правда, но не вся и не всегда.

Во-первых, деление на G- и M-коды условно. Раньше, во времена первых станков с ЧПУ, это имело практическое значение, потому что связь синтаксиса с аппаратной реализацией была жестче. Сейчас же, это деление практически потеряло свое значение. Однако, правило о том, что M-код может быть только один на строке, все же стоит выполнять, как в старые времена, потому что никогда не знаешь точно, на сколько вольно производитель контроллера станка обошелся с реализацией языка. Например, на станках DMG Mori, автоматическое измерение длины инструмента, установленного в шпинделе, выполняется кодом G324, но если вы просто хотите активировать измерительный сенсор для того, чтобы почистить его (при этом крышка, под которой он скрыт во время обычной работы, открывается, и он выдвигается, но измерение не происходит), вам нужно выполнить код M44. По классической логике языка, использование G-кода для измерения длины нестандартное решение, потому что вы явно не хотите, чтобы одновременно с этим (одной строкой кода) выполнялись какие-то еще действия. Но в современных реалиях это не имеет значения. На станках Haas та же операция измерения делается вообще запуском специальной подпрограммы с параметрами (тип и номер инструмента), а не одним кодом. Плюс, практически любой контроллер позволяет определять пользовательские G- или M-коды, полностью стирая различие между ними.

Ветвление и циклы


В G-code есть условный и безусловный переход по команде GOTO. Синтаксис адреса (аргумента) этой команды может различаться. Чаще всего, это число, соответствующее номеру строки, заданному на самой строке, как Nчисло. Но некоторые реализации языка, например синтаксис контроллеров Okuma, позволяют давать строкам буквенные метки. С одной стороны, это хорошо, а с другой нетипично, что смущает некоторых программистов и операторов.

Условный переход выполняется традиционным IF [выражение] THEN команда. Конструкция ELSE в языке не нужна, потому что если условие ложно, команда на этой строке не будет выполнена, а будет выполнен переход на следующую строку. Это важно понимать, потому что ошибка с тем, чтобы поместить команду, которая должна быть выполнена только если условие истинно, на следующую строку одна из самых распространенных в ручном программировании. Вероятно, это случается с неопытными программистами, которые до этого привыкли к синтаксису языков высокого уровня. В некоторых реализациях не обязательно и THEN, что добавляет краткости, но не добавляет читаемости. Сравните (даже не имея представления о смысле):
IF [#1 NE 10] THEN #2=20
и
IF [#1 NE 10] #2=20

Циклы в явном виде реализованы конструкцией WHILE [выражение] DOметка ... ENDметка, но, конечно, могут быть реализованы и через условный переход. Синтаксис позволяет также выпрыгивать изнутри цикла, используя GOTO. Но запрыгнуть внутрь цикла, используя размещенную внутри него метку нельзя. Возможно, в каких-то контроллерах это и разрешено, но в тех, на которых я это проверял, это вызывает ошибку.

Подпрограммы


История использования подпрограмм в G-code тянется еще со времен перфолент. Существует несколько способов их вызывать, и это достаточно избыточно. Каждая программа или подпрограмма на G-code имеет свой идентификатор цифровой код. Положение (под)программы определяет, должен ли этот идентификатор начинаться с латинской O или латинской N. По этому коду их можно вызывать разными способами. Эти способы (используемые для этого коды) различаются, например, тем, где контроллер будет искать эту подпрограмму внутри файла (на станках Haas это код M97) программы или во всех файлах (а это уже M98). Если подпрограмма содержится в файле программы и имеет идентификатор номера строки (N), ее следует вызывать, как внутреннюю подпрограмму. В этом случае, совершенно не нужно беспокоиться об уникальности идентификатора. Если же подпрограмма имеет идентификатор, начинающийся с буквы O, она может содержаться и внутри файла основной программы, и в отдельном файле. В этом случае, следует заботиться о том, чтобы номер был уникален среди всех программ в памяти контроллера, потому что иначе, контроллер либо выдаст ошибку при попытке записать такую подпрограмму в его память, либо, что хуже, может выполнить первую попавшуюся подпрограмму из нескольких с одинаковыми номерами. На большинстве контроллеров это, к счастью, невозможно. В общем, любую программу можно вызвать, как подпрограмму, только из-за отсутствия кода возврата M99, аналога return, и присутствия кода остановки M30, аналога halt, контроллер просто остановит выполнение. Но в некоторых случаях (когда это действительно конец процесса обработки детали) это может быть совершенно нормальным решением, пусть оно и выглядит некрасиво с точки зрения классического программирования. Это различие, на самом деле, восходит к временам, когда носителем для программ были перфокарты и перфолента, которые нужно было менять вручную, если подпрограмма находилась на другой ленте или в другой пачке перфокарт.

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

Указатели, переменные, регистры


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

Если вы хоть раз видели программу на G-code для промышленного станка, вы, возможно, заметили, что в начале самой программы, а иногда в начале каждого фрагмента или подпрограммы, отвечающей за один инструмент или один элемент детали, есть длинная строка кодов, которые вроде бы ничего не делают. Это так называемая safe line. Она нужна, потому что станок помнит свое состояние. Например, содержимое какого-то регистра может сохраняться даже после выключения и включения станка, потому абсолютно всегда имеет смысл в явном виде устанавливать желаемое состояние перед совершением каких-то операций. Это напоминает то, как в web-разработке используются Reset.css и Normalize.css. Иначе, это правило для программистов звучит как никогда не предполагай, что станок находится в определенном состоянии, если ты его в это состояние не привел. Пренебрежение этим может стоить дорого, включая капитальный ремонт станка. При этом, наиболее надежной практикой считается именно приведение станка в искомое состояние, а не проверка, находится ли он в нем. Почему? Потому что приведение, как правило, делается одной безусловной командой, а проверка требует условного ветвления.

Практический пример. При использовании контроллера Haas, некоторые адреса доступны для чтения только по номеру ячейки памяти, тогда как для записи по буквенному псевдониму и по номеру. Скажем, чтобы установить скорость вращения шпинделя, достаточно выполнить код S<целое число>, запись IF [S EQ 200] (проверка если скорость шпинделя равна 200) работать не будет, нужно писать IF [#цифровой номер ячейки EQ 200]. Очевидно, что установить нужную скорость куда проще, чем проверить ее. Более того, я с большим трудом могу себе представить ситуацию, когда проверка была бы действительно нужна, за исключением всего одного случая, с которым мне пришлось столкнуться. Некоторые станки имеют в своем наборе инструментов вентилятор, который устанавливается в шпиндель, как обычный держатель фрез. Это нужно, чтобы сдувать охлаждающую жидкость и стружку с детали после окончания ее обработки. Работа вентилятора зависит от скорости вращения он складной, ему нужна определенная скорость, чтобы раскрыться от центробежной силы. Но станок имеет функцию изменения скорости вращения шпинделя, чтобы при отладке программы оператор мог на ходу переопределить скорость, заданную программой. Однако, если забыть отключить это изменение, вентилятор может или не раскрыться, или разлететься от слишком быстрого вращения. До того, как я начал работать в компании, этот вопрос никак не решался, считалось, что это ответственность оператора. Я же обратил на это внимание после первого происшествия и написал дополнение к программе для вентилятора, которое запускает вентилятор сразу после его установки в шпиндель, затем читает по нумерованному адресу (на счастье, документированному) значение реальной скорости вращения, делит его на устанавливаемую программой скорость и определяет, не различаются ли они больше чем на 1% (легкие вариации допускаются, хотя 1% это порог с запасом), и если различаются останавливает программу, включая индикатор ошибки и выдавая сообщение о том, что переопределение скорости следует отключить. Иронично, что тот же самый контроллер позволяет запретить переопределение некоторых параметров из программы (скорости движения стола, например), но не скорости вращения шпинделя. Почему? Так решил производитель. А моя задача сделать так, как нужно производству, несмотря на то, что думает производитель, не нарушая гарантию. Для типичного производственного программиста, который не связан с автоматизацией, подобное решение выходит за рамки его деятельности.

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

Приведение типов


Это одна из неприятных особенностей многих реализаций G-code и контроллеров. Глядя на параметр X10, логично предположить, что это целое число. Но, в зависимости от того, как контроллер работает и как настроен, машина может интерпретировать и как X10.0 и как X0.0010 в втором случае, это будет десять минимальных единиц инкремента для данного контроллера. (Что, в свою очередь, может быть и десять микрон и десять десятитысячных долей дюйма.) Чудовищно, правда? Студенты и начинающие операторы постоянно делают эту ошибку. При этом, это можно настроить в контроллере. Потому, для полной переносимости и независимости от настроек, десятичная точка должна быть в цифровых значениях координат абсолютно всегда.

Хуже становится, когда речь о параметрах, передаваемых вызываемой подпрограмме. Практический пример. Автоматический измеритель длины инструмента Renishaw, установленный на станке Haas, требует для запуска измерения одного инструмента код G65 P9023 A12. T1, где T1 номер инструмента (1, в данном случае). Но если вы хотите измерить сразу несколько инструментов, код будет G65 P9023 A22. I1. J2. K3. Тут уже параметры должны быть с точкой. Почему? Потому что когда вы пишете в T, этот адрес предназначен для хранения номера инструмента, потому на станке Haas он автоматически интерпретируется как целое число (мне неизвестны реализации, где это может быть дробное число, но я не могу этого исключить, например у одного инструмента могут быть разные режущие кромки, нумеруемые, как дробная часть его номера). А вот когда параметры передаются через регистры, хранящие локальный стек переменных общего назначения, точка нужа, потому что там может храниться что угодно. При этом, у тех же станков Haas есть две настройки, которые отвечают за изменение этого поведения. Одна касается ввода параметров в контроллер, а другая интерпретации некоторых именованных регистров использующихся для хранения координат.

Об обучении


Программированию станков с ЧПУ учат очень разными путями и с разными задачами. В одном случае, речь просто о том, чтобы научить пользоваться CAD/CAM, чтобы программист был в состоянии превратить модель (чертёж) в код, исполняемый на том или ином станке, изготавливающий деталь по модели. Это напоминает процесс обучения программированию общего назначения в ВУЗе, где вопросы исполнения кода, аппаратной архитектуры и написания кода на Ассемблере рассматриваются очень поверхностно. В других, заметно более редких случаях, процесс более всего напоминает обучение системному программированию, а примеры исполнения кода на конкретной архитектуре входят в него, как неотъемлемая часть. Поскольку я когда-то учился цифровой электронике, и программирование железа на низком уровне было частью этого, пусть и в довольно скромном объеме, второй вариант лично мне как-то ближе, и именно так я старался преподавать это сам, когда у меня была такая возможность.

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

Проблема логических языков программирования

09.01.2021 22:17:27 | Автор: admin
Некоторое время назад я писал про Интернациональное программирование на естественных языках, в которой попытался представить достойную цель для абстрактного язык программирования, попробовав примерить на него роль связующего звена между миром программистов с компьютерами и не программистов.

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

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

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

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

Так почему этого так и не случилось? В чем проблема Пролога, да и любой системы / языка программирования, назначение которых анализировать факты и искать ответы на вопросы?

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



Подходы к написания программ


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

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

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

Именно к декларативному стилю относится язык Пролог, да и все остальные логические языки программирования. К декларативному стилю написания программ следует относить и язык структурированных запросов (SQL).

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

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

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

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

Проблема поиска решений


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

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

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

Масштабирование производительности


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

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

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

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

Поиск обобщенного решения


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

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

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

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

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

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

Область не решаемых задач


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

Зачем нам понадобился еще один язык программирования

26.01.2021 10:19:42 | Автор: admin

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

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

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

Текущее положение дел

Предположим, что вы хотите разработать небольшое приложение по учету ИТ-активов, рассмотрению каких-либо заявок, простому финансовому учету, бюджетированию, обычному складскому учету или любое другое бизнес-приложение. Данные в этом приложении должны храниться в РСУБД - для надежности и скорости работы. Пользователю необходимо предоставить простой и удобный веб-интерфейс.

Для реализации этой задачи будем использовать, например, Java-stack технологий (хотя ничего принципиально не изменится, если это будет тот же Python). Посмотрим, знание каких языков нам для этого понадобится :

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

  • XML. Мало кто считает его языком, тем не менее, даже с точки зрения названия (Extensible Markup Language) он им является. Хотя конечно же, это не язык программирования, а язык разметки. Нам он так или иначе понадобится, поскольку именно его используют для конфигурирования множество библиотек (например, Spring, log4j или тот же Apache Tomcat). Кроме того, при разработке фронтенда придется использовать HTML, который, можно сказать, является разновидностью XML.

  • SQL. Теоретически можно обойтись без него, и использовать какую-нибудь ORM технологию (например, Hibernate). Однако, при обработке хоть сколько-нибудь большого объема информации все равно придется переходить или на плоский SQL, или на псевдо-SQL (вроде HQL).

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

  • CSS. Тоже язык разметки со своим специфическим синтаксисом (хоть и довольно простым).

Проблемы

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

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

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

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

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

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

Решение

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

Во-первых, это будет проще для изучения новичку.

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

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

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

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

Выбрав решение с ключевыми словами, нужно было еще определиться ,в каком регистре они должны быть. Для SQL на эту тему ведется много баталий. Для первоначальной реализации мы все-таки выбрали схему с большими буквами для ключевых слов, чтобы отделять их от идентификаторов при работе без IDE. Именно такая схема была в первоначальной логике SQL и того же FoxPro, и лишь потом начали практиковаться разные виды регистров. В следующих версиях lsFusion мы также планируем разрешить использовать разный регистр букв для ключевых слов.

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

Язык lsFusion получился достаточно похожим на SQL, с той лишь поправкой, что SQL описывает таблицы и запросы, а lsFusion - функциональную логику. Пример того, как логика, описанная в SQL, соответствует такой же логике в lsFusion описана в статье Функциональная СУБД. Примеры остального синтаксиса можно увидеть вот здесь : императивная логика, GUI. Более подробное описание языка можно найти в одних из первых трех статей блога (1, 2, 3).

Надеюсь, что у читателя статьи создалось представление, почему мы решили использовать свой собственный язык, а не создавать очередной Java/Python/Ruby-framework.

Подробнее..

Языку программирования Python исполнилось 30 лет

23.02.2021 12:21:01 | Автор: admin


Время идет быстро, и Python, одному из самых популярных языков программирования современности, исполнилось 30 лет. Впервые о нем стало известно в конце февраля 1991 года, когда Гвидо ван Россум опубликовал первый выпуск Python в группе alt.sources.

Работа над языком велась два года, изначальной задачей было создание скриптового языка для работы с операционной системой Amoeba. Язык этот должен был быть более высокого уровня, чем Си, плюс представлял бы удобный доступ к системным вызовам операционной системы.

В первой версии языка появилась поддержка классов с наследованием, обработка исключений, плюс система модулей и базовые типы list, dict и str. Такой инструмент, как реализация модулей и исключений был заимствован из языка Modula-3. Стиль кодирования на основе отступов попал в Python из языка АBC, в разработке которого автор тоже принимал непосредственное участие.

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

  • Заимствование полезных идей из других проектов.
  • Стремление к простоте, но без чрезмерного упрощения (принцип Эйншейна Всё должно быть изложено так просто, как только возможно, но не проще).
  • Следование философии UNIX, в соответствии с которой программы реализуют какую-то одну функциональность, но делают это хорошо.
  • Не слишком заботиться о производительности, оптимизации можно добавлять по мере необходимости, когда они понадобятся.
  • Не пытаться бороться с сложившимися вещами, а плыть по течению.
  • Избегать перфекционизма, обычно хватает и уровня достаточно хорошо.
  • Иногда можно срезать углы, особенно, если что-то можно сделать позднее.

Есть и другие принципы, которыми руководствовался Гвидо ван Россум при разработке Python:

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


Сейчас Python находится на четвертом месте по востребованности среди разработчиков. В этом году довольно неожиданно доля Python на рынке ЯП сократилась. Причины пока не очень ясны возможно, ниша Data Science заполнена, может, повлияло еще что-то. Но как бы там ни было, снижение не существенное, а самому языку предстоит работать на благо человечества еще многие годы, скорее всего десятки лет.

Подробнее..

Перевод CSS строго типизированный язык программирования

14.05.2021 16:12:54 | Автор: admin

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

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

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

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

TypeScript

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

Расширения типа TypeScript были созданы чтобы помочь с этим. Думайте о нём как о плагине для JavaScript который прививает сильную типизацию языку. Это помогает программистам ориентироваться в сложных настройках. Примером этого может служить одностраничное приложение с большим объемом данных, используемое для интернет-магазина. TypeScript в настоящее время очень популярен в индустрии веб-разработки, и многие новые проекты по умолчанию используют TypeScript.

Компиляция

Компиляция это период времени, когда язык программирования переводится в машинный код. Это предвестник времени выполнения периода времени, за который компьютер выполняет машинный код.

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

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

Пример ошибки TypeScript в VS CodeПример ошибки TypeScript в VS Code

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

  • устройства, на котором браузер запущен

  • другой работы, которую выполняет браузер

  • другой работы, которую выполняют другие программы устройства

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

CSS

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

Типы в CSS

Список типов в CSS довольно исчерпывающий:

Текстовые типы:

  • Глобально объявленные ключевые слова:

    • initial

    • inherit

    • unset

    • revert

  • Пользовательские идентификаторы, которые используются для объявления таких атрибутов как grid-area

  • Строки типа "hello"

  • URL-ы вроде https://css-tricks.com/

  • Пунктирные идентификаторы (--) служащие для обозначения пользовательских свойств

Числовые типы:

  • Целые числа, которые являются десятичными числами 0-9

  • Дробные числа, такие как 3.14

  • Проценты, такие как 25%

  • Размеры, числа с присоединенными единицами измерения, такие как 100px или 3s

  • Отношения, такие как 16/9

  • Flex и Grid переменные для расчета длинны

Количественные типы:

  • Длины:

    • Абсолютные, такие как пиксели и сантиметры

    • Относительные, такие как ems или высота зоны видимости

    • Продолжительность, например 200ms

  • Время, например 250ms

  • Углы, например 15deg

  • Частота, такая как 16Hz

  • Разрешение, например 96dpi

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

Типы обозначения цветов:

  • Ключевые слова:

    • Имена цветов, напримерpapayawhip

    • transparent

    • currentColor

  • RGB цвета

    • Шестнадцатиричная запись, например#FF8764

    • RGB/RGBa-записи, такие как rgba(105, 221, 174, 0.5)

  • HSL/HSLA цвета, напримерhsl(287, 76%, 50%)

  • Системные цвета, напримерButtonText

Типы обозначения изображений:

  • Изображения, URL которых ведет на файл или градиент.

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

  • Список цветов и их продолжительности в линейном градиенте.

  • Длины цветов в процентах, используемые для интерполяции цветов в градиенте.

  • Обозначения граней (используются ключевые слова circleилиellipse) в радиальном градиенте.

Типы для позиционирования в двумерном пространстве:

  • Ключевые слова

    • top

    • right

    • bottom

    • left

    • center

  • Обозначения длинны в процентах, например 25%

Программирование в CSS

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

В CSS есть функции: вычисление, условная логика, алгоритмические выражения, поведение на основе состояния. Так же в CSS можно задавать пользовательские свойства динамически обновляемые CSS-переменные. Черт возьми, вы можете даже решить fuzzbuzz-задачу на CSS.

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

Ловим ошибки

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

Доказательство

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

Пример 1: Простое объявление свойств/значений

В этом примере браузер не понимает объявления стиля границы potato в свойстве border-style. Обратите внимание, что другие объявления свойств/значений селектора класса .banner выполняются браузером и отображаются, даже если стиль границы имеет несоответствие типов. Это пример того, насколько устойчив CSS.

Свойство border-style ожидает один из следующих текстовых типов

  • ключевые слова из глобальной области видимости,

  • пунктирный отступ, предшествующий пользовательской переменной.

Если мы для border-style используем допустимое значение dotted, браузер отобразит границу!

Пример 2: вычисление

Функция calc() в CSS принимает два аргумента, оператор и возвращает результат вычисления. Если один из аргументов использует недопустимый тип, вычисления не будет.

В этом примере свойство font-size селектора p ожидает значение с числовым типом измерения (например, 1.5rem). Однако функция вычисления выдает недопустимое значение типа для свойства font-size. Это связано с тем, что второй аргумент в функции calc () является строкой (2rem), а не числовым типом измерения.

Из-за этого размер шрифта абзаца возвращается к следующему наиболее подходящему родительскому узлу - font-size 1,5 rem, объявленному в элементе body.

Пример 3: переопределение настраиваемого свойства

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

В селекторе: root этого примера я установил настраиваемое свойство --color-cyan со значением #953FE3. Затем в классе .square я обновил значение настраиваемого свойства --color-cyan, сделав его верхним. Хотя top является допустимым типизированным значением, это не тот тип, который воспринимает background-color.

Обратите внимание, что обновленное настраиваемое свойство имеет область действия .square и не влияет на другие способы использования, например на правую границу на фразе Dont play to type.. А если вы удалите переопределенное настраиваемое свойство из .square, вы снова увидите голубой цвет фона.

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

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

Инструменты

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

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

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

вывод терминала stylelintвывод терминала stylelint

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

Версия для разработчиков FirefoxВерсия для разработчиков Firefox

Подведем итог

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

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

Подробнее..

Небольшой язык программирования и его разработка

17.05.2021 00:12:32 | Автор: admin

Как всё началось

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

Переходя в следующий курс я начал активно изучать всё что касалось ОСи, но толком никуда не продвинулся. Тогда и родилась у меня идея создать свой ЯП.

Времени было мало, а делать было что-то нужно и я в свободное от удалёнки (с серой ЗП) что-то писал.

Язык я решил назвать The Gorge.

Часть первая. Как работает язык и где его найти

Было принято решение разместить язык на платформе гитхаб и создать новый профиль.
На тот момент я имел в распоряжении старый подаренный мне акк, но в последствии всё-таки создал свой и сейчас его можно найти так: (просто допишите сайт)/pcPowerJG/natural-network.

В папке src в файле lib.rs мы можем увидеть чудо, язык написан почти полностью на раст (почему почти? к сожалению в далёкие времена 2019 года раст не давал открыть файл на моей любимой манжаре и пришлось открывать его через Си).

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

words.push("object".to_string());//1 // используется для создания объекта, который хранит значения в памятиwords.push("if".to_string());//2// оператор условия, нужен для сравнения ДВУХ параметровwords.push("exit_()".to_string());//3//выход из приложенияwords.push("func".to_string());     //4//инициализация функции words.push("print".to_string());//5 // вывод на консольwords.push("remove".to_string());//6 //удалениеwords.push("array".to_string());//7 // создание массиваwords.push("struct".to_string()); //8 // создание структурыwords.push("end".to_string());//9//end operationwords.push("end_func".to_string()); // 10 // конец функцииwords.push("return".to_string()); //  11words.push("!eq".to_string());//  12words.push(">".to_string());  //  13words.push("<".to_string());  //  14words.push("loop".to_string());// 15words.push("end_loop".to_string());// 16words.push("_".to_string()); // 17 // просто в качестве НЕ ключевого словаwords.push("break".to_string()); // 18words.push("true".to_string()); // 19words.push("false".to_string()); // 20

Как мы видим у слов есть определённая нумерация (и не с нуля. это важно).

Следующая главная функция это функция старт.

pub fn start_(&mut self, text: String) -> u8

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

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

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

Привожу код.

let mut temp_values: String = String::new();//ВРЕМЕННЕ ПЕРЕМЕННЕlet mut temp_name: String = String::new();        //...let mut temp_buffer: String = String::new();//...let mut func_text: String = String::new();let mut last_op: [usize; 3] = [0; 3]; // храним три последних действия// ----------------------------------------------let mut if_count: usize = 0;let mut if_result: bool = true; // ответ на условиеlet mut struct_flag: bool = false; // это структура или условиеlet mut function_inactive_flag: bool = false; // если функция не активнаlet mut loop_flag: bool = false; // попали на циклlet mut index_loop: usize = 0; // количество циклов (для цикла в цикле)

Так же смотря код можно заметить много флагов, они нужны как-раз таки для нормального функционирования.

Всего наш код делиться на три блока

if ch == ' ' || ch == '\t' {  //...................} else if ch == '\n' {  //...................} else if ch == '=' {  //...................}} else {  temp_values.push(ch);}

В первом мы выполняем действия при разделении кода (то есть сразу смотрим что за ключевое слово или переменная и записываем в карту действий). Второй выполняем после символа окончания строки, тут мы выполняем действия. И третий выполняется при присваивании.
Блок else нужен только для записи переменной.

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

if function_inactive_flag {  // ...}if loop_flag {  // ...}match temp_values.trim() {  // ...}

Мы видим в блоке ещё три блока. Первый блок связан с функциями, в нём происходит запись их в переменную (мы храним имя функции как переменную).

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

Всё остальное же идёт согласно законам логики построения программы.

К примеру: a = b + c
Преобразуется в: last_op[0] = 1 last_op[1] = 17
И выполниться: в функции math_work .

Математика

Функция преобразования переменных в значения:

fn math_work(&self, text: String) -> String {  let text: String = Words::trim(text.clone());  let mut result_string: String = String::new();  let mut temp_string: String = String::new();  for ch in text.chars() {    match ch {      '+' | '-' | '/' | '*' | '(' | ')' | '&' | '|' | '!' | '=' | '<' | '>' => {        if Words::is_digit(temp_string.clone()) {          result_string += temp_string.clone().as_str();        } else {          result_string += self.search_var(temp_string).0.clone().as_str();        }        result_string.push(ch.clone());        temp_string = String::new();      },      _ => {        temp_string.push(ch.clone());      },    }  }   let (value, type_, _temp) = self.search_var(temp_string.clone());  if _temp {    result_string += value.as_str();  } else {    result_string += temp_string.clone().as_str();  } result_string}

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

Передаём всё в следующую функцию:

fn eval(str_: Vec<char>) -> f32 {  let mut i: usize = 0;  Words::expr(str_, &mut i)}
Вся математическая магия
fn eval(str_: Vec<char>) -> f32 {  let mut i: usize = 0;  Words::expr(str_, &mut i)}fn plus_one(u: &mut usize) {  *u += 1;}fn number(ch_: Vec<char>, idx: &mut usize) -> f32 {  let mut result: f32 = 0.0;  //float result = 0.0;  let mut div: f32 = 10.0;  let mut sign: f32 = 1.0;  if ch_[*idx] == '-'{    sign = -1.0;    *idx += 1;  }  while *idx < ch_.len() &&  match ch_[*idx] {    '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { true },    _ => { false }  }  {    result = result * 10.0 + (f32::from_str(&ch_[*idx].to_string()).expect("не удалось форматировать строку"));    *idx += 1;  }  if *idx < ch_.len() && (ch_[*idx] == '.'){    *idx += 1;            while *idx < ch_.len() &&    match ch_[*idx] {      '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { true },      _ => { false }    }     {      result = result + (f32::from_str(&ch_[*idx].to_string()).expect("не удалось форматировать строку")) / div;      div *= 10.0;      *idx += 1;    }  }  sign * result}fn expr(ch_: Vec<char>, idx: &mut usize) -> f32 {  let mut result: f32 = Words::term(ch_.clone(), idx);      while *idx < ch_.len() && (ch_[*idx] == '+' || ch_[*idx] == '-') {    match ch_[*idx] {      '+' => {        *idx += 1;        result += Words::term(ch_.clone(), idx);      },      '-' => {        *idx += 1;            result -= Words::term(ch_.clone(), idx);      },      _ => {},    }   } result}fn term(ch_: Vec<char>, idx: &mut usize) -> f32 {  let mut result: f32 = Words::factor(ch_.clone(), idx);  let mut div: f32 = 0.0;  while *idx < ch_.len() && (ch_[*idx] == '*' || ch_[*idx] == '/') {    match ch_[*idx] {      '*' => {        *idx += 1;        result *= Words::factor(ch_.clone(), idx);      },      '/' => {        *idx += 1;            div = Words::factor(ch_.clone(), idx);            if (div != 0.0) {          result /= div;        } else {          panic!("Division by zero!\n");                            }      },      _ => {},    }  } result}fn factor(ch_: Vec<char>, idx: &mut usize) -> f32 {  let mut result: f32 = 0.0;  let mut sign: f32 = 1.0;  if (ch_[*idx] == '-') {    sign = -1.0;    *idx += 1;  }  if (ch_[*idx] == '(') {    *idx += 1;    result = Words::expr(ch_.clone(), idx);    if (ch_[*idx] != ')') {      panic!("Brackets unbalanced!\n");    }    *idx += 1;  } else { result = Words::number(ch_, idx); }  sign * result}

Переменные

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

Все переменные занимают место сразу в двух массивах, массив имён и типов:

object_buffer: Vec<(String, usize)>

Массив значений:

value_buffer: Vec<String>

Для добавления новой переменной используется функция add_vars:

fn add_vars(&mut self, vars_name: String, mut vars_value: String, vars_type: usize) {  //object_buffer: Vec<(String, usize)>  //value_buffer: Vec<String>  if vars_value.clone().split('\"').collect::<Vec<&str>>().len() > 1 {    vars_value = vars_value.split('\"').collect::<Vec<&str>>()[1].to_string();  } else {    vars_value = vars_value.clone().trim().to_string();  }  self.object_buffer.push((vars_name, vars_type));  self.value_buffer.push(vars_value);}

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

Для удаления переменной:

fn remove_vars(&mut self, vars_name: String) {  for i in 0..self.object_buffer.len() {    if self.object_buffer[i].0.clone() == vars_name {      self.object_buffer.remove(i);      self.value_buffer.remove(i);      return;    }  }}

Запись значения и поиск:

fn set_value(&mut self, vars_name: String, mut vars_value: String) {  for i in 0..self.object_buffer.len() {    if self.object_buffer[i].0 == vars_name {      if vars_value.clone().split('\"').collect::<Vec<&str>>().len() > 1 {        vars_value = vars_value.split('\"').collect::<Vec<&str>>()[1].to_string();      } else {        vars_value = vars_value.clone().trim().to_string();      }      self.value_buffer[i] = vars_value.clone();      return;    }  }}pub fn search_var(&self, vars_name: String) -> (String, usize, bool) {  for i in 0..self.object_buffer.len() {    if self.object_buffer[i].0 == vars_name {      let value: String = self.value_buffer[i].clone();      let type_: usize = self.object_buffer[i].1.clone();      return (value, type_, true);     }  }  (String::new(), 0, false)}

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

import("/lib.so")extern_func("lib.so", func_name)  extern_func("lib.so", func_name, arg1, arg2)close_import("lib.so")

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

Что нужно исправить и добавить?

  1. Исправить функцию обработки условий (не заходите туда, я серьезно);

  2. Зачем нам всё хранить в тексте? Исправить на байты;

  3. Импортировать поддержу сишных библиотек из старой версии языка

Спасибо за внимание.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    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