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

Здесь вы не найдёте устаревшего посыла «функциональное программирование спасёт мир!»; мой список состоит из куда менее популярных наименований. Готов поспорить, многие из читателей вообще не слышали о большинстве языков и парадигм, о которых пойдёт речь, так что надеюсь, вам будет так же интересно с ними разбираться, как и мне.

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



Конкурентность по умолчанию



Примеры языков: ANI, Plaid

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

Например, представьте, что вы написали три строки кода — А, В и С.

A;
B;
C;

В большинстве языков сначала будет выполняться А, затем В, а потом уже С. Но в ANI и ему подобных A, B и C будут выполнены одновременно!

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

Давайте разберём пример из ANI. Как говорится в туториале, программы, написанные на ANI, состоят из каналов (pipes) и задвижек (latches), с помощью которых происходит управление потоками данных. В этом необычном синтаксисе не так просто разобраться, а сам язык кажется мёртвым, но концепты он предлагает весьма интересные.

Вот пример реализации «Hello, world!» на ANI:

"Hello, World!" ->std.out

Используя принятые в ANI термины, мы отправляем объект «Hello, world!» (текст) в поток std.out. А что если отправить еще какой-нибудь текст в std.out?

"Hello, World!" ->std.out
"Goodbye, World!" ->std.out

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

s = [string\];
"Hello, World!" ->s;
\s ->std.out;

Первая строка объявляет задвижка (что-то вроде переменной) под названием s, в которой содержится текст; вторая строка отправляет в s текст «Hello, world!»; третья — «открывает» задвижку на s и отправляет содержимое в std.out. Здесь вы можете наблюдать, как подспудно происходит упорядочивание в ANI: так как каждая строка зависит от предыдущей, строки кода будут выполняться в той последовательности, в которой они написаны.

Язык Plaid также, согласно заверениям создателей, поддерживает конкурентность по умолчанию, однако использует при этом модель разрешений (подробнее в этой статье) для настройки контроля потока. Plaid также экспериментирует и с другими интересными концепциями, например, программирование, ориентированное на состояние типа, где на первый план выходят изменения состояния: объекты определяются не как классы, а как серии состояний и переходов, которые может отслеживать компилятор. Этот подход представляется мне занятным — время в нём определяется как языковой конструкт первого класса. Rich Hickey писал об этом в своей статье Are we there yet.

Многоядерность сейчас на подъёме, и добиться конкурентности до сих пор сложнее, чем хотелось бы. ANI и Plaid предлагают нетривиальный подход к проблеме, который мог бы улучшить производительность в разы. Вопрос только в том, сделает ли принцип «параллелизм по умолчанию» управление конкурентностью проще.

Зависимые типы



Примеры языков: Idris, Agda, Coq

Вы, наверное, привыкли к системам, представленным в таких языках, как C и Java, где компилятор может определять разновидность переменной — целое число, список или текст. Но что если он мог бы определять переменную как «положительное целое число», «список из двух пунктов» или «текст, который является палиндромом»?

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

Вот как объявить Vector, который содержит значения 1, 2, 3 в библиотеке shapeless:

val l1 = 1 :#: 2 :#: 3 :#: VNil

Таким образом, создаётся переменная 11, в сигнатуре которой указано не только то, что это Vector, который содержит Ints, но и то, что длина вектора — 3. Компилятор может использовать эту информацию, что отлавливать ошибки. Давайте применим метод vAdd к вектору, чтобы произвести попарное сложение двух векторов:

val l1 = 1 :#: 2 :#: 3 :#: VNil
val l2 = 1 :#: 2 :#: 3 :#: VNil
 
val l3 = l1 vAdd l2
 
// Result: l3 = 2 :#: 4 :#: 6 :#: VNil

В приведённом примере всё работает нормально, так как система знает: у обоих векторов длина 3. Однако если бы мы попытались применить vAdd к векторам разной длины, то получили бы ошибку уже на стадии компиляции, ещё до выполнения!

val l1 = 1 :#: 2 :#: 3 :#: VNil
val l2 = 1 :#: 2 :#: VNil
 
val l3 = l1 vAdd l2
 
// Result: a *compile* error because you can't pairwise add vectors 
// of different lengths!

Shapeless — отличная библиотека, но, насколько мне известно, ещё немного сыроватая: поддерживает только небольшой набор зависимых типов, даёт тяжеловесный код и сигнатуры. Idris же делает типы объектом первого класса в языке программирования, вследствие чего система зависимых типов получается аккуратнее и мощнее. Для детального сравнения почитайте Scala vs Idris: Dependent Types, Now and in the Future.

Формальные методы верификации существуют уже давно, но зачастую оказываются слишком громоздкими, чтобы программисты общего профиля находили их полезными. Зависимые типы в языках вроде Idris, а в будущем, возможно, и Scala, могут предоставить более компактные и практичные альтернативы, которые существенно расширят возможности системы в том, что касается выявления ошибок. Конечно, никакая система зависимых типов не способна отловить все ошибки, которые возникают из-за ограничений, накладываемых проблемой остановки. Однако при разумном применении зависимые типы могут стать значительным скачком для статических систем типов.

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



Примеры языков: Forth, cat, joy

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

Звучит как-то туманно, поэтому давайте возьмём простой пример из cat:

2 3 +

Здесь мы добавляем в стек два числа, а затем вызываем функцию +, которая выбрасывает оба из стека и добавляет туда их сумму. The output этого кода — 5. Теперь рассмотрим пример чуть поинтереснее:

def foo {
  10 <
  [ 0 ]
  [ 42 ]
  if
}
 
20
foo

Разберём каждую строку:

  1. Сначала объявим функцию foo. Обратите внимание на то, что в cat параметры ввода функций не указываются: все параметры считываются со стека.
  2. foo вызывает функцию <, которая добавляет в стек первое число, сравнивает его с 10 и добавляет значение True или False.
  3. Далее мы добавляем в стек значения 0 и 42, причём заключаем их в скобки, чтобы они добавились без прохождения проверки. Причина в том, что они будут использоваться как ветви then и else соответственно при вызове функции «если» в следующей строке.
  4. Функция if выбрасывает из стека три вещи: логическое условие, ветвь then и ветвь else.
  5. Наконец, мы добавляем число 20 в стек и вызываем функцию foo.
  6. В конечном итоге мы получаем число 42.

За более подробным введением вы можете обратиться к The Joy of Concatenative Languages.

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

Декларативное программирование



Примеры языков: Prolog, SQL

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

Например, если вы с нуля создаете алгоритм сортировки на С, то вам нужно прописать инструкции для сортировки слиянием, в которых будет пошагово расписано, как рекурсивно разделить все данные на две части, а потом объединить в соответствующем сортировке порядке; вот пример. Если вы сортируете числа в декларативном языке вроде Prolog, вместо всего этого вы прописываете желаемый результат: «Я хочу получить список из тех же составляющих, но число на позиции i должно быть меньше или равно числу на позиции i+1». Сравните решение на С, о котором шла речь выше, с этим кодом из Prolog:

sort_list(Input, Output) :-
  permutation(Input, Output),
  check_order(Output).
  
check_order([]).
check_order([Head]).
check_order([First, Second | Tail]) :-
  First =< Second,
  check_order([Second | Tail]).

Если вы когда-нибудь пользовались SQL, то у вас есть опыт декларативного программирования, пусть даже вы, возможно, об этом и не догадывались. Когда вы отправляете запрос типа select X from Y where Z, вы описываете типа данных, которые хотите получить; как выполнять этот запрос решает сам движок базы данных. В большинстве баз данных вы можете применить команду explain, чтобы посмотреть на схему выполнения и получить представление, как все происходило за кулисами.

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

sudoku(Puzzle, Solution) :-
  Solution = Puzzle,
  
  Puzzle = [S11, S12, S13, S14,
            S21, S22, S23, S24,
            S31, S32, S33, S34,
            S41, S42, S43, S44],
  
  fd_domain(Solution, 1, 4),
  
  Row1 = [S11, S12, S13, S14],
  Row2 = [S21, S22, S23, S24],
  Row3 = [S31, S32, S33, S34],
  Row4 = [S41, S42, S43, S44],      
  
  Col1 = [S11, S21, S31, S41],
  Col2 = [S12, S22, S32, S42],
  Col3 = [S13, S23, S33, S43],
  Col4 = [S14, S24, S34, S44],      
  
  Square1 = [S11, S12, S21, S22],
  Square2 = [S13, S14, S23, S24],
  Square3 = [S31, S32, S41, S42],
  Square4 = [S33, S34, S43, S44],      
  
  valid([Row1, Row2, Row3, Row4,
         Col1, Col2, Col3, Col4,
         Square1, Square2, Square3, Square4]).
 
valid([]).
valid([Head | Tail]) :- fd_all_different(Head), valid(Tail).

Вот как запустить игру, описанную выше:

| ?- sudoku([_, _, 2, 3,
             _, _, _, _,
             _, _, _, _,
             3, 4, _, _],
             Solution).
 
 
S = [4,1,2,3,2,3,4,1,1,2,3,4,3,4,1,2]

К сожалению, у этого метода есть и недостатки: декларативные языки часто сталкиваются с проблемами производительности. Незамысловатый алгоритм сортировки, который мы приводили выше, скорее всего даст O(n!); в игре судоку поиск производится силовыми методами, большинству разработчиков приходится включать в подсистему дополнительные намёки и индексы, чтобы избежать затратных и неэффективных планов при выполнении SQL запросов.

Символическое программирование



Пример языка: Aurora

Язык Aurora — образчик символического программирования. Код, который вы пишете на нём и ему подобных, помимо простого текста может включать в себя изображения, математические уравнения, графики и так далее. Это позволяет вам описывать и работать с самыми разнообразными данными в их оригинальном формате, вместо того, чтобы переводить всё в текст. Кроме того, Aurora — в высшей степени интерактивный язык, который сразу же показывает вам результат после каждой строки кода, прямо как REPL на стероидах.

Язык Aurora был создан Chris Granger, авторству которого принадлежит также Light Table IDE. Он говорит о мотивации, которая побудила его изобрести Aurora, в своём посте Toward a better programming. Он преследовал такие цели, как сделать программирование более наглядным, прямым и свести случайное усложнение к минимуму. Если хотите узнать больше, обязательно ознакомьтесь с материалами от Bret VictorInventing on Principle, Media for Thinking the Unthinkable и Learnable Programming.

Программирование, основанное на знаниях



Пример языка: Wolfram

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

Я полагаю, что из всех существующих языков у Wolfram самая обширная «стандартная библиотека» и набор данных. Также меня очень вдохновляет мысль, что связь с Интернет-сообществом становится неотъемлемой частью процесса написания кода: это напоминает IDE, где функция автозаполнения осуществляет поиск в Google. Интересно будет проверить, правда ли символическая модель программирования настолько гибкий, как утверждает Wolfram, и может ли она должным образом воспользоваться всеми этими данными.
Поделиться с друзьями
-->

Комментарии (48)


  1. mayorovp
    03.05.2017 16:43
    +2

    Сравните решение на С, о котором шла речь выше, с этим кодом из Prolog:

    … а теперь сравните время выполнения :-)


    Декларативное программирование — замечательная парадигма, но не надо объяснять ее на основе языка Prolog.


    1. MacIn
      03.05.2017 18:56
      +4

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


      1. Idot
        03.05.2017 22:01
        +1

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

        А если я не знаю решение, но очень хочу найти, то как быть?


        1. fireSparrow
          03.05.2017 23:29
          +1

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


          1. Idot
            04.05.2017 09:21

            Можно ли пример кода, как выглядит "задача коммивояжера" на ПроЛоге?


            1. fireSparrow
              04.05.2017 09:55

              Сам я на прологе не пишу, но беглое гугление по запросу «prolog задача коммивояжера» выдаёт множество ссылок.

              Вот один из найденных примеров:

              Пример
              /**************************************************************
              Коммивояжёр. Поиск в глубину.
              **************************************************************/
              domains ss = string*
              database - путь
              путь(string,integer,string)
              оценка(ss,integer)
              predicates
              оптим_маршрут(ss,integer)
              nondeterm маршруты(string,integer,ss,integer)
              nondeterm вариант(string,integer,string,ss,integer)
              nondeterm участок(string,integer,string)
              уник(ss,ss,integer)
              удалить(string,ss,ss)
              принадлеж(string,ss)
              goal
              оптим_маршрут(Маршрут,Длина).
              clauses
              оптим_маршрут(Маршрут,Длина):-
              путь(Начало,_,_),!,
              findall(Город,путь(Город,_,_),Города),
              уник(Города,_,Количество),
              маршруты(Начало,Количество,Маршрут,Длина),!.

              маршруты(Начало,Количество,_,_):-
              вариант(Начало,Количество,Начало,[Начало],0),fail.
              маршруты(_,_,Маршрут,Длина):-
              оценка(Маршрут,Длина).

              вариант(Начало,0,От,Маршрут,Длина):-
              участок(От,Участок,Начало),
              Длина1=Длина+Участок,
              not(оценка(_,_)),
              assert(оценка([Начало|Маршрут],Длина1)).
              вариант(Начало,0,От,Маршрут,Длина):-
              участок(От,Участок,Начало),
              Длина1=Длина+Участок,
              оценка(_,Длина0),Длина1<Длина0,
              retract(оценка(_,Длина0)),
              assert(оценка([Начало|Маршрут],Длина1)).
              вариант(Начало,К,От,Маршрут,Длина):-К>0,
              участок(От,Участок,До),
              not(принадлеж(До,Маршрут)),
              Длина1=Участок+Длина, К1=К-1,
              вариант(Начало,К1,До,[До|Маршрут],Длина1).

              уник([Э|Х],[Э|Х2],Число):-удалить(Э,Х,Х1),!,
              уник(Х1,Х2,Число1),Число = Число1+1.
              уник([],[],0).
              удалить(Э,[Э|Х],Х1):-!,удалить(Э,Х,Х1).
              удалить(Э,[А|Х],[А|Х1]):-!,удалить(Э,Х,Х1).
              удалить(_,[],[]).

              участок(От,Длин,До):-путь(От,Длин,До).
              участок(От,Длин,До):-путь(До,Длин,От).

              принадлеж(Город,[Город|_]):-!.
              принадлеж(Город,[_|Города]):-принадлеж(Город,Города).

              путь("Курск",12,"Орёл").
              путь("Курск",120,"Магадан").
              путь("Курск",40,"Азов").
              путь("Магадан",110,"Орёл").
              путь("Магадан",52,"Колыма").
              путь("Орёл",32,"Азов").
              путь("Орёл",105,"Колыма").
              путь("Азов",112,"Колыма").


              1. mayorovp
                04.05.2017 10:15
                +3

                Судя по постоянным использованиям retract, assert и оператора ! — вы привели императивный код, но никак не простое описание закономерностей, которым должно удовлетворять решение.


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


                1. Danov
                  06.05.2017 12:08

                  Приведенный пример перегруженный. Можно проще написать. Нужна пара строчек permutation
                  permute([], []).
                  permute([X|Rest], L) :- permute(Rest, L1).
                  для генерации вариантов, затем описание графа дорого в виде списка
                  table([way(1,2,10), way(2,3,30),....]).
                  и далее основное правило для поиска минимума.
                  К сожалению, под рукой нет Prolog. Попробовал онлайн — не получилось с ходу разобраться.

                  PS: преимущество пролога как раз в скорости написания кода, а недостаток — в скорости перебора. Но были версии движка со свтроенной оптимизацией перебора.


        1. vanxant
          04.05.2017 02:06
          +2

          Программа на прологе содержит три основных секции:
          1. Секцию фактов, оно же «дано», по сути константы/база данных. «Коля — мальчик», например.
          2. Секцию правил вывода, в форме «если верно А и Б, то верно В». Скажем, «Оля любит мальчиков».
          3. Секцию целей. Что, собственно, нужно найти. Условное «Г», которое нужно доказать или опровергнуть, но не вообще, а для конкретно заданных значений А, Б и т.д. из пункта 1. «Любит ли Оля Колю?»
          Все вот эти А, Б, В, Г и т.д. по-умному называются предикатами, т.е. переменными или функциями, возвращающими тип bool (т.е. истина или ложь). А сам язык пролог по сути занимается доказыванием математических теорем. Есть такой алгоритм бэктрекинга, вот собственно его рантайм пролога и исполняет.


          1. vanxant
            04.05.2017 03:37
            +4

            На практике получается вот что:
            — Первая секция («фактов») тривиальна;
            — Третья («целей»), в общем, тоже; это или конструкция goal, или просто восклицательный знак после выражения, типа «приехали».
            А вот секция правил вывода — это чистейший вынос мозга. Ну вот представьте, что у вас есть «дано» в шахматах (начальная расстановка фигур) и условия «надо» (правила мата). Только вам нужно придумать не алгоритм выигрывания, а правила, по которым должны ходить ваши фигуры — так, чтобы в итоге выиграть. «Если ты ладья, слева-сзади твой ферзь, а справа чужая пешка, то...» И эти правила, в первом приближении, работают одновременно (в прологе есть порядок выполнения, но в первом приближении так).
            Причем это не только для программистов вынос мозга, для математиков тоже. В отличие от теорем, здесь есть побочные эффекты, ну там ввод-вывод, запрос даты-времени и вот это всё. В этом ключевое отличие от модной функциональщины.
            В итоге имеем красивую идею с отлично проработанным математическим бэкграундом… и полное отсутствие программистов, согласных на этом писать.


            1. Pakos
              04.05.2017 11:02

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


              PS. Автору


              Таким образом, создаётся переменная 11

              l1?


            1. Femistoklov
              04.05.2017 19:12
              +1

              В отличие от теорем, здесь есть побочные эффекты, ну там ввод-вывод, запрос даты-времени и вот это всё. В этом ключевое отличие от модной функциональщины.
              В итоге имеем красивую идею с отлично проработанным математическим бэкграундом… и полное отсутствие программистов, согласных на этом писать.
              Как только появляются побочные эффекты, перестаёт работать всенаправленность предиката и «красивая идея» вырождается в «модную функциональщину». А если не видно разницы, зачем мучиться, допиливая напильником N! до log(N).


  1. DrPass
    03.05.2017 16:54
    +3

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

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

    Идея в том, что язык состоит из функций, которые добавляют данные в стек или же выбрасывают данные из стека;

    ПОЛИЗ же. И советские программируемые калькуляторы :)


    1. kozzztik
      03.05.2017 17:13
      +1

      Так ассемблер же вроде, чего далеко ходить?


      1. Alex_ME
        04.05.2017 01:19

        Все-таки большинство процессорных архитектура — регистровые, а не стековые (стек есть, но помимо него — регистры, с которыми в основном и происходит работа).


        Примеры чисто стековых — виртуальные машины CLR и JVM, а так же процессор RTX2010, стоявший на зонде Rosetta


        RTX2010 — куда более редкий зверь. Это стековый процессор. Он оперирует данными, которые находятся не в регистрах, а в двух встроенных стеках. Роль машинного кода при этом исполняет высокоуровневый язык программирования Forth.


        1. 0xd34df00d
          04.05.2017 02:16

          PostScript ещё можно вспомнить.


        1. Deosis
          04.05.2017 07:10
          -2

          CLR не чисто стековая.


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


          1. Alex_ME
            06.05.2017 12:59

            В CLR есть регистры?


    1. slovak
      03.05.2017 23:38
      +1

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


      1. DrPass
        04.05.2017 01:13
        +2

        Verilog

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


  1. daiver19
    03.05.2017 17:56

    Да, я теперь смотрю на код и понимаю, как он всё-таки хорош по сравнению со всякими извращениями. Надо было здесь еще Brainfuck или Malbolge для полноты картины упомянуть.


    1. 0xd34df00d
      04.05.2017 02:16

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


    1. neomedved
      10.05.2017 06:16
      +1

      Так ведь это обычные императивные языки.


  1. nightwolf_du
    03.05.2017 18:26

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


    1. MacIn
      03.05.2017 18:48
      +1

      А если у вас многопроцессорная машина?
      Вычислительный комплекс с десятками ядер, и это не суперскалярное что-то? Какие-нибудь MIMD.


    1. IvanTamerlan
      03.05.2017 20:14

      принципы параллельного исполнения различны. У x86 (и x64) возможность параллельного вычисления вычисляется на лету, т.к. код то последовательный (совместимость с одноядерными).
      Следующий уровень — когда в коде задан явный параллелизм, т.е. те же потоки для х86 и х64. Или длинные команды для VLIW.
      Так что возможность параллельного исполнения смотрится не только на уровне задачи и кода, но и возможностей процессора. Тем более, что задача может быть решена несколькими способами, причем одни варианты могут быть параллельными, а другие — нет.


    1. Regis
      04.05.2017 02:48

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


      1. DrPass
        04.05.2017 09:43
        +2

        Это, грубо говоря, экономия на спичках за счёт сжигания пачки денег. При таком всегда «параллельном» подходе вы немного облегчаете работу самому дешёвому ресурсу — компилятору (и то, лишь в тех случаях, когда компилятор знает архитектуру исполняющих модулей конкретного процессора, чтобы его максимально загрузить параллельными тасками. Т.е. по сути, применение ограничивается JIT-компиляцией), существенно усложняя работу самому дорогому, программистам. При этом я не слишком ошибусь, если скажу, что узкие места в приложениях сейчас в 99.9999% случаев связаны отнюдь не с недостаточной работой оптимизатора компилятора.


        1. Regis
          04.05.2017 16:21

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

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


          1. vintage
            05.05.2017 06:58
            +2

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


  1. sbnur
    03.05.2017 19:44

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


  1. mbait
    03.05.2017 21:29

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

    Ок, проверить, что строковая константа является палиндромом на этапе компиляции можно и на C++. Но как это поддерживается для динамических данных? Каждый раз при изменении значения запускается процедура валидации или код пишется так, что можно доказать, что строка всегда будет оставаться палиндромом? Если первое, то как себя ведёт программа, если контракт нарушен?


    1. 0xd34df00d
      04.05.2017 02:17
      +1

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

      Вот так, да.


    1. michael_vostrikov
      04.05.2017 09:09
      -1

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


  1. CrazyFizik
    03.05.2017 23:41

    Было бы хорошо расширить и углубить статью :-)

    Конкурентность по умолчанию

    А чем это отличается от G/LabVIEW? Там конкурентность и параллелизм на уровне синтаксиса. И кстати Alice еще есть. По идее можно было бы наверное и Verilog с VHDL вспомнить, там правда конкурентность ИМХО под вопросом, т.к. определяется таймингами, но зато там чистый параллелизм. Еще наверное можно и Erlang с Go сюда же добавить (ну или хотя бы объяснить разницу, если нет).

    Язык Aurora — образчик символического программирования.

    Опять таки не упомянули LabVIEW — куда более известная и устоявшаяся штука (в отличии от Авроры, которую походу вообще ради фана делали), а также VisSim, SystemView, и JMCAD, а также все языки стандарта IEC 61131-3 и их потомки(символическое/графическое программирование, наверное наверное самое распространенное для промышленного программирования управления охрененно больших и дорогих штук). Ну Наверное еще и HiAsm и Simulink — правда я хз, можно ли вот эти два считать уже полноценными ЯП или нет.

    Требуем больше хлеба и зрелищь больше примеров и сравнений!


  1. d_olex
    04.05.2017 00:02
    +3

    Разработка подразумевающая конкурентность по умолчанию, кстати, используется в полный рост, только в немного других областях.
    Не так давно я в порядке хобби начал изучать FPGA после over 10 лет программирования на обычных ЯП, и это прям доставляет. Конфигурация для FPGA (программой это назвать наверное нельзя в виду отсутствия концепции потока исполнения) пишется на HDL языках вроде VHDL, Verilog, итд. которые сильно напоминают ANI/Plaid по вашему описанию: «инструкции» в пределах одного блока синхронной логики «исполняются» все и сразу за каждый тик тактового сигнала.
    Эти же концепции очень широко используются в системе команд и архитектуре современных процессоров. Почти наверняка у вас есть мобильное устройство с baseband модемом от Qualcomm внутри которого стоит несколько суперскалярных ядер с архитектурой Hexagon (разжиревший DSP который в ходе своего развития вытеснил проц общего назначения), там весь машинный код разбит на блоки по 4 (ЕМНИП) инструкции которые исполняются конкурентно. Процессоры со стековой архитектурой (конкатенативное программирование на низком уровне в сущности) тоже кстати бывают :)


  1. bentall
    04.05.2017 00:57

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


  1. Sirikid
    04.05.2017 11:07

    Конкурентность по умолчанию

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


    Зависимые типы

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

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


    data Positive = One | Succ Positive
    
    data Palindrome a
      -- ^ Вариант с четной длинной, полностью симметричный список
      = OddP [a]
      -- ^ Вариант с нечетной длинной, между симметричными частями элемент без пары
      | EvenP a [a]
    
    toList :: Palindrome a -> [a]
    toList (OddP xs) = xs ++ reverse xs
    toList (EvenP x xs) = xs ++ [x] ++ reverse xs

    То, что перечислил автор, скорее refinement types, чем dependent types.


    Здесь вы не найдёте устаревшего посыла «функциональное программирование спасёт мир!»; мой список состоит из куда менее популярных наименований.

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

    Лямбда-исчисление больше не катит, переходим на комбинаторную логику %)


    Эквивалент кода из поста на Haskell (ленивость then- и else- веток достигается встроенными средствами языка): https://ideone.com/mCRLdF


    import Data.Bool (bool)
    
    -- (.) - операция композиции-конкатенации функций
    foo = bool 0 42 . (10 <)
    
    main = print (foo 20)


    1. Sirikid
      04.05.2017 12:18

      Блин, опять четную с нечетной перепутал >_<


    1. 0xd34df00d
      05.05.2017 00:39

      К палиндрому это тоже относится.

      Это немножко читерство. Вы переносите описание свойств структуры данных с её типа на её интерпретацию (toList в вашем случае).


      1. Sirikid
        05.05.2017 02:46

        Согласен.
        А как написать список, который не сможет не быть палиндромом? Предикат легко: http://lpaste.net/8126944908561874944


  1. worldmind
    06.05.2017 11:08
    +1

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


  1. aristarh1970
    10.05.2017 06:10
    -2

    Название статьи претенциозное и неудачное.
    А на фоне признания автора, что он не является экспертом ни в одном из перечисленных языков (а может, и парадигм) вызыват стокие ассоциации с назойливой рекламой религиозных сект. Причем, шести сразу.
    Готовность автора пересматривать свои взгляды на программирования неоднократно вызывает по крайней мере сильные сомнения в полезности статьи: получается, что он уже поменял свои вгляды на программирование ШЕСТЬ раз. И предлагает читателям последовать его интеллектуальным метаниям, так как вполне вероятно это далеко не предел и его взгляды еще поменяются не раз.
    Описание парадигм, по крайне мере символического, конкатенативного и «основанного на знаниях» — крайне поверхностное.
    Короче, статья — рекламный мусор.


    1. DarkEld3r
      10.05.2017 10:12
      +2

      поменял свои вгляды на программирование ШЕСТЬ раз. И предлагает читателям последовать его интеллектуальным метаниям, так как вполне вероятно это далеко не предел и его взгляды еще поменяются не раз.

      Как будто что-то плохое. Конечно же, догматизм и синдром утёнка лучше.


      Короче, статья — рекламный мусор.

      Надо было ещё добавить проплаченный рекламный мусор, ну так для полноты картины. (:


  1. yuklimov
    10.05.2017 06:10

    Стоит добавить HDL языки (VHDL или Verilog). Кстати, вполне практичное применение конкурентности.


  1. Mishkun
    10.05.2017 06:10

    При упоминании слов "символы" и "программирование" вспоминается APL.
    А Aurora, по крайней мере, на видео, выглядит как не очень удобный MathLAB


  1. artur93gev
    10.05.2017 06:10

    Статья хорошая, мне понравилась.спасибо


  1. rkosolapov
    10.05.2017 06:10

    Интересующимся парадигмами зело рекомендую следующую книжку: https://mitpress.mit.edu/books/concepts-techniques-and-models-computer-programming Ну и другие книжки этого автора также неплохи.