Обратная связь, ресурсы и информация для декларативного языка программирования - PullRequest
3 голосов
/ 27 ноября 2010

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

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

Основная идея декларативного программирования - «что» против «как».Тем не менее, я слышал это очень много раз, так что, похоже, оно почти всегда похоже на слово «интересно», когда оно на самом деле ничего вам не говорит, что расстраивает.

В версии Джонатана Эдварда, хотяСначала он подчеркнул ленивую оценку 1010 *.Это имеет некоторые интересные последствия, а именно функционально-реактивное программирование (FRP) .Вот пример FRP с анимацией (с использованием синтаксиса, который я составил):

x as time * 2 // time is some value representing the current time
y as x + (2 * 500)

new Point(x, y)

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

Полагаю, здесь применяется аналогичный транзитивный принцип.Наша примесь time.Все, к чему прикоснулся time, будучи x, таким образом y и, следовательно, new Point(x, y), нечисты.Тем не менее, уведомление (2 * 500) чисто.Итак, вы видите, что это говорит компилятору, где находятся его ограничения.Я думаю об этом, как об упрощении математического выражения с помощью переменных:

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

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

void main ()
{
    String input = "";

    writeln("Hello, world!");
    writeln("What's your name? ");

    input = readln();

    writeln("Hello, %s!", input);
    writeln("What's your friends name? ");

    input = readln();

    writeln("Hello to you too, %s!", input);
}

Здесь ключевое слово bind говорит о том, что следующий код выполняется при изменении begin.Ключевое слово mutable говорит, что ввод не ленивый, а нетерпеливый.Теперь давайте посмотрим, как его может представлять «изменение атомарного состояния».

program:
    mutable step := 0

    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")
        ++step

    bind readln() as input when step = 1:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        ++step

    bind readln() as input when step = 2:
        writeln("Hello to you too, %s!", input)

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

program:
    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")

    bind readln() as input:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        yield // This just means the program jumps to here instead of at the beginning
        writeln("Hello to you too, %s!", input)
        halt

Это лучше.Не идеально, хотя.Но если бы я знал идеальный ответ, меня бы здесь не было, верно?

Вот лучший пример с использованием игрового движка:

class VideoManager:
    bind begin: // Basically a static constructor, will only be called once and at the beginning
        // Some video set up stuff

    bind end: // Basically a static destructor
        // Some video shut down stuff

class Input:
    quitEvent     as handle // A handle is an empty value, but can be updated so code that's bound to it changes.
    keyboardEvent as handle(KeyboardEvent) // This handle does return a value though
    mouseEvent    as handle(MouseEvent)

    // Some other code manages actually updating the handles.

class Sprite:
    mutable x := 0
    mutable y := 0

    bind this.videoManager.updateFrame:
        // Draw this sprite

class FieldState:
    input  as new Input
    player as new Sprite

    bind input.quitEvent:
        halt

    bind input.keyboardEvent as e:
        if e.type = LEFT:
            this.player.x -= 2
        else if e.type = RIGHT:
            this.player.x += 2
        else if e.type = UP:
            this.player.y -= 2
        else if e.type = DOWN:
            this.player.y += 2

Мне нравится, что для этого не требуетсяобратные вызовы, события или даже циклы или что-то, и потоки очевидны.Проще сказать, что происходит, и это не просто Python-подобный синтаксис.Я думаю, что это похоже на то, когда разработчики языка поняли, что было только несколько вещей, для которых люди использовали ярлыки и переходы: условные ветви и циклы.Таким образом, они встроили if-then-else, в то время как и для языков, ярлыки и goto стали устаревшими, и компиляторы, а также люди могли сказать, что происходит.Большая часть того, что мы используем, происходит из этого процесса.

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

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

Это было связано с тем, как я воспринимал функцию.Например:

int add (int a, int b)
{
    return a + b;
}

Хорошо, add вернул int, но что было это?Это было похоже на int ожидание, чтобы произойти.Как головоломка без нескольких кусочков.Возможности были ограничены, и подходили только определенные части, но когда вы закончили, у вас был готовый продукт, который вы могли бы затем использовать в другом месте.Это, как я уже сказал, принцип абстракции.Вот несколько примеров того, что я считаю абстракцией + отсутствующими частями -> конкретными отношениями:

  • функция + аргументы -> значение
  • абстрактный класс + методы -> класс
  • класс + значения экземпляра -> объект
  • шаблон + аргументы -> функция или класс
  • программа + ввод + состояние -> вывод

Они всетесно связаны.Кажется, этим можно воспользоваться.Но как?Опять же, вот почему это вопрос.Но ленивая оценка здесь интересна, поскольку вы можете передать что-то , а его части по-прежнему отсутствуют , во что-то другое.Для компилятора это в основном вопрос разыменования имен вплоть до примесей.Как мой пример, приведенный выше:

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

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

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

1 Ответ

2 голосов
/ 27 ноября 2010

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

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

Итак, вопрос в том, как Haskell справляется с необходимыми примесями, возникающими при любом IO.

Ответ вполне вписывается в мысли, которые вы представили. Haskell использует математическую конструкцию, называемую монады , которая в основном представляет вычисление, производящее некоторое значение, наряду с функцией bind (>>= в качестве инфиксного оператора), которая упорядочивает такие вычисления.

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

do
    putStr "Enter your name: "
    name <- getLine
    putStrLn ("Hello " ++ name)

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

(putStr "Enter your name: ") >>
(getLine >>= \name ->
 putStrLn ("Hello " ++ name))

Теперь вы можете определить это bind / >>= для произвольных видов вычислений любым удобным для вас способом. Таким образом, фактически все, о чем вы говорили, может быть реализовано таким образом - даже FRP.

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

...