Функції в GAP

Огляд

Викладання: 40 хв
Вправи: 15 хв
Питання
  • Функції як спосіб повторного використання коду

Цілі
  • Використання командного рядка для прототипування

  • Створення функцій

  • Читання GAP-коду з файлу

Просто нагадаємо наше завдання: для скінченної групи G ми хотіли б обчислити середній порядок її елементів (тобто суму порядків її елементів, поділену на порядок групи).

Ми починаємо з дуже прямого підходу, повторюючи всі елементи групи, про яку йдеться:

S:=SymmetricGroup(10);
Sym( [ 1 .. 10 ] )
sum:=0;
0
for g in S do
  sum := sum + Order(g);
od;
sum/Size(S);
39020911/3628800

Тепер припустімо, що ми хочемо зберегти цей фрагмент коду GAP і пізніше повторити цей розрахунок для деяких інших груп. Ми навіть можемо переформатувати його, щоб помістити його в один рядок, і використати подвійну крапку з комою, щоб закоментувати вивід sum:

sum:=0;; for g in S do sum := sum + Order(g); od; sum/Size(S);
39020911/3628800

Тепер ми можемо легко скопіювати та вставити його в сеанс GAP наступного разу, коли він нам знадобиться. Але тут ми бачимо першу незручність: код очікує, що група, про яку йде мова, має бути збережена в змінній з іменем S, тому або ми повинні скидати S кожного разу, або нам потрібно редагувати код:

S:=AlternatingGroup(10);
Alt( [ 1 .. 10 ] )
sum:=0;; for g in S do sum := sum + Order(g); od; sum/Size(S);
2587393/259200

Це працює лише для швидкого прототипування

  • можна випадково скопіювати та вставити лише частину коду, а неповне введення може викликати цикл розриву;
  • ще небезпечніше: можна забути скинути sum на нуль перед новим обчисленням і отримати неправильні результати;
  • група, про яку йде мова, може мати іншу назву змінної, тому код доведеться змінити;
  • останнє, але не менш важливе: коли код GAP вставляється в інтерпретатор, він оцінюється рядок за рядком. Якщо у вас є довгий файл із багатьма командами, а синтаксична помилка міститься в рядку N, про цю помилку буде повідомлено лише тоді, коли GAP завершить оцінку всіх попередніх рядків, а це може зайняти досить багато часу.

Ось чому нам потрібно надати нашому коду GAP більше структури, організувавши його у функції:

Наступна функція приймає аргумент G і обчислює середній порядок його елементів:

AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
  sum := sum + Order(g);
od;
return sum/Size(G);
end;
function( G ) ... end

Тепер ми можемо застосувати це до іншої групи, передавши групу як аргумент:

A:=AlternatingGroup(10); AvgOrdOfGroup(A); time;
Alt( [ 1 .. 10 ] )
2587393/259200
837

Наведений вище приклад також демонструє time - це змінна, яка зберігає час процесора в мілісекундах, витрачений на виконання останньої команди.

Таким чином, тепер ми можемо створювати нові групи та повторно використовувати AvgOrdOfGroup для обчислення середнього порядку їхніх елементів у тому самому сеансі GAP. Наша наступна мета — зробити цю функцію придатною для повторного використання для обчислень у майбутніх сесіях.

Використовуючи текстовий редактор (наприклад, той, який ви, можливо, використовували на попередніх уроках Software Carpentry), створіть текстовий файл під назвою avgord.g, що містить наступний код функції та коментарі (хороший шанс попрактикуватися в їх використанні!):

#####################################################################
#
# AvgOrdOfGroup(G)
#
# Обчислення середнього порядку елемента G, де G 
# означає групу, але насправді може бути будь-якою колекцією об’єктів із
# мультиплікативним порядком
#
AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
  sum := sum + Order(g);
od;
return sum/Size(G);
end;

Тепер почніть новий сеанс GAP і створіть іншу групу, наприклад, MathieuGroup(11):

M11:=MathieuGroup(11);
Group([ (1,2,3,4,5,6,7,8,9,10,11), (3,7,11,8)(4,10,5,6) ])

Очевидно, що AvgOrdOfGroup не визначено в цьому сеансі, тому спроба викликати цю функцію призводить до помилки:

AvgOrdOfGroup(M11);
Error, Variable: 'AvgOrdOfGroup' must have a value
not in any function at line 2 of *stdin*

Щоб бути доступною, її потрібно спочатку завантажити за допомогою функції Read. Нижче ми припускаємо, що файл знаходиться в поточному каталозі, тому шлях не потрібен.

Read("avgord.g");

Це завантажує файл у GAP, і функція AvgOrdOfGroup тепер доступна:

AvgOrdOfGroup(M11);
53131/7920

У цьому прикладі, використовуючи Read, було розпочато новий сеанс GAP, щоб було зрозуміло, що AvgOrdOfGroup не існувало до виклику Read і було завантажено з файлу. Однак файл із такою функцією можна прочитати кілька разів під час одного сеансу GAP (пізніше ви побачите випадки, коли повторне читання файлу є складнішим). Це означає, що якщо код функції було змінено і в ньому немає помилок (але, можливо, є попередження), функція буде перезаписана. Ніколи не ігноруйте попередження!

Наприклад, давайте відредагуємо файл і замінимо рядок

return sum/Size(G);

рядком із навмисною синтаксичною помилкою:

return Float(sum/Size(G);

Тепер прочитайте цей файл

Read("avgord.g");

і Ви побачите повідомлення про помилку:

Syntax error: ) expected in avgord.g line 7
return Float(sum/Size(G);
                        ^

Оскільки сталася помилка, функція AvgOrdOfGroup у нашому сеансі не була перевизначена та залишається такою ж, як і минулого разу, коли її успішно прочитали:

Print(AvgOrdOfGroup);
function ( G )
    for g  in G  do
        sum := sum + Order( g );
    od;
    return sum / Size( G );
end

Тепер виправте помилку, додавши відсутню закриваючу дужку, прочитайте файл ще раз і перерахуйте середній порядок елемента для M11:

Read("avgord.g");
AvgOrdOfGroup(M11);
6.70846

Тепер давайте розглянемо приклад попередження. Оскільки це лише попередження, це призведе до перевизначення функції, і це може призвести до несподіваного результату. Щоб побачити, що може статися, спочатку відредагуйте файл, щоб відкотити зміни в типі результату (тобто він повертатиме раціональне значення замість числа з плаваючою точкою), а потім закоментуйте два рядки наступним чином:

AvgOrdOfGroup := function(G)
# local sum, g;
# sum := 0;
for g in G do
  sum := sum + Order(g);
od;
return sum/Size(G);
end;

Тепер, коли ви прочитаєте файл, ви побачите попередження:

Read("avgord.g");
Syntax error: warning: unbound global variable in avgord.g line 4
for g in G do
       ^
Syntax error: warning: unbound global variable in avgord.g line 5
  sum := sum + Order(g);
       ^
Syntax error: warning: unbound global variable in avgord.g line 5
  sum := sum + Order(g);
             ^
Syntax error: warning: unbound global variable in avgord.g line 7
return sum/Size(G);
          ^

Ці попередження означають, що оскільки g і sum не оголошено як локальні змінні, GAP очікує, що вони будуть глобальними змінними під час виклику функції. Оскільки вони не існували під час виклику Read, відображалося попередження. Однак, якби вони існували до того моменту, попередження не було б, і будь-який виклик AvgOrdOfGroup перезаписав би їх! Це показує, наскільки важливим є оголошення локальних змінних. Давайте розберемося трохи детальніше, що сталося:

Функцію тепер перевизначено, як ми бачимо з її виводу (або перевіряємо за допомогою PageSource(AvgOrdOfGroup), який також відображатиме будь-які коментарі):

Print(AvgOrdOfGroup);
function ( G )
    for g in G  do
        sum := sum + Order( g );
    od;
    return sum / Size( G );
end

але спроба запустити його призводить до розриву циклу:

AvgOrdOfGroup(M11);
Error, Variable: 'sum' must have an assigned value in
  sum := sum + Order( g ); called from
<function "AvgOrdOfGroup">( <arguments> )
 called from read-eval loop at line 24 of *stdin*
you can 'return;' after assigning a value
brk>

з якого можна вийти за допомогою quit;.

Те, що відбувається далі, демонструє, як все може піти не так:

sum:=2^64; g:=[1];
18446744073709551616
[ 1 ]
AvgOrdOfGroup(M11);
18446744073709604747/7920
sum; g;
18446744073709604747
(1,2)(3,10,5,6,8,9)(4,7,11)

Тепер, перш ніж читати наступну частину уроку, будь ласка, скасуйте останню зміну, розкоментувавши два рядки з коментарями, щоб у вас знову була початкова версія AvgOrdOfGroup у файлі avgord.g:

AvgOrdOfGroup := function(G)
local sum, g;
sum := 0;
for g in G do
  sum := sum + Order(g);
od;
return sum/Size(G);
end;

Шляхи

  • Важливо знати, як вказувати шляхи до файлів у всіх операційних системах і де знайти свій домашній і поточний каталог.

  • Корисно знати, що завершення шляху та імені файлу активується натисканням Esc два або чотири рази.

Ключові моменти

  • Командний рядок добре підходить для прототипування; функції підходять для повторних обчислень.

  • Інформативні назви функцій і коментарі зроблять код більш читабельним для вас і інших.

  • Остерігайтеся неоголошених локальних змінних!