Войти
ФлеймФорумПрограммирование

Революционные идеи в сфере языков программирования (5 стр)

Страницы: 14 5 6 732 Следующая »
#60
18:12, 15 ноя. 2018

Параметрические циклы строятся аналогичным образом: конструкция

for <parameter-list> in <source-expression> do
    <body-expression-sequence>
end

заворачивает тело цикла в лямбду и передаёт её в метод объекта, который уже внутри себя вызывает эту лямбду в цикле.

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

local arr = new List[Integer] {1, 2, 3}
for x: Integer in arr do
    print[x]
end
for x: Integer, index -> i: Integer in arr do
    print[i, ": ", x]
end
for local x: Integer, -> index: Integer in modify arr do
    x = index * (index + 10)
end
-- arr остался {1, 2, 3}
for modify x: Integer, -> index: Integer in arr do
    -- ошибка - arr передан как external, тогда как требуемая перегрузка берёт modify
end
for modify x: Integer, -> index: Integer in modify arr do
    x = index * (10 - index)
end
-- arr теперь {0, 9, 16}

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

language eggspam {second_order_expressions}
-- <...>
local arr2 = filter x: Integer in arr do
    if x % 10 == 0 do
        reject
    else
        accept
    end
end
-- arr2 is {9, 16}
local arr3 = map x: Integer in arr do x * 2 end
-- arr3 is {0, 18, 32}
local arr4 = reap
    for x: Integer in arr do
        if x % 10 == 0 do
            sow 100 + x
        end
        sow 200 + x
        if x % 10 ~= 0 do
            sow 300 + x
        end
        sow 400 + x
    end
end
-- arr4 is {100, 200, 400, 209, 309, 409, 216, 316, 416}


Прошло более 1 года
#61
4:14, 1 янв. 2020

Знаки препинания сведены к минимуму.
Кроме того, каждому знаку ставится в соответствие как можно меньше значений; в идеале - один знак может значить только одну вещь, вне зависимости от контекста.
Цель, очевидно, - повысить читаемость кода; причём не только для людей, не знающих синтаксис этого языка, но и для владеющих, за счёт возможности прочитать код вслух (это важно!)

Текущий набор пунктуации выглядит примерно так.
Арифметические операторы +, -, *. Целочисленное деление обозначается кейвордами div, mod. / означает вещественное деление. "5 / 6" является ошибкой - нужно делать либо "5 div 6", либо "Float[5] / 6".
Операторы сравнения <, <=, >, >=, ==, !=.
Группировка выражений ().
Вызов функции [].
Да, для группировки и вызова используются разные скобочки. Это революция!
Индексация массива является частным случаем вызова, причём язык позволяет как "x = arr[i]", так и "arr[i] = x" (это магия!)
Присваивание =. "a = b = c" и "if x = y" запрещены.
Именная привязка -> передаёт непозиционные аргументы.
Задание типа : указывает типы параметров функции, типы переменных и констант, а так же служит для явного приведения статического типа выражения.
Разыменование . работает с объектами, классами, неймспейсами и прочими составными вещами.
Запятая , в роли запятой.

Границы forwhile-выражений задаются кейвордами:
if cond then stuff elseif stuff else stuff end
while cond do stuff else stuff end
for iters in source do stuff else stuff end
function name [ params ] : rettype do body end
Условия при этом не заключаются в скобки, их границы так же определяются кейвордами.
Логические операции обозначаются кейвордами and, or, xor, not
Битовые операции обозначаются функциями "BitwiseAnd[x, y, z...]"; импортирование специального модуля добавляет операторы-кейворды band, bor, bxor, bnot.
Местный аналог классов-шаблонов работает как функции: "Map[Text, List[Text]]".
Все указатели являются библиотечными, и работают через классы, функции и перегрузку вызова: "Pointer[Top]", "Shared[Text]", «ptr[] = "hello foo"».
Вместо ссылок используются режимы передачи аргументов, обозначаемые кейвордами local, external, produce, mutate и так далее.
Конструкции не разделяются явно. Оно и не нужно. В многом местный синтаксис основывается на Lua, там единственная проблема с разделением - это конструкции вида «x = y \n (print)("hello")», которые можно распарсить и как «x = y(print)("hello")», и как «x = y; print("hello")»; тут же эта проблема решается самой главной революцией, лол.
Инициализация так же реализуется через вызов функции, при необходимости вместе с именными привязками: "Point[x -> 10, y -> 20]", "Array[Integer][1, 5, 3, 11]".

#62
9:59, 1 янв. 2020

А теперь о серьёзном.
Система типов строится на трёх примитивах.

Интерфейс - это основная единица типизации. Интерфейс задаёт список действий, применимых к объекту данного типа.

interface Visual
    requires procedure Show[]
end

interface Audial
    requires procedure Talk[]
end

interface Naval
    requires procedure Swim[]
end


Интерфейс может быть как простым, так и параметрическим.
interface Equatable[OtherType]
    requires function Equals[other: OtherType]: Testable
end

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

interface Bird
    requires interface Visual
    requires interface Audial
    requires procedure Fly[]
end

interface Comparable[OtherType]
    requires interface Equatable[OtherType]
    requires function Less[other: OtherType]: Testable
end

interface Ordered[OtherType]
    requires interface Comparable[OtherType]
end

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

interface GenericDuck
    requires procedure Tell[things: String]
end

implementation for GenericDuck of Bird
    procedure Show[]
        Tell["I look like a beautiful duck"]
    end

    procedure Talk[]
        Tell["Quack!"]
    end

    requires procedure Fly[]
end

implementation for GenericDuck of Equatable[GenericDuck]
    function Equals[other: GenericDuck]: Boolean
        return false -- no two ducks are created equal
    end
end

interface RubberDuck
    requires interface GenericDuck
end

implementation for RubberDuck of Bird
    procedure Fly[]
        Tell["I was hit so hard I flew out of the window"]
    end
end

implementation for RubberDuck of Naval
    procedure Swim[]
        Tell["You cannot sink me!"]
    end
end


При выполнении конструкции "implementation for A of B", компилятор составляет список действий, которые перечислены в B и всех его надтипах, и при этом не реализованы ни в самом B напрямую, ни в предыдущих определениях для A косвенно. Для действий из этого списка, в конструкции должна быть либо полная реализация, либо явная передача ответственности через requires.

Оба отношения являются вариантами наследования: реализацию можно свободно подставить на место абстракции, расширение - на место базы.

В целом, интерфейсы и их отношения образуют ациклический направленный граф. На нём определены отношения подмножества - когда от одного интерфейса существует путь до другого, пересечения - для обозначения объектов, реализующих одновременно несколько интерфейсов, и объединения - для обозначения результатов операций типа "if cond then x: A else y: B end" ==> "A or B".
Пересечения сохраняются как список интерфейсов и не теряют информации об аргументах; объединение же сокращается до интерфейсов, общих для всех вариантов - например, "RubberDuck or Comparable[RubberDuck]" сокращается до "Equatable[GenericDuck]".

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

interface Foo
    requires function B[]: Bar
end
interface Bar
    requires function F[]: Foo
end

однако, не даёт соорудить граф с циклами
interface Foo
    requires interface Bar -- Bar is still not a valid type at this point
end
interface Bar
    requires interface Foo
end

Помимо этого, в языке действует своя версия "orphan rules".
Каждый вводимый тип может быть либо источником отношений наследования, либо поглотителем.
interface Pivot
end

interface Source
end

implementation for Source of Pivot
end

-- mark as a sink type
behavior Sink
end

implementation for Pivot of Sink
end

behavior Bridge
end

implementation for Source of Bridge
end

implementation for Bridge of Sink -- invalid: Bridge is a sink type
end


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

#63
16:02, 1 янв. 2020

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

#64
(Правка: 17:07) 17:07, 1 янв. 2020

Что-то интересное, жаль только, что дофига писюнины, как бы прочитать все это...

#65
18:52, 1 янв. 2020

Zegalur
Вообще, такая система ближе всего к трейтам из Rust.

#66
19:30, 1 янв. 2020

Delfigamer
Угу, посмотрел. В Расте оно более ООП-нутое (интерфейсы) и менее гибкое, проблем с зацикливанием скорее всего не возникнет. Если что, я про случай, когда у нас граф без циклов, но c бесконечной цепочкой, типа

C[A[T]] <- C[A[A[T]]] <- C[A[A[A[T]]]] <- ...
То-есть, реализацию "метода" f для C[A[T]] мы возьмем из C[A[A[T]]], который в свою очередь из  C[A[A[A[T]]]] и т.д.
#67
1:39, 2 янв. 2020

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

#68
(Правка: 2:15) 2:14, 2 янв. 2020

gammaker
Там нет чёткого разделения между компайл- и рантаймом.
Семантически, ядро языка работает в непрерывном режиме, подобно ОС. Когда приходит время, он загружает определённые модули, которые, в свою очередь, прицепляют новые типы к глобальному графу. Хоть прогрузка модуля и проходит в несколько стадий, разные стадии независимых модулей могут свободно перемежаться; в том числе в процессе исполнения функция одного модуля может заказать подгрузку других модулей в рантайме.

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

parametric [ T: subset of Addable[ T ] ]
function Total[ seq: Sequence[ T ], base: T ]: T
    with elem, rest from seq.Split[] do
        return Total[ rest, base + elem ]
    else
        return base
    end
end

сможет работать с любыми реализациями Addable, даже добавленными после компиляции и загрузки Total; с другой стороны, проверка корректности проводится ещё на стадии статического анализа - если убрать «: Addable[T]», то компилятор разругается на счёт «base + elem»; если убрать «: Sequence[T]» - то «Split» окажется неопределённым идентификатором.

Так что, как я понимаю эти термины, в моём языке статическая типизация (проверка корректности происходит заранее) и динамическая диспатчеризация (выбор конкретной функции происходит в рантайме).

#69
19:45, 2 янв. 2020

> Семантически, ядро языка работает в непрерывном режиме, подобно ОС.

уу..пошла каша.

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

#70
19:47, 2 янв. 2020

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

#71
23:09, 6 янв. 2020

Delfigamer
> Каждый вводимый тип может быть либо источником отношений наследования, либо
> поглотителем.

Язык разрешает довольно много нетривиальных вещей в типовой системе.
Отношение "X implements Y" не требует полной реализации Y. Возможен даже случай, когда X вообще не реализует ничего:

interface Dog
    requires procedure Woof[]
    requires procedure Bark[]
end

interface AdvancedDog
    requires procedure Wanwan[]
end

implementation for AdvancedDog of Dog
    requires procedure Woof[]
    requires procedure Bark[]
end
-- legit non-implementation

procedure Pet[good: Dog]
    good.Woof[]
end

procedure AdvancedPet[better: AdvancedDog]
    Pet[better] -- ok: AdvancedDog is a subtype of Dog
    better.Bark[] -- ok: AdvancedDog supports Dog.Bark[]
    better.Wanwan[] -- ok: AdvancedDog supports AdvancedDog.Wanwan[]
end

Интерфейс может сам же задать дефолтную реализацию своего действия:

interface Cat
    procedure Meow[]
        Print["Meow world!"]
    end
    requires procedure Eat[]
end

Интерфейс A может отнаследовать реализацию B от третьего интерфейса C:

interface Catgirl
    requires procedure Talk[]
end

implementation for Catgirl of Cat
    procedure Eat[]
        with local food from globalFridge.TakeRandom[] do
            food.Consume[]
        else
            Print["I am hungry, nya!"]
        end
    end
end

interface Natsuko
end

implementation for Natsuko of Cat
    requires procedure Eat[]
end

implementation for Natsuko of Catgirl
    procedure Talk[]
        Print["The world won't change for you, unless you change it yourself, nya."]
    end
end
-- Cat.Eat[] for Natsuko is taken from Catgirl

Отнаследованные реализации можно переопределять:

implementation for Natsuko of Cat
    procedure Meow[]
        Print["Nya!"]
    end
end

В любое время можно добавлять новые реализации уже существующих интерфейсов:
interface NatsukoInumimi
    requires interface Natsuko
end

implementation for NatsukoInumimi of Dog
    procedure Woof[]
        Print["Erm... woof... nya?"]
    end

    procedure Bark[]
        Print["Nya-hark?!"]
    end
end

procedure FeelsWeirdMan[n: NatsukoInumimi]
    Pet[n] -- ok: NatsukoInumimi is a subtype of Dog
    n.Meow[] -- ok: NatsukoInumimi supports Cat.Meow[]
end

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

-- 'behavior' is like an 'interface', except it's a sink type rather than a source
behavior Animal
    requires procedure Cry[]
end

implementation for Dog of Animal
    procedure Cry[]
        Bark[]
    end
end

implementation for Cat of Animal
    procedure Cry[]
        Meow[]
    end
end

behavior Mob
    requires procedure DoSomething[]
    requires procedure DoSomethingElse[]
end

implementation for Animal of Mob
    procedure DoSomething[]
        Cry[]
    end

    procedure DoSomethingElse[]
        Cry[]
    end
end

implementation for Catgirl of Mob
    procedure DoSomethingElse[]
        Talk[]
    end
end

procedure AreYouWithMeStill[n: NatsukoInumimi]
    n.DoSomething[] -- will print "Nya!" because reasons
    n.DoSomethingElse[] -- will print the inspirational quote
end

продолжение в следующем сообщении

#72
(Правка: 23:17) 23:17, 6 янв. 2020

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

Мы формулируем проблему таким образом.

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

Интерфейс - это список глаголов с опциональными реализациями.

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

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

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

В примере выше, это будут таблицы следующего вида:

Dog:
    procedure Dog.Woof[] = 0
    procedure Dog.Bark[] = 0

AdvancedDog:
    procedure Dog.Woof[] = 0
    procedure Dog.Bark[] = 0
    procedure AdvancedDog.Wanwan[] = 0

Cat:
    procedure Cat.Meow[] = implementation Cat.Meow
    procedure Cat.Eat[] = 0

Catgirl:
    procedure Cat.Meow[] = implementation Cat.Meow
    procedure Cat.Eat[] = implementation Catgirl.Eat
    procedure Catgirl.Talk[] = 0

Natsuko:
    procedure Cat.Meow[] = implementation Natsuko.Meow
    procedure Cat.Eat[] = implementation Catgirl.Eat
    procedure Catgirl.Talk[] = implementation Natsuko.Talk

NatsukoInumimi:
    procedure Dog.Woof[] = implementation NatsukoInumimi.Woof
    procedure Dog.Bark[] = implementation NatsukoInumimi.Bark
    procedure Cat.Meow[] = implementation Natsuko.Meow
    procedure Cat.Eat[] = implementation Catgirl.Eat
    procedure Catgirl.Talk[] = implementation Natsuko.Talk

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

Внутри типа-поглотителя, будем хранить не один набор реализаций, а таблицу "тип-набор" для каждого значимого типа.

Если "A implements B" и A является типом-источником - то в B сохраняется только запись для A. Если же A - это тип-поглотитель, то как сам A, так и все типы, значимые для A, считаются значимыми для B.

Поэтому, в примере выше, для поглотителей получится примерно такое содержание:

Animal:
    Dog:
        procedure Animal.Cry = implementation Dog -> Animal.Cry
    Cat:
        procedure Animal.Cry = implementation Cat -> Animal.Cry

Mob:
    Dog:
        procedure Mob.DoSomething = implementation Animal -> Mob.DoSomething
        procedure Mob.DoSomethingElse = implementation Animal -> Mob.DoSomethingElse
    Cat:
        procedure Mob.DoSomething = implementation Animal -> Mob.DoSomething
        procedure Mob.DoSomethingElse = implementation Animal -> Mob.DoSomethingElse
    Animal:
        procedure Mob.DoSomething = implementation Animal -> Mob.DoSomething
        procedure Mob.DoSomethingElse = implementation Animal -> Mob.DoSomethingElse
    Catgirl:
        procedure Mob.DoSomething = implementation Animal -> Mob.DoSomething
        procedure Mob.DoSomethingElse = implementation Catgirl -> Mob.DoSomethingElse

Наконец, процедура диспатча выглядит так.

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

Этот вариант возможно реализовать в том числе методом классических vtable.

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

продолжение в следующем сообщении

#73
23:24, 6 янв. 2020

В примерах кода выше диспатч произойдёт так.

В procedure FeelsWeirdMan, спрятанный внутри вызов Dog.Woof[] возьмёт реализацию из списка в NatsukoInumimi, что напишет "Erm... woof... nya?"; если только фактический тип объекта не переопределит этот глагол ещё дальше по графу наследования.

В procedure AreYouWithMeStill, рантайм начнёт по очереди искать значимый для Mob тип среди надтипов NatsukoInumimi. Поскольку «requires interface Natsuko» стоит впереди «implementation for NatsukoInumimi of Dog», то и Catgirl в списке надтипов будет стоять выше, чем Cat и Dog; следовательно, будет выбрана реализация Catgirl -> Mob.

При вызове Mob.DoSomething, выберется статически отнаследованная implementation Animal -> Mob.DoSomething, которая вызовет Animal.Cry. Это опять произведёт динамический диспатч с поиском по надтипам NatsukoInumimi, и на этот раз первый встреченный значимый - это Cat, для которого записана реализация implementation Cat -> Animal.Cry. Он, в свою очередь, вызывает Cat.Meow - на этот раз, глагол типа-источника, поэтому его реализация будет взята напрямую из виртуальной таблицы - implementation Natsuko.Meow, который, наконец, напишет "Nya!" в стандартный вывод.

Для Mob.DoSomethingElse та же процедура диспатча выдаст тот же результат - будет взята реализация из Catgirl -> Mob, на этот раз - прописанная явно implementation Catgirl -> Mob.DoSomethingElse. Он сразу вызывает глагол источника Catgirl.Talk, реализация которого для NatsukoInumimi - implementation Natsuko.Talk - напишет вдохновляющую фразу.

Основное применение типов-источников - это, очевидно, классическое ООП с его тремя китами.

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

procedure PrintNice[x: Integer, width: Integer]
    <...>
end

procedure PrintNice[x: String, width: Integer]
    <...>
end

система под капотом преобразует их в аналог следующего:

behavior PrintNiceAble[WType]
    requires procedure PrintNice[width: WType]
end

implementation for Integer of PrintNiceAble[Integer]
    procedure PrintNice[width: Integer]
        <...>
    end
end

implementation for String of PrintNiceAble[Integer]
    procedure PrintNice[width: Integer]
        <...>
    end
end

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

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

#74
23:24, 6 янв. 2020

slepov
> Вот тогда ты поймешь что всего лишь балуешься синтаксисом.
Абсолютно.

Страницы: 14 5 6 732 Следующая »
ФлеймФорумПрограммирование