Функції в GAP

Last updated on 2026-04-27 | Edit this page

Estimated time: 55 minutes

Overview

Questions

  • Функції як спосіб повторного використання коду

Objectives

  • Використання командного рядка для прототипування
  • Створення функцій
  • Читання GAP-коду з файлу

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

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

GAP

S:=SymmetricGroup(10);

OUTPUT

Sym( [ 1 .. 10 ] )

GAP

sum:=0;

OUTPUT

0

GAP

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

OUTPUT

39020911/3628800

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

GAP

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

OUTPUT

39020911/3628800

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

GAP

S:=AlternatingGroup(10);

OUTPUT

Alt( [ 1 .. 10 ] )

GAP

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

OUTPUT

2587393/259200
Callout

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

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

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

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

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

GAP

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

OUTPUT

function( G ) ... end

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

GAP

A:=AlternatingGroup(10); AvgOrdOfGroup(A); time;

OUTPUT

Alt( [ 1 .. 10 ] )
2587393/259200
837

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

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

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

GAP

#####################################################################
#
# AvgOrdOfGroup(G)
#
# Calculating the average order of an element of G, where G meant to
# be a group but in fact may be any collection of objects having
# multiplicative order
#
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):

GAP

M11:=MathieuGroup(11);

OUTPUT

Group([ (1,2,3,4,5,6,7,8,9,10,11), (3,7,11,8)(4,10,5,6) ])

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

GAP

AvgOrdOfGroup(M11);

ERROR

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

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

GAP

Read("avgord.g");

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

GAP

AvgOrdOfGroup(M11);

OUTPUT

53131/7920

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

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

GAP

return sum/Size(G);

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

GAP

return Float(sum/Size(G);

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

GAP

Read("avgord.g");

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

ERROR

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

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

GAP

Print(AvgOrdOfGroup);

OUTPUT

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

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

GAP

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

OUTPUT

6.70846

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

GAP

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

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

GAP

Read("avgord.g");

ERROR

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), який також відображатиме будь-які коментарі):

GAP

Print(AvgOrdOfGroup);

OUTPUT

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

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

GAP

AvgOrdOfGroup(M11);

ERROR

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;.

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

GAP

sum:=2^64; g:=[1];

OUTPUT

18446744073709551616
[ 1 ]

GAP

AvgOrdOfGroup(M11);

OUTPUT

18446744073709604747/7920

GAP

sum; g;

OUTPUT

18446744073709604747
(1,2)(3,10,5,6,8,9)(4,7,11)

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

GAP

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

Шляхи

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

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

Key Points
  • Командний рядок добре підходить для прототипування; функції підходять для повторних обчислень.
  • Інформативні назви функцій і коментарі зроблять код більш читабельним для вас і інших.
  • Остерігайтеся неоголошених локальних змінних!