Тут было дело много я интересовался темой "монад", детерминированностью функций и кешируемостью значений:
Прислали хорошую ссылку про монады
Может быть кто-то расскажет мне про монады?
Техника MapReduce это пример использования монад?
Ещё вопрос: Ленивые вычисления и кешируемость - как-нибудь соотносятся?
О чём я?
Учитывая вот этот пост:
ToDo. Написать про массивы, списки и итераторы
И вот этот комментарий к нему:
"Скорее, разрабатываемый Вами язык начинает обретать черты, присущие популярным скрипт-языкам.
(надеюсь, что автор не обидится на то, что я его процитировал).
Так вот. Учитывая всё вышесказанное.
Хочу сказать, что я дошёл до того момента, что стал учитывать детерминированность функций и "кешировать результат их выполнения".
Результаты - не то чтобы "прям впечатляющие", но заметные.
Ну и в заключение вспомню вот это:
Awaitable-значения в Delphi
И вот этот мой комментарий:
"Всеволод! Если Вы думаете, что я «придираюсь» к Роману, то Вы — не правы. Просто спросил.
Не совсем "в тему", но на самом деле - "пища для ума".
Кстати о "ленивости вычислений".
В "моих скриптах" я достиг её "случайно", таким вот образом:
Т.е. если мы напишем:
-- то выражение ( A AND B ) - "автоматически" вычисляться не будет.
Аналогично определяется и оператор AND:
Т.е. если мы напишем:
-- то выражение ( A AND B ) - "автоматически" вычисляться не будет.
Аналогично можно написать:
Т.е. если напишем:
Или так:
А если так:
Посмотрим вот на этот комментарий - "иначе ни выражение aLeft2, ни выражение aRight вычислены не будут".
Ну про "ни выражение aRight" - это - по-моему понятно, но когда "выражение aLeft2" надо вычислять?
Разве оно не всегда равно ссылке на переменную?
А вот - не всегда.
Напишем так:
А если написать так:
Если же написать так:
-- то за счёт предложения TYPE aLeft2 ConformsTo TYPE aRight - совместимость типов будет вычисляться в момент компиляции.
Почему так сложно?
У меня есть несколько объяснений:
1. Так исторически сложилось.
2. Я не знаю как иначе.
3. Это всё таки "оператор", а не функция или процедура. В чём разница? В том, что оператор может применяться к типам ANY, а процедуры и функции - не могут.
Конечно - ни одно из объяснений - мне лично не кажутся убедительными.
Но уж что есть, то есть.
C++ - я писать не планировал, хотя выходит "некое жалкое подобие", да ещё и с элементами "функциональности".
Вот за "подобие" - видимо вот такая расплата.
Отмечу один момент - "конечный пользователь" скриптов всего этого "ужаса" - не видит. Эти средства нужны лишь для "доопределения аксиоматики", которая не определена "хардкорно" на "стороне Delphi".
Ну и ещё:
Можно же написать так:
Так вот.
Про Evaluate:
Сложно. Да. Не спорю.
Но оно не "для конечных пользователей".
А для разработки инфраструктуры для "конечных пользователей".
И ещё.
Уход от ОПЗ:
Понятно, что мы "наследуемся от FORTH".
Там операция сложения выглядит как?
А так:
-- складываем 2 и 3.
А умножения?
А так:
-- умножаем 2 и 3.
Как избавиться от ОПЗ?
А вот так:
И вот так:
Тогда:
- компилируется как:
X := 1 + 2
А:
- компилируется как:
X := 1 * 2
А как компилируется вот это:
?
А вот так:
X := (1 + 2) * 3
А как сделать, чтобы "компилировалось нормально"?
А вот так:
И:
Обратите внимание на строки:
С их учётом:
А как компилируется вот это:
?
А вот так:
X := 1 + ( 2 * 3 )
Почему "так сложно"? Ну потому что "так сложно". Потому что "исторически сложилось".
Ну в C++ - арность и приоритет операций - "зашиты" в компилятор.
У меня - "не зашито" и не "прибито гвоздями".
Зато - сложно.
Но можно сравнить например с Прологом:
Операторы - тоже функторы
Пролог
Основные особенности языка Пролог
Процитирую:
"Некоторые функторы удобнее записывать, как операторы.
Может и не покажется уж слишком сложным.
Прислали хорошую ссылку про монады
Может быть кто-то расскажет мне про монады?
Техника MapReduce это пример использования монад?
Ещё вопрос: Ленивые вычисления и кешируемость - как-нибудь соотносятся?
О чём я?
Учитывая вот этот пост:
ToDo. Написать про массивы, списки и итераторы
И вот этот комментарий к нему:
"Скорее, разрабатываемый Вами язык начинает обретать черты, присущие популярным скрипт-языкам.
На мой взгляд, это
совершенно естественное явление, обусловленное желанием обеспечить
больше возможностей для создания программ, которые растут в объёме и при
разработке которых начинают возникать потребности в обобщениях.
Для того, чтобы эти обобщения выполнять, нужны соответствующие инструменты - классы, наследование, полиморфизм и т.д.
Так что, в этом плане вектор развития Вашего языка вполне понятен и логичен."(надеюсь, что автор не обидится на то, что я его процитировал).
Так вот. Учитывая всё вышесказанное.
Хочу сказать, что я дошёл до того момента, что стал учитывать детерминированность функций и "кешировать результат их выполнения".
Результаты - не то чтобы "прям впечатляющие", но заметные.
Ну и в заключение вспомню вот это:
Awaitable-значения в Delphi
И вот этот мой комментарий:
"Всеволод! Если Вы думаете, что я «придираюсь» к Роману, то Вы — не правы. Просто спросил.
А насчёт FreeAndNil — Вы неправы. Но я не буду начинать этот бесконечный спор.
А что хочу сказать по сути?
Роман продемонстрировал ОФИГЕННУЮ вещь! Просто и со вкусом. СПАСИБО, Роман!
Я обязательно прикручу её к своему коду.
Я даже знаю куда.
1. Тесты иногда запускают несколько потоков, чтобы например обрабатывать асинхронно контекстное меню.
2. В индексаторе есть сортировка слиянием. Независимые пары коробок — можно сортировать асинхронно.
3. В редакторе есть рендеринг пачки параграфов. Его тоже можно делать асинхронно."1. Тесты иногда запускают несколько потоков, чтобы например обрабатывать асинхронно контекстное меню.
2. В индексаторе есть сортировка слиянием. Независимые пары коробок — можно сортировать асинхронно.
Не совсем "в тему", но на самом деле - "пища для ума".
Кстати о "ленивости вычислений".
В "моих скриптах" я достиг её "случайно", таким вот образом:
BOOLEAN operator OR BOOLEAN IN aLeft // - параметр слева (передаётся по значению) BOOLEAN ^ IN aRight // - параметр справа (передаётся по ссылке) // Собственно реализация оператора: if aLeft then Result := true // - если левый параметр (уже вычисленный) истинен, то истинен и сам оператор OR else Result := Evaluate aRight // - иначе вычисляем значение правого параметра ; // OR
Т.е. если мы напишем:
if true OR ( A AND B ) then Print 'true' else Assert false 'не должны сюда попасть'
-- то выражение ( A AND B ) - "автоматически" вычисляться не будет.
Аналогично определяется и оператор AND:
BOOLEAN operator AND BOOLEAN IN aLeft // - параметр слева (передаётся по значению) BOOLEAN ^ IN aRight // - параметр справа (передаётся по ссылке) // Собственно реализация оператора: if aLeft then Result := Evaluate aRight // - если левый параметр (уже вычисленный) истинен, то вычисляем правый параметр else Result := false // - иначе оператор AND - неистинен ; // AND
Т.е. если мы напишем:
if false AND ( A AND B ) then Print 'false' else Assert false 'не должны сюда попасть'
-- то выражение ( A AND B ) - "автоматически" вычисляться не будет.
Аналогично можно написать:
VOID operator ?:= BOOLEAN IN aLeft1 // - параметр слева, передаваемый по значению ANY ^@ IN aLeft2 // - параметр слева, передаваемый по ссылке. // Псевдотип ANY указывает на "любой тип". // Но этот ANY "слева" должен быть совместим с ANY "справа" // По крайней мере для оператора ^:= // который называется присвоить значение по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке if aLeft1 then // - если значение aLeft1 (уже вычисленное) - истинно aLeft2 ^:= Evaluate aRight // - вычисляем значение aRight и присваиваем его тому, на что указывает aLeft2 // - иначе ни выражение aLeft2, ни выражение aRight вычислены не будут ; // ?:=
Т.е. если напишем:
INTEGER VAR X true X ?:= 1 // - переменной X будет присвоено значение 1
Или так:
INTEGER VAR X false X ?:= 1 // - переменной X не будет присвоено ничего
А если так:
INTEGER VAR X true X ?:= '1' // - получим ошибку компиляции "несовместимость типов" // или ошибку выполнения "невозможно присвоить строку '1' в INTEGER VAR X // ну "как повезёт". Всё зависит от сложности выражений "справа" и "слева". // Пока далеко не всё вычисляется во "время компиляции", // но во "время выполнения" - ловится всё
Посмотрим вот на этот комментарий - "иначе ни выражение aLeft2, ни выражение aRight вычислены не будут".
Ну про "ни выражение aRight" - это - по-моему понятно, но когда "выражение aLeft2" надо вычислять?
Разве оно не всегда равно ссылке на переменную?
А вот - не всегда.
Напишем так:
OBJECT VAR X true X -> Y // - вот это выражение вычисляет адрес поля Y на объекте X ?:= 1 // - полю X -> Y будет присвоено значение 1
А если написать так:
OBJECT VAR X false X -> Y // - вот это выражение вычисляет адрес поля Y на объекте X ?:= 1 // - полю X -> Y не будет присвоено ничего, // более того - и само выражение X -> Y - вычисляться не будет
Если же написать так:
VOID operator ?:= // VOID кстати означаете, что оператор ?:= не должен ничего возвращать, // точнее "оставлять на стеке". // Мы же всё-таки от FORTH наследуемся, посему - по-любому - работаем со "стеком значений" BOOLEAN IN aLeft1 // - параметр слева, передаваемый по значению ANY ^@ IN aLeft2 // - параметр слева, передаваемый по ссылке. // Псевдотип ANY указывает на "любой тип". // Но этот ANY "слева" должен быть совместим с ANY "справа" // По крайней мере для оператора ^:=, // который называется присвоить значение по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке TYPE aLeft2 ConformsTo TYPE aRight if aLeft1 then // - если значение aLeft1 (уже вычисленное) - истинно aLeft2 ^:= Evaluate aRight // - вычисляем значение aRight и присваиваем его тому, на что указывает aLeft2 // - иначе ни выражение aLeft2, ни выражение aRight вычислены не будут ; // ?:=
-- то за счёт предложения TYPE aLeft2 ConformsTo TYPE aRight - совместимость типов будет вычисляться в момент компиляции.
Почему так сложно?
У меня есть несколько объяснений:
1. Так исторически сложилось.
2. Я не знаю как иначе.
3. Это всё таки "оператор", а не функция или процедура. В чём разница? В том, что оператор может применяться к типам ANY, а процедуры и функции - не могут.
Конечно - ни одно из объяснений - мне лично не кажутся убедительными.
Но уж что есть, то есть.
C++ - я писать не планировал, хотя выходит "некое жалкое подобие", да ещё и с элементами "функциональности".
Вот за "подобие" - видимо вот такая расплата.
Отмечу один момент - "конечный пользователь" скриптов всего этого "ужаса" - не видит. Эти средства нужны лишь для "доопределения аксиоматики", которая не определена "хардкорно" на "стороне Delphi".
Ну и ещё:
Можно же написать так:
^@ FUNCTION GetFieldY // - ^@ FUNCTION означает определение функции, возвращающей ссылку на значение, // которое потом может быть выполнено через Evaluate OBJECT ^@ IN aSelf // - параметр слева, передаваемый по ссылке Result := Evaluate aSelf -> Y // - вычисляем поле Y объекта aSelf и возвращаем ссылку на него // Почему тут Evaluate? Объясню ниже. ; // GetFieldY OBJECT VAR X true X GetFieldY // - вот это выражение вычисляет адрес поля Y на объекте X ?:= 1 // - полю X -> Y будет присвоено значение 1
Так вот.
Про Evaluate:
^@ FUNCTION GetFieldY OBJECT ^@ IN aSelf // - параметр слева, передаваемый по ссылке Result := Evaluate aSelf -> Y // - вычисляем поле Y объекта aSelf и возвращаем ссылку на него // Почему тут Evaluate? Объясню ниже. ; // GetFieldY OBJECT VAR X true X GetFieldY GetFieldY // - вот это выражение вычисляет адрес поля Y на объекте Y на объекте X ?:= 1 // - полю X -> Y -> Y будет присвоено значение 1,
// если выражение X -> Y -> Y - конечно вычисляемое
Сложно. Да. Не спорю.
Но оно не "для конечных пользователей".
А для разработки инфраструктуры для "конечных пользователей".
И ещё.
Уход от ОПЗ:
Понятно, что мы "наследуемся от FORTH".
Там операция сложения выглядит как?
А так:
2 3 +
-- складываем 2 и 3.
А умножения?
А так:
2 3 *
-- умножаем 2 и 3.
Как избавиться от ОПЗ?
А вот так:
override ANY // - почему тут ANY, а не INTEGER или DOUBLE? // Именно потому что inherited + применим к РАЗНЫМ типам // Но как же контроль типов? Обратите внимание на TYPE Result ConformsTo TYPE aLeft operator + ANY ^@ IN aLeft // - параметр слева, передаваемый по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке TYPE Result ConformsTo TYPE aLeft // - постулируем, что тип Result совместим с aLeft TYPE Result ConformsTo TYPE aRight // - постулируем, что тип Result совместим с aRight Result := ( Evaluate aLeft // - вычисляем значение параметра слева Evaluate aRight // - вычисляем значение параметра справа inherited + // - вызываем оператор +, который "ещё ОПЗ" ) ; // +
И вот так:
override ANY operator * ANY ^@ IN aLeft // - параметр слева, передаваемый по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке TYPE Result ConformsTo TYPE aLeft // - постулируем, что тип Result совместим с aLeft TYPE Result ConformsTo TYPE aRight // - постулируем, что тип Result совместим с aRight Result := ( Evaluate aLeft // - вычисляем значение параметра слева Evaluate aRight // - вычисляем значение параметра справа inherited * // - вызываем оператор *, который "ещё ОПЗ" ) ; // *
Тогда:
INTEGER VAR X X := 1 + 2
- компилируется как:
X := 1 + 2
А:
INTEGER VAR X X := 1 * 2
- компилируется как:
X := 1 * 2
А как компилируется вот это:
INTEGER VAR X X := 1 + 2 * 3
?
А вот так:
X := (1 + 2) * 3
А как сделать, чтобы "компилировалось нормально"?
А вот так:
override ANY PRIORITY 0 operator + ANY ^@ IN aLeft // - параметр слева, передаваемый по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке TYPE Result ConformsTo TYPE aLeft // - постулируем, что тип Result совместим с aLeft TYPE Result ConformsTo TYPE aRight // - постулируем, что тип Result совместим с aRight Result := ( Evaluate aLeft // - вычисляем значение параметра слева Evaluate aRight // - вычисляем значение параметра справа inherited + // - вызываем оператор +, который "ещё ОПЗ" ) ; // +
И:
override ANY PRIORITY 1 operator * ANY ^@ IN aLeft // - параметр слева, передаваемый по ссылке ANY ^ IN aRight // - параметр справа, передаваемый по ссылке TYPE Result ConformsTo TYPE aLeft // - постулируем, что тип Result совместим с aLeft TYPE Result ConformsTo TYPE aRight // - постулируем, что тип Result совместим с aRight Result := ( Evaluate aLeft // - вычисляем значение параметра слева Evaluate aRight // - вычисляем значение параметра справа inherited * // - вызываем оператор *, который "ещё ОПЗ" ) ; // *
Обратите внимание на строки:
PRIORITY 0 PRIORITY 1
С их учётом:
А как компилируется вот это:
INTEGER VAR X X := 1 + 2 * 3
?
А вот так:
X := 1 + ( 2 * 3 )
Почему "так сложно"? Ну потому что "так сложно". Потому что "исторически сложилось".
Ну в C++ - арность и приоритет операций - "зашиты" в компилятор.
У меня - "не зашито" и не "прибито гвоздями".
Зато - сложно.
Но можно сравнить например с Прологом:
Операторы - тоже функторы
Пролог
Основные особенности языка Пролог
Процитирую:
"Некоторые функторы удобнее записывать, как операторы.
Например, можно записать
+(1, 2)
или
+
/ \
1 2
Удобнее записать 1+2 , т.е. в виде оператора. Причем надо понимать, что это не операция сложения, а операторная запись структуры. Такие операторы называются инфиксными.
Аналогично операторная запись
2*a+b*c
может быть представлена в виде структуры:
+( *(2, a), *(b, c))
Это и производит пролог при трансляции операторных выражений. Надо четко понимать, что операторы - это другая форма записи структуры."Может и не покажется уж слишком сложным.
Комментариев нет:
Отправить комментарий