Предыдущая серия была тут -
#1163. :, PROCEDURE, FUNCTION. Параметры справа и слева.Часть 1.
Там мы рассмотрели ключевые слова
:,
;,
FUNCTION,
PROCEDURE.
А также "параметры слева".
Рассмотрим теперь "параметры справа".
Пусть у нас есть пример с "параметрами слева":
INTEGER FUNCTION Plus
INTEGER IN A
INTEGER IN B
A B + >>> Result // - складываем A и B и помещаем в Result
; // Plus
1 2 Plus . // - вызываем нашу функцию и печатаем результат
Тут мы имеет "типичную"
ОПЗ.
А что делать, если мы хотим пользоваться
инфиксной нотацией?
Вот тут нам помогут
параметры справа.
Перепишем наш пример с использованием
параметров справа:
INTEGER FUNCTION Plus
INTEGER IN A // - параметр слева
^ IN B // - параметр СПРАВА передаётся по ССЫЛКЕ, а не по ЗНАЧЕНИЮ.
// Его надо разыменовывать.
A // - значение параметра A
B DO // - разыменовываем значение B. Т.е. зовём метод DO на том слове на которое указывает B
+ >>> Result // - складываем A и B и помещаем в Result
; // Plus
1 Plus 2 . // - вызываем нашу функцию ИНФИКСНО и печатаем результат
Подчеркну, что
параметры справа передаются
по ссылке.
Также можно написать:
1 Plus ( 2 Plus ( 3 Plus 4 ) ) .
Скобки пока обязательны.
Как обойтись без скобок - напишу отдельно.
Также наш пример можно переписать так:
INTEGER FUNCTION Plus
INTEGER IN A // - параметр слева
^ IN B // - параметр СПРАВА передаётся по ССЫЛКЕ, а не по ЗНАЧЕНИЮ.
// Его надо разыменовывать.
A // - значение параметра A
B |^ // - разыменовываем значение B. Т.е. зовём метод |^ на том слове на которое указывает B
+ >>> Result // - складываем A и B и помещаем в Result
; // Plus
1 Plus 2 . // - вызываем нашу функцию ИНФИКСНО и печатаем результат
Тут используется |^ вместо DO.
Они вообще говоря равноценны.
Про
отличия я напишу несколько позже.
Метод |^ в аксиоматике определяется так:
: |^
^@ IN aRef
%SUMMARY 'Разыменовывает параметр слева' ;
aRef pop:Word:GetRef DO
; // |^
Детали реализации |^ я также опишу позже.
Но пока отмечу, что |^ использует DO. Т.е. |^ является производным от DO.
Пойдём далее.
Зачем параметры справа передаются по ссылке,а не по значению?
Тому есть много причин.
В частности - "
ленивые вычисления".
Рассмотрим реализацию булевских операций AND и OR.
Вот она:
BOOLEAN operator AND
BOOLEAN IN aFirst
^ IN aSecond
%SUMMARY 'Двусторонний, а не обратный польский &&' ;
if aFirst then
(
if ( aSecond DO ) then
( true >>> Result )
else
( false >>> Result )
)
else
( false >>> Result )
; // AND
BOOLEAN operator OR
BOOLEAN IN aFirst
^ IN aSecond
// Двусторонний, а не обратный польский ||
if aFirst then
( Result := true )
else
if ( aSecond DO ) then
( Result := true )
else
( Result := false )
; // OR
Тут видно, что параметр
aSecond будет вычисляться ТОЛЬКО если он нужен для вычисления всего выражения.
Т.е. если по параметру
aFirst - результат выражения будет
ещё неясен.
Слово operator является аналогом слов : и FUNCTION. ОН лишь подчёркивает "операторную сущность" определяемых слов.
В частности - операторам можно задавать "
приоритет выполнения" как например в
Prolog.
Чтобы например избавиться от скобок в примере со словом Plus выше.
Но об этом расскажу отдельно.
Но пока будем считать, что operator определён как:
WordAlias operator :
WordAlias OPERATOR :
И что мы получаем с
ленивыми вычислениями?
Если написать без ленивых вычислений:
if ( ( anObject <> nil ) ( anObject .SomeMethod ) && ) then
То получим Access Violation.
А с ленивыми вычислениями:
if ( ( anObject <> nil ) AND ( anObject .SomeMethod ) ) then
Access Violation - не будет.
Надеюсь - понятно почему.
Операция
<> кстати тоже определена в базовой аксиоматике при помощи
правых и
левых параметров. И через операцию =.
Вот так:
BOOLEAN operator <>
IN aLeft
^ IN aRight
%SUMMARY 'Правосторонний, а не обратный польский !=' ;
Result := ( aLeft = ( aRight DO ) ! )
; //<>
Комментировать не буду. Отмечу лишь, что операция
! - это
постфиксное отрицание.
Пойдём далее.
Тот факт, что передаётся ссылка на слово, а не значение означает то, что если в качестве слова падали переменную, то мы можем писать в неё.
Реализуем например методы инкремента и декремента.
Так как они описаны в аксиоматике:
VOID operator DEC
^ IN aWhatToDecrement
aWhatToDecrement DO // - разыменовываем переменную aWhatToDecrement
1 - // - вычитаем единицу
>>>^ aWhatToDecrement // - записываем значение туда куда указывает aWhatToDecrement
; // DEC
VOID operator INC
^ IN aWhatToIncrement
aWhatToIncrement DO // - разыменовываем переменную aWhatToDecrement
1 + // - прибавляем единицу
>>>^ aWhatToIncrement // - записываем значение туда куда указывает aWhatToIncrement
; // INC
И вызов:
INTEGER VAR A // - определяем целочисленную переменную A
0 >>> A // - инициализируем её нулём
A . // - печатаем
INC A // - увеличиваем A на единицу
A . // - печатаем
DEC A // - уменьшаем A на единицу
A . // - печатаем
Понятное дело, что если мы напишем Inc 1, то мы получим ошибку если не
компиляции, то времени исполнения.
Ну и предположим нам надо описать методы IncBy и DecBy.
Вот они:
VOID operator DecBy
^ IN aWhatToDecrement
^ IN aDelta
aWhatToDecrement DO // - разыменовываем переменную aWhatToDecrement
aDelta DO // - разыменовываем переменную aDelta
- // - вычитаем
>>>^ aWhatToDecrement // - записываем значение туда куда указывает aWhatToDecrement
; // DecBy
VOID operator IncBy
^ IN aWhatToIncrement
^ IN aDelta
aWhatToIncrement DO // - разыменовываем переменную aWhatToDecrement
aDelta DO // - разыменовываем переменную aDelta
+ // - прибавляем
>>>^ aWhatToIncrement // - записываем значение туда куда указывает aWhatToIncrement
; // IncBy
И вызов:
INTEGER VAR A // - определяем целочисленную переменную A
0 >>> A // - инициализируем её нулём
A . // - печатаем
IncBy A 2 // - увеличиваем A на 2
A . // - печатаем
DecBy A 2 // - уменьшаем A на 2
A . // - печатаем
Пойдём далее.
Параметры справа также удобно использовать для обращения к
лямбда-выражениям.
Приведу пример:
: Iteration
^ IN aLambda
0 // - начальное значение
1 aLambda DO
2 aLambda DO
3 aLambda DO
4 aLambda DO
5 aLambda DO
6 aLambda DO
7 aLambda DO
8 aLambda DO
9 aLambda DO
10 aLambda DO
; // Iteration
// Вызов:
Iteration ( IN A IN B A B + ) . // - просуммирует числа от 0 до 10 и напечатает сумму
// Или короче:
Iteration + . // - просуммирует числа от 0 до 10 и напечатает сумму
Можно вынести начальное значение за скобки:
: Iteration
^ IN aLambda
1 aLambda DO
2 aLambda DO
3 aLambda DO
4 aLambda DO
5 aLambda DO
6 aLambda DO
7 aLambda DO
8 aLambda DO
9 aLambda DO
10 aLambda DO
; // Iteration
// Вызов:
0 Iteration ( IN A IN B A B + ) . // - просуммирует числа от 0 до 10 и напечатает сумму
// Или короче:
0 Iteration + . // - просуммирует числа от 0 до 10 и напечатает сумму
1 Iteration * . // - перемножит числа от 1 до 10 и напечатает произведение
Также можно использовать
массивы и
итерацию по ним:
: Iteration
^ IN aLambda
[ 1 2 3 4 5 6 7 8 9 10 ] .for> ( aLambda DO )
; // Iteration
// Вызов:
0 Iteration ( IN A IN B A B + ) . // - просуммирует числа от 0 до 10 и напечатает сумму
// Или короче:
0 Iteration + . // - просуммирует числа от 0 до 10 и напечатает сумму
1 Iteration * . // - перемножит числа от 1 до 10 и напечатает произведение
Подведём итоги.
Мы разобрали
параметры справа. Их
разыменование.
Также мы разобрали запись значений в те переменные на которые указывают параметры справа.
Также мы рассмотрели как параметры справа могут использоваться для лямбда-выражений.
Также мы немного коснулись массивов и итерации по ним.
В следующей статье мы разберём
параметры слева передаваемые
по ссылке и рассмотрим как например реализовать операции такие как
+= -= и т.п.
Надеюсь, что данная статья была вам полезна.