Набор библиотек для Рефала-5

Библиотека LibraryEx

Библиотека удобных вспомогательных функций для Рефала-5.

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

Примечание. Есть одноимённая библиотека LibraryEx в Рефале-5λ, эта библиотека была написана на основе неё. На данный момент библиотеке Рефала-5λ имеется гораздо больше функций, не все из которых перенесены в эту библиотеку и не все совместимы с чистым Рефалом-5.

Утилитарные функции

Пустые функции Success и Fails для Рефала-05

*$EENUM Success, Fails

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

<SomeFunc e.SomeArgs>
  == Success e.SuccessfulResult
  == Fails e.ErrorInfo

Функция LoadFile

<LoadFile e.FileName> == e.Lines

e.Lines ::= (e.Line)*
e.Line ::= s.CHAR*

Функция загружает содержимого текстового файла с заданным именем как последовательность строк. Функция одинаково корректно работает с файлами, последняя строка которых как заканчивается на символ перевода строки, так и не заканчивается. Первый случай некоторые текстовые редакторы трактуют как пустую строку в конце — функция LoadFile пустую строку в конец не добавляет.

Функция использует дескриптор файла 39.

Функция SaveFile

<SaveFile (e.FileName) e.Lines>

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

Функция использует дескриптор файла 39.

Функция Inc и Dec

<Inc e.LongNumber> == e.LongNumber
<Dec e.LongNumber> == e.LongNumber

Функции, соответственно, вычисляют значение на единицу большее и меньшее своего аргумента. Запись <Inc s.X> компактнее, чем <Add s.X 1>, поэтому они добавлены в библиотеку.

Функция ArgList

<ArgList> == (e.Arg)+
e.Arg ::= s.CHAR*

Возвращает список аргументов командной строки. Первый скобочный терм — имя запущенной программы. Поскольку встроенная функция Arg не может отличить пустой аргумент от последнего (при обращении к аргументу с номером, большим, чем последний, возвращается пустая строка), функция ArgList перечисляет аргументы до первого пустого.

Функции Trim, Trim-L, Trim-R

<Trim s.CHAR*> == s.CHAR*
<Trim-L s.CHAR*> == s.CHAR*
<Trim-R s.CHAR*> == s.CHAR*

Отбрасывают символы пустого пространства от начала и конца строки (Trim), только от начала (Trim-L) и только от конца (Trim-R). К символам пустого пространства относятся пробелы, табуляции (\t), возвраты каретки (\r) и переводы строк (\n).

Функция OneOf

<OneOf t.Elem e.SampleElems> == True | False

Возвращает истину, если t.Elem присутствует в e.SampleElems и ложь в противоположном случае.

Функция Unique

<Unique t.Item*> == t.Item*

Удаляет дублирующиеся термы из аргумента.

Функции высшего порядка

Замыкание (t.Closure), функция Apply

t.Closure ::=
    s.WORD
  | (t.Closure e.Bounded)

<Apply t.Closure e.Arg> == e.Res

e.Bounded, e.Arg, e.Res ::= e.AnyExpr

Все функции высшего порядка принимают объект «замыкание», который может быть либо составным символом, либо каррированной функцией. Каррированная функция представляет собой скобочный терм, первым термом содержимого которого является другое замыкание, остальная часть — связанный аргумент.

Например, (Add 3) — замыкание, в котором функция сложения связана с первым аргументом. (Map (Add 3)) — функция, которая принимает последовательность чисел и к каждому прибавляет 3 (функция Map чуть ниже по тексту).

Для вызова замыкания используется функция Apply:

<Apply Add 1 2>                   → 3
<Apply (Add 3) 10>                → 13
<Apply (Map (Add 3)) 100 200 300> → 103 203 303

Функция Apply для вызова замыкания использует Mu. Остальные функции высшего порядка используют уже Apply для вызова замыканий, а значит, косвенно используют Mu.

Примечание. Рефал-5λ поддерживает функции высшего порядка — указатели на функциии и безымянные функции («лямбды»), их тоже можно вызывать посредством функции Mu, поэтому их тоже можно использовать в упомянутых ниже функциях.

В Рефале-5λ тоже есть библиотека LibraryEx и она совместима с LibraryEx настоящего набора библиотек, однако имеет некоторые расширения (не описанные здесь функции). Рекомендуем обратиться к библиотеке или исходникам LibraryEx Рефала-5λ для получения дополнительных подробностей.

Примечание. Имя функции для Mu можно указывать не только символом-словом, но и последовательностью литер в скобках. Поэтому если нужно вызвать при помощи Apply функцию, заданную цепочкой литер, следует использовать замыкание (Mu (e.FuncName)).

Функция Map

<Map t.Closure t.Item*> == e.ItemRes*

<Apply t.Closure t.Item> == e.ItemRes

Функция применяет t.Closure к t.Item* слева-направо. Заметим, что в отличие от других языков программирования, функция Map в Рефале более универсальна. Замыкание может не возвращать никакое значение (возвращать пустую строку), и в итоге весь вызов Map будет возвращать пустоту. Функция Map также может осуществлять фильтрацию, если замыкание возвращает либо свой аргумент как есть, если терм «подходит», либо возвращает пустую строку, если терм «не подходит».

Пример. Преобразование каждого числа в строковую форму с добавлением точки с запятой в конце:

/*
  <CSVLine s.NUMBER*> == s.CHAR*
*/
CSVLine {
  e.Numbers = <Map map_example_FormatNumber e.Numbers>;
}

$ENTRY map_example_FormatNumbers {
  s.Number = <Symb s.Number> ';';
}

Функция распечатывает строчки чисел по одной на строке

/*
  <CSVTable (s.NUMBER*)*> == пусто
*/
CSVTable {
  e.Lines = <Map map_example_PutLine e.Lines>;
}

$ENTRY map_example_PutLine {
  (e.Numbers) = <Prout <CSVLine e.Numbers>>;
}

Функция удаляет пустые строки:

/*
  <RemoveEmpty e.Lines> == e.Lines
  e.Lines ::= (s.CHAR*)*
*/
RemoveEmpty {
  e.Lines = <Map map_example_CheckEmpty e.Lines>;
}

$ENTRY map_example_CheckEmpty {
  (/* пусто */) = /* пропускаем */;

  (e.Line) = (e.Line); /* оставляем */
}

Функция Reduce

<Reduce t.Closure t.Accum t.Item*> == t.Accum′

<Apply t.Closure t.Accum t.Item> == t.Accum′
t.Accum′ ::= t.Accum

Функция реализует левую свёртку, т.е. применяет замыкание t.Closure к паре из аккумулятора t.Accum и очередного терма t.Item слева-направо, результат выполнения замыкания должен быть ровно одним термом — новым значением аккумулятора. Если замыкание возвращает не один терм (а, например, пустую строку или несколько термов), поведение функции не определено. Возвращаемое значение — аккумулятор после свёртки последнего терма.

Пример. Хеш-функция:

/*
  <Hash s.Value*> == s.NUMBER
  s.Value ::= s.NUMBER | s.CHAR
*/
Hash {
  e.InputString = <Reduce reduce_example_HashOne 43 <Ord e.InputString>>;
}

$ENTRY reduce_example_HashOne {
  s.Hash s.Number
    , <Add <Mul s.Hash 37> s.Number> : e.Overflow s.LastDigit
    = s.LastDigit;
}

Функция MapAccum

<MapAccum t.Closure t.Accum t.Item*> == t.Accum′ e.ItemRes*

<Apply t.Closure t.Accum t.Item> == t.Accum′ e.ItemRes
t.Accum′ ::= t.Accum

Функция применяет замыкание t.Closure к каждому терму из t.Item* с использованием состояния, сохранённого в аккумуляторе t.Accum. Возвращаемое значение замыкания — новое содержимое аккумулятора и результат преобразования очередного терма. Результат всей функции — последнее значение аккумулятора и конкатенация результатов трансформации термов.

Пример. Функция, которая отделяет агнцев от козлищ.

*$ENUM Lamb /* агнец */, Goat /* козлище */

/*
  <Separate t.Animal*> == (t.Lamb*) t.Goat*
  t.Animal ::=
      (Lamb t.Lamb)
    | (Goat t.Goat)
*/
Separate {
  e.Animals =
    <MapAccum mapaccum_example_Separate (/* lambs */) e.Animals>;
}

$ENTRY mapaccum_example_Separate {
  (e.Lambs) (Lamb t.Lamb) = (e.Lambs t.Lamb) /* пусто */;

  (e.Lambs) (Goat t.Goat) = (e.Lambs) t.Goat;
}

Здесь функция mapaccum_example_Separate кладёт ягнёнка в аккумулятор и оставляет пустоту как результат преобразования. Козла же в аккумулятор она не кладёт, оставляя его как результат. В итоге после завершения перебора в аккумуляторе остаются ягнята, козлы остаются снаружи.

Другой пример в функции DelAccumulator.

Функция DelAccumulator

<DelAccumulator t.Accum e.AnyExpr> == e.AnyExpr

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

Формально это, конечно, функция первого порядка, но она предназначена для совместного использования с функцией MapAccum, поэтому рассматривается в этом подразделе.

Пример. Функция, которая добавляет номер в начало каждой строки.

/*
  <Enumerate (e.Line)*> == (s.NUMBER e.Line)*
*/
Enumerate {
  e.Lines =
    <DelAccumulator
      <MapAccum mapaccum_example_AddNumber 1 e.Lines>
    >;
}

$ENTRY mapaccum_example_AddNumber {
  s.LineNo (e.Line) = <Inc s.LineNo> (s.LineNo e.Line);
}

После завершения прохода по строкам функция MapAccum оставит в поле зрения пронумерованные строки и число, на единицу большее номера последней строки. Это число не нужно, его отбрасываем при помощи DelAccumulator.