Функції в 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 два або чотири рази.
Ключові моменти
Командний рядок добре підходить для прототипування; функції підходять для повторних обчислень.
Інформативні назви функцій і коментарі зроблять код більш читабельним для вас і інших.
Остерігайтеся неоголошених локальних змінних!