четверг, 24 декабря 2015 г.

#1167. :, PROCEDURE, FUNCTION. Параметры справа и слева.Часть 2

Предыдущая серия была тут - #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 и напечатает произведение

Подведём итоги.

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

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

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

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

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

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

4 комментария:

  1. Спасибо огромное за статьи, именно то что нужно. Все четко и по делу. Очень интересно. Не забудьте добавить их в базу знаний, сильно помогает в восприятии.
    Вопросы у меня лично остались только по пространствам имен(словари; как работать с глобальным и локальным пространством имен; на что влияет декларация типов при объявлении) в Word'ах. Многое уже описано в предыдущих статьях(точно есть работа со свойствами Word'a снаружи), буду разбираться.
    Попробую сделать свою реализацию на коленке, поиграться. Тема для меня правда интересная.

    ОтветитьУдалить
    Ответы
    1. Пожалуйста. Вскорости напишу ещё.

      Непонятно только зачем что-то своё пытаться делать. Скриптовая машина тем и хороша, что она готовая есть.

      Ну это как говорится - каждый "заблуждается" так как сам того хочет :-)

      Удалить
    2. своё делать для практики. Никто и не говорит о полноценной машине, так - немного кода, что бы поиграться и вникнуть в суть. Я же не собираюсь это на практике использовать, когда есть готовое решение..

      Удалить