Wiki

Clone wiki

MindStream / Статьи на русском / Устройство скриптовой машины / PROCEDURE, FUNCTION. Параметры справа и слева.Часть 2

Предыдущая серия была тут - . :, PROCEDURE, FUNCTION. Параметры справа и слева.Часть 1.

Там мы рассмотрели ключевые слова :, ;, FUNCTION, PROCEDURE.

А также "параметры слева".

Рассмотрим теперь "параметры справа".

Пусть у нас есть пример с "параметрами слева":

#!delphi
INTEGER FUNCTION Plus
 INTEGER IN A
 INTEGER IN B
 A B + >>> Result // - складываем A и B и помещаем в Result
; // Plus

1 2 Plus . // - вызываем нашу функцию и печатаем результат
Тут мы имеет "типичную" ОПЗ.

А что делать, если мы хотим пользоваться инфиксной нотацией?

Вот тут нам помогут параметры справа.

Перепишем наш пример с использованием параметров справа:

#!delphi
INTEGER FUNCTION Plus
 INTEGER IN A // - параметр слева
 ^ IN B // - параметр СПРАВА передаётся по ССЫЛКЕ, а не по ЗНАЧЕНИЮ. 
        //   Его надо разыменовывать.
 A // - значение параметра A
 B DO // - разыменовываем значение B. Т.е. зовём метод DO на том слове на которое указывает B
 + >>> Result // - складываем A и B и помещаем в Result
; // Plus

1 Plus 2 . // - вызываем нашу функцию ИНФИКСНО и печатаем результат
Подчеркну, что параметры справа передаются по ссылке.

Также можно написать:

#!delphi
1 Plus ( 2 Plus ( 3 Plus 4 ) ) .
Скобки пока обязательны.

Как обойтись без скобок - напишу отдельно.

Также наш пример можно переписать так:

#!delphi
INTEGER FUNCTION Plus
 INTEGER IN A // - параметр слева
 ^ IN B // - параметр СПРАВА передаётся по ССЫЛКЕ, а не по ЗНАЧЕНИЮ. 
        //   Его надо разыменовывать.
 A // - значение параметра A
 B |^ // - разыменовываем значение B. Т.е. зовём метод |^ на том слове на которое указывает B
 + >>> Result // - складываем A и B и помещаем в Result
; // Plus

1 Plus 2 . // - вызываем нашу функцию ИНФИКСНО и печатаем результат
Тут используется |^ вместо DO.

Они вообще говоря равноценны.

Про отличия я напишу несколько позже.

Метод |^ в аксиоматике определяется так:

#!delphi
: |^
  ^@ IN aRef

 %SUMMARY 'Разыменовывает параметр слева' ;
 aRef pop:Word:GetRef DO
; // |^
Детали реализации |^ я также опишу позже.

Но пока отмечу, что |^ использует DO. Т.е. |^ является производным от DO.

Пойдём далее.

Зачем параметры справа передаются по ссылке,а не по значению?

Тому есть много причин.

В частности - "ленивые вычисления".

Рассмотрим реализацию булевских операций AND и OR.

Вот она:

#!delphi
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 определён как:

#!delphi
WordAlias operator :
WordAlias OPERATOR :
И что мы получаем с ленивыми вычислениями?

Если написать без ленивых вычислений:

#!delphi
if ( ( anObject <> nil ) ( anObject .SomeMethod ) && ) then

То получим Access Violation.

А с ленивыми вычислениями:

#!delphi
if ( ( anObject <> nil ) AND ( anObject .SomeMethod ) ) then
Access Violation - не будет.

Надеюсь - понятно почему.

Операция <> кстати тоже определена в базовой аксиоматике при помощи правых и левых параметров. И через операцию =.

Вот так:

#!delphi
BOOLEAN operator <>
  IN aLeft
  ^ IN aRight
 %SUMMARY 'Правосторонний, а не обратный польский !=' ;
 Result := ( aLeft = ( aRight DO ) ! )
; //<>
Комментировать не буду. Отмечу лишь, что операция ! - это постфиксное отрицание.

Пойдём далее.

Тот факт, что передаётся ссылка на слово, а не значение означает то, что если в качестве слова падали переменную, то мы можем писать в неё.

Реализуем например методы инкремента и декремента.

Так как они описаны в аксиоматике:

#!delphi
VOID operator DEC
  ^ IN aWhatToDecrement
 aWhatToDecrement DO // - разыменовываем переменную aWhatToDecrement
 1 - // - вычитаем единицу
 >>>^ aWhatToDecrement // - записываем значение туда куда указывает aWhatToDecrement
; // DEC

VOID operator INC
  ^ IN aWhatToIncrement
 aWhatToIncrement DO // - разыменовываем переменную aWhatToDecrement
 1 + // - прибавляем единицу
 >>>^ aWhatToIncrement // - записываем значение туда куда указывает aWhatToIncrement
; // INC
И вызов:

#!delphi
INTEGER VAR A // - определяем целочисленную переменную A
0 >>> A // - инициализируем её нулём
A . // - печатаем
INC A // - увеличиваем A на единицу
A . // - печатаем
DEC A // - уменьшаем A на единицу
A . // - печатаем
Понятное дело, что если мы напишем Inc 1, то мы получим ошибку если не компиляции, то времени исполнения.

Ну и предположим нам надо описать методы IncBy и DecBy.

Вот они:

#!delphi
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
И вызов:

#!delphi
INTEGER VAR A // - определяем целочисленную переменную A
0 >>> A // - инициализируем её нулём
A . // - печатаем
IncBy A 2 // - увеличиваем A на 2
A . // - печатаем
DecBy A 2 // - уменьшаем A на 2
A . // - печатаем
Пойдём далее.

Параметры справа также удобно использовать для обращения к лямбда-выражениям.

Приведу пример:

#!delphi
: 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 и напечатает сумму
Можно вынести начальное значение за скобки:

#!delphi
: 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 и напечатает произведение
Также можно использовать массивы и итерацию по ним:
#!delphi
: 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 и напечатает произведение
Подведём итоги.

Мы разобрали параметры справа. Их разыменование.

Также мы разобрали запись значений в те переменные на которые указывают параметры справа.

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

Также мы немного коснулись массивов и итерации по ним.

В следующей статье мы разберём параметры слева передаваемые по ссылке и рассмотрим как например реализовать операции такие как += -= и т.п.

Надеюсь, что данная статья была вам полезна.

Updated