Magma 入門




# はじめに

この記事は，代数学などの問題を解くのに適した数式処理システム「Magma」を新たに始める方向けの入門記事です．

Magmaには[公式ハンドブック（オンライン）][of-full]や簡単な[入門書][of-first]などの，豊富なドキュメント（その他は[こちら](http://magma.maths.usyd.edu.au/magma/documentation/)を参照）が用意されていますが，いずれも**英語**で書かれており，初学者（私も含む）にはかなりとっつきにくいことでしょう．
そこで，Magmaを始めるにあたって最も基礎的な部分，特に一般的なプログラミング言語に則した部分を日本語で学習できるようにこの記事にまとめました！
私もMagmaに関しては初心者ですので，ご一緒に学習していきましょう！

なお，こちらの[紹介記事][harada]も大変参考になるので，是非ご一読ください！本記事も，このサイトを参考にしております．

また，他の数式処理システムも考えている方は，次のサイトが参考になるかと思います．

- [数式処理システムの一覧 - Wikipedia](https://ja.wikipedia.org/wiki/%E6%95%B0%E5%BC%8F%E5%87%A6%E7%90%86%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E4%B8%80%E8%A6%A7)
- [数式処理システム比較表](https://booksproj.blogspot.com/p/blog-page_55.html?m=1)


# 目次
- [Magmaとは](#magmaとは)
- [Step 1. まずは出力](#step-1-まずは出力)
- [Step 2. 変数を使おう](#step-2-変数を使おう)
- [Step 3. 基本的な演算子](#step-3-基本的な演算子)
    - [四則演算子ほか](#四則演算子ほか)
    - [比較演算子](#比較演算子)
    - [論理・集合演算子](#論理集合演算子)
    - [大型演算子](#大型演算子)
- [Step 4. 比較と繰り返し](#step-4-比較と繰り返し)
    - [早見表](#早見表)
    - [`if`文](#if文)
    - [三項演算子(`select`文)](#三項演算子select文)
    - [`case`文](#case文)
    - [`for`文](#for文)
    - [`while`文](#while文)
    - [`repeat`文](#repeat文)
- [Step 5. 関数](#step-5-関数)
    - [`function`構文](#function構文)
    - [`func`文](#func文)
    - [`procedure`構文](#procedure構文)
    - [`proc`文](#proc文)
    - [関数名と前方宣言](#関数名と前方宣言)
    - [型とストラクチャについて](#型とストラクチャについて)
- [Step 6. リスト・数列・集合など](#step-6-リスト数列集合など)
    - [基本書式](#基本書式)
        - [数列 Sequences](#数列-sequences)
        - [リスト Lists](#リスト-lists)
        - [集合 Sets](#集合-sets)
        - [順序付き集合 Indexed Sets](#順序付き集合-indexed-sets)
        - [多重集合 Multisets](#多重集合-multisets)
    - [特別な表記](#特別な表記)
        - [リストや集合の個数](#リストや集合の個数)
        - [区間表記](#区間表記)
        - [内包表記](#内包表記)
    - [配列操作などに関する関数](#配列操作などに関する関数)
    - [その他のデータ構造](#その他のデータ構造)
        - [文字列](#文字列)
        - [タプル Tuples](#タプル-tuples)
        - [デカルト積（直積） Cartesian products](#デカルト積直積-cartesian-products)
        - [余積 Coproducts](#余積-coproducts)
        - [レコード（構造体） Records](#レコード構造体-records)
        - [連想配列 Associative arrays](#連想配列-associative-arrays)
        - [写像 Maps](#写像-maps)
- [Step 7. デバッグ・エラー処理・その他構文](#step-7-デバッグエラー処理その他構文)
    - [`assert`文](#assert文)
    - [`error`文](#error文)
    - [`try`-`catch`文](#try-catch文)
    - [`exists`文](#exists文)
    - [`forall`文](#forall文)
- [Step 8. 組み込み関数を使ってみよう](#step-8-組み込み関数を使ってみよう)
    - [素数判定](#素数判定)
    - [最大公約数（GCD）・最小公倍数（LCM）](#最大公約数gcd最小公倍数lcm)
    - [素因数分解](#素因数分解)
    - [ビット操作](#ビット操作)
        - [16進の入力（代入）・出力](#16進の入力代入出力)
        - [シフト操作](#シフト操作)
        - [ビットワイズ操作](#ビットワイズ操作)
    - [時間計測](#時間計測)
        - [`Cputime`関数](#cputime関数)
        - [`Realtime`関数](#realtime関数)
        - [`ClockCycles`関数](#clockcycles関数)
        - [`Time`関数](#time関数)
        - [`time`文](#time文)
- [おわりに](#おわりに)
- [参考サイト](#参考サイト)
- [関連リンク](#関連リンク)


# Magmaとは

前述の[紹介記事][harada]にて，次のように説明されています．

> （Magmaは）代数学の領域に特化して，その領域を深くカバーしたシステムなのである．

> 計算代数の最新の成果を貪欲に取り込むことで，それぞれの分野において，現在知られている最先端のアルゴリズムまでが実装されている．したがって，よほど複雑な計算をしない限り，通常計算できると知られているものの多くはあらかじめコマンドとして用意されていると考えて良い．

Magmaは「線形代数，加群，群論，可換群論（グレブナー基底を含む），環論，代数的整数論，表現論，代数幾何，数論幾何，保形形式，組合せ論，符号理論，暗号理論など」を扱うことができます．一方，「三角関数や指数関数などの関数を含んだ式の簡単化」や「導関数や不定積分を求めること」はできません．説明の通り，「代数学の領域に特化」した数式処理システムなのです．


さて，Magmaを実行するとなると，主に2つの方法が挙げられます．「Magmaを公式サイトからインストールする」，あるいは「公式のサービス[Magma Calculator][of-calc]を利用する」の2つです．
前者でMagmaの実行環境を構築するためには，（確かな情報ではありませんが）ライセンスが必要であり，また**有料**です．しかしこちらはターミナル上でMagmaを動かすことができ，さらに**インタラクティブに実行**することができます！もちろんプログラムファイルを読み込んで実行することもできます．
後者の場合ですと，オンラインの接続環境は必要ですが，**無料**でMagmaを実行させることができます．基本はブラウザ上での実行になりますが，それが面倒という方は私がコンソール上で行える[スクリプトファイル](https://qiita.com/usuke1726/private/5585bf1401933d361207)を作成しましたので，よろしければご活用ください．


# Step 1. まずは出力


Magmaを実行できる環境は整いましたか？まだの方はとりあえず[Magma Calculator][of-calc]を開きましょう．

これからMagmaの基本的な文法を説明していきます．
[マニュアル](http://magma.maths.usyd.edu.au/magma/handbook/part/1)に詳しく説明がありますので，余裕があったらぜひ読んでみてください．

まずは出力からです．

Magmaをインストールした方はターミナル上で

```
$ magma
```

と（単に`magma`です．先頭の`$`は不要です．）入力してMagmaを起動してください．（終了は `quit;` でできます．）
そうすれば，コマンドの実行が可能になります．Magma Calculatorを使用している方はページ上のテキストボックスにコマンドを入力していってください．

出力は，

```
print "Hello World!";
// Result: Hello World!
```

とします．コマンドは`print`で，引数として半角スペースをはさんで**必ずダブルクォーテーションで囲んで**文字列を指定してください．**シングルクォーテーションやバッククォーテーションを使用してはいけません．** また，コマンドの末尾に**必ずセミコロン`;`を記入する**ことを忘れてはいけません．
C言語と同じように，セミコロンはコマンドの**終わり**を表す役目を持ちます．セミコロンを入れないと，次のような場合にエラーが発生します．

```
print "Hello"
print "World"
/* Result:
>> print "World"
   ^
User error: bad syntax
*/
```

なお，コマンド`print`は**省略可能**です．つまり，

```
"Hello World!";
// Result: Hello World!
```

とするだけで出力が行われます．他にも`printf`とかも使えます．次の「補足」をご覧ください．

<details>
<summary>補足</summary>
<div>

- 次のような記法も可能です．

```
print("Hello World!");
// Result: Hello World!
```

- 文字列内にダブルクォーテーション記号を使用するときは，

```
"Hello \"World\"!";
// Result: Hello "World"!
```

とします．


- 他の様々な型のデータも出力可能です．例えば，数値を出力するなら，

```
print 2;
// Result: 2
```

```
print 3 + 5;
// Result: 8
```

あるいは

```
2;
// Result: 2
```

```
3 + 5;
// Result: 8
```

とします．

- 複数の引数をコンマ区切りで渡すことができます．出力は半角スペース区切りとなります．

```
print "Hello", 2, "World!";
// Result: Hello 2 World!
```

- 出力の末尾には改行が付されます．

```
print "Hello";
print 2;
"World!";
/* Result:
Hello
2
World!
*/
```

- `printf`文などもあります．

```
printf "%o ", 4;
printf "%-4o", 3;
printf "%o!\n", 1;
printf "%3o", 7;
/* Result:
4 3   1!
  7
*/
```

- コード例にもあるように，コメントアウトはC言語と同様`//`あるいは`/* ... */`で行います．今後もこの記事に掲載するコード例には実行結果をコメントアウトして載せていきます．

```
// 1行のみコメントアウト
/*
行をまたいで
コメントアウト
*/
```

</div>
</details>


# Step 2. 変数を使おう

他のプログラム言語と同じで，変数を導入することができます．
ただし，**代入はPascalなどと同じ`:=`を用います．**

```
a := 3;
print a;
a, a * 2;
/* Result:
3
3 6
*/
```

```
h := "Hello";
w := "World";
h, w, "!";
// Result: Hello World !
```

値の更新も同じく`:=`を使います．

```
i := 1;     print i; // -> 1
i := i + 1; print i; // -> 2
i := 3 * i; print i; // -> 6
```


<details>
<summary>補足</summary>
<div>

- 変数の型は`Type(変数)`で取得できます．型名は公式サイトの[ハンドブック][of-full]を参照するときに有用です！

```
a := true; Type(a); // -> BoolElt
a := 3;    Type(a); // -> RngIntElt
a := 2.4;  Type(a); // -> FldReElt
```

- 変数の消去や定義判定も行えます．

```
x := 2;
x;          // -> 2
assigned x; // -> true
delete x;
assigned x; // -> false
```

- 実際の変数に格納しないダミー変数 `_` への代入が可能です．これは，関数（Step 5を参照）からの複数の返り値を受け取る際に便利です．

```
_ := 3; // 何もしないのと同じ
f := func< | 1, 2, 3, 4>;
a, _, b, c := f();
a, b, c;
/* Result:
1 3 4
*/
```

- `where`句を用いると一時的な変数を使うことができます．

```
n := 6;
F := GF(n, 2) where n := 7;
F, n;
print m + n where m, n := func<|2, 3>();
"";
n, n where n := 0;
n, (n where n := 0);
/* Result:
Finite field of size 7^2
6
5

0 0
6 0
*/
```

</div>
</details>    

<details>
<summary>配列も使えます（後の章で詳述）</summary>
<div>

配列変数は，`a := [1, 2, 3];`などとして宣言します．要素は**1始まり**であることに注意してください！

```
a := [6, 6, 1, 8, 4, 2];
i := 4;
print a[i];
// Result: 8
```

なお，`a := {1, 2, 3};`とすると，`a`は集合を表すようになります（数学チックな書き方！）．

```
a := {1, 2, 3};
b := {4, 6, 4, 4};
print a, b;
/* Result:
{ 1, 2, 3 }
{ 4, 6 }
（改行で区切られます）
*/
```

</div>
</details>


# Step 3. 基本的な演算子

演算子は他の言語と似たようなものもあれば，大きく異なるようなものもあります．それらを見ていきましょう．

## 四則演算子ほか

|種類|記法|
|:-:|:-:|
|加算|`a + b`|
|減算|`a - b`|
|乗算|`a * b`|
|除算|`a / b`|
|べき乗|`a ^ b`|

ただし除算は「整数 / 整数」の場合に**有理数**として振舞います．
またオペランドがともに整数ならば，次の演算子`div`，`mod`も使用可能です．（それぞれC言語の`/`，`%`，つまり商と余りに相当します．）

<details>
    <summary>参考コード</summary>
<div>

```
7 * 3;      // -> 21
7.0 * 3;    // -> 21.0000000000000000000000000000
7 / 3;      // -> 7/3
7.0 / 3.0;  // -> 2.33333333333333333333333333333
7.0 / 3;    // -> 2.33333333333333333333333333333
7 ^ 3;      // -> 343
7.0 ^ 3;    // -> 343.000000000000000000000000000
7 ^ 0.5;    // -> 2.64575131106459059050161575364
7 div 3;    // -> 2
7 mod 3;    // -> 1
```

</div>
</details>

<details>
<summary>補足</summary>
<div>

- 四則演算子と代入を組み合わせた複合代入演算子が使用可能です．

```
x := 6;  x; // -> 6
x +:= 4; x; // -> 10
x /:= 5; x; // -> 2
x ^:= 3; x; // -> 8
```


</div>
</details>

## 比較演算子

基本的に他の演算子は**記号ではなく単語**で表現します．

|記法|C言語では|意味|
|:-:|:-:|:-:|
|`a eq b`|`a == b`|`a`と`b`が等価（**E**qual）|
|`a ne b`|`a != b`|`a`と`b`が異なる（**N**ot **E**qual）|
|`a lt b`|`a < b`|`a`が`b`より小さい（**L**ess **T**han）|
|`a le b`|`a <= b`|`a`が`b`以下である（**L**ess than or **E**qual）|
|`a gt b`|`a > b`|`a`が`b`より大きい（**G**reater **T**han）|
|`a ge b`|`a >= b`|`a`が`b`以上である（**G**reater than or **E**qual）|
|`a in R`|該当なし|`a`が`R`に属する|
|`a notin R`|該当なし|`a`が`R`に属さない|

なお，この比較結果（`BoolElt`型，"Elt"は"Element"の略？）を変数に格納することができます．

```
a := 4; b := 5;
res := a eq b;
print res; // -> false
```

<details>
<summary>補足</summary>
<div>

- エラーを考慮する等価比較`cmpeq`，`cmpne`もあります．2つのオペランドが比較可能なら`eq`，`ne`と同じですが，比較不可能の場合でもエラーを生じません．（比較不可能なら`cmpeq`は`false`を返し，`cmpne`は`true`を返します．）

```
1 eq 1;      // -> true
1 cmpeq 1;   // -> true
1 cmpeq "a"; // -> false
1 cmpne <4, 5>; // -> true
```

次の場合はエラーとなります．

```
1 eq "a";
/* Result:
>> 1 eq "a";
     ^
Runtime error in 'eq': Bad argument types
Argument types given: RngIntElt, MonStgElt
*/
```

- 文字列に対しても`eq`や`lt`などが適用可能です（辞書順で比較）．

```
"A" eq "A";      // -> true
"A" ne "a";      // -> true
"A" lt "a";      // -> true
"ABC" eq "ABC";  // -> true
"ABDA" gt "ABC"; // -> true
```
    
</div>
</details>

## 論理・集合演算子

同様に記号ではなく単語で表現します．

- 論理演算子

|記法|C言語では|意味|
|:-:|:-:|:-:|
|`not A`|`!A`|`A`でない（否定）|
|`A and B`|`A && B`|`A`かつ`B`（論理積）|
|`A or B`|<code>A &#124;&#124; B</code>|`A`または`B`（論理和）|
|`A xor B`|`A ^ B`|`A`と`B`の排他的論理和|

論理演算子は`BoolElt`型に対してのみ適用可能です．ビットワイズは兼ね備えていません．

<details>
<summary>補足</summary>
<div>

- `and` や `or` は**短絡評価**をサポートしています．例えば，`(条件1) and (条件2)`で条件1が`false`であった場合，条件2は評価されません．（条件2が`Runtime error`を起こすようなものであってもエラーは発生しません．）

```
function a()
    print "a";
    return false;
end function;
function b()
    print "b";
    return true;
end function;
print a() and b();
print "";
print b() and a();
/* Result:
a
false

b
a
false
*/
```

- ただし，無視されるのは `Runtime error`のときであり， `User error`の場合は短絡評価で評価されなくてもエラーになります．（ただし，`where`句の場合はちょっと挙動が異なるようです．）

```
function f()
    return false, _;
end function;
print "without 'where'";
a, b := f();
a and b eq 1;

print "\nwith 'where'";
a and b eq 1 where a, b := f();
(not a) and b eq 1 where a, b := f();
/* Result:
without 'where'

>> a and b eq 1;
         ^
User error: Identifier 'b' has not been declared or assigned

with 'where'
false

>> (not a) and b eq 1 where a, b := f();
               ^
Runtime error: Variable 'b' has not been initialized

*/
```


</div>
</details>

- 集合演算子

|記法|数式|意味|
|:-:|:-:|:-:|
|`S meet T`|$S \cap T$|共通部分|
|`S join T`|$S \cup T$|和集合|
|`S diff T`|$S - T$|差集合|
|`S sdiff T`|$S \bigtriangleup T = (S \cup T) - (S \cap T)$|対称差|


もちろん，これらの演算子に加えて括弧`( ... )`も使用できます．

- 文字列の結合

文字列の結合には`cat`演算子や`*`演算子が使用できます．

```
a := "World";
print "Hello " cat a * "!";
// Result: Hello World!
```

## 大型演算子

総和$\sum$などの大型演算子に対応する演算子`&(演算子)(オペランド)`が定義されています．


|数式例|記法|概要|
|:-:|:-:|:-:|
|$\displaystyle \sum_{v \in Values} v$|`&+(配列や集合など)`|総和|
|$\displaystyle \prod_{v \in Values} v$|`&*(配列や集合など)`|総乗|
|$\displaystyle \bigwedge_{B \in Bools} B$|`&and(配列や集合など)`|論理積|
|$\displaystyle \bigvee_{B \in Bools} B$|`&or(配列や集合など)`|論理和|
|$\displaystyle \bigcap_{S \in Sets} S$|`&meet(配列や集合など)`|共通部分|
|$\displaystyle \bigcup_{S \in Sets} S$|`&join(配列や集合など)`|和集合|

また`&cat(配列)`あるいは`&*(配列)`とすると文字列を結合することができます．

<details>
<summary>参考コード</summary>
<div>


```
&+[1, 2, 3, 4]; // -> 10
&*[1, 2, 3, 4]; // -> 24
&*{5, 2, 5, 5}; // -> 10
&and[true, true]; // -> true
&or[false, true]; // -> true
&join[{1, 2, 5}, {2, 3, 4}]; // -> { 1, 2, 3, 4, 5 }
&meet[{1, 2, 5}, {2, 3, 4}]; // -> { 2 }
&join{{1, 2, 3}, {3, 4}}; // -> { 1, 2, 3, 4 }
&meet{{1, 2, 3}, {3, 4}}; // -> { 3 }
a := [1, 2, 3, 4]; &+a; // -> 10
&cat["Hello", " ", "World", "!"]; // -> Hello World!
```


</div>
</details>

# Step 4. 比較と繰り返し

## 早見表

|種類|構文|
|:-:|:-:|
|`if`文|`if 条件式 then 文 else 文 end if;`|
|三項演算子(`select`文)|`(条件式) select 値 else 値;`|
|`case`文|`case 値: when 値1: when 値2: ... end case;`|
|`for`文1|`for 変数 := 値 to 値 by 値 do 文 end for;`|
|`for`文2|`for 変数 in 配列等 do 文 end for;`
|`for random`文|`for random 変数 in 配列等 do 文(need "break"!) end for;`
|`while`|`while 条件式 do 文 end while;`|
|`repeat`|`repeat 文 until 条件式;`|



## `if`文

`if`文は`if ... then ... else ... end if;`と記述します．末尾のセミコロンを忘れずに！

```
res := 3 eq 3;
if res then
    print "OK";
else
    print "NG";
end if;
// Result: OK
```

なお，インデントは必ず必要というわけではありませんが，読みやすさのために入れておくとよいでしょう．


## 三項演算子(`select`文)

C言語の三項演算子`条件式 ? 値 : 値`のような構文が用意されています．


```
a := 4;
b := (a gt 2) select "> 2" else "<= 2";
print a, b;
// Result: 4 > 2
```

## `case`文

C言語でいう`switch`文が記述できます．ただし，C言語と異なり値が一致する`when`文を見つけたら対応する文を実行してすぐに終了します（`case`文から抜け出します）．

```
v := 2;
case v:
    when 0:
        "zero";
    when 1: 
        "one";
    when 2:
        "two";
    when 2:
        "NG";
    else:
        "another";
end case;
// Result: two
```

次のような記法もあります．こちらは，**必ずデフォルト値を設定**しなければならず，またデフォルト値は**必ず最後尾に記入**しなければなりません．

```
x := 2;
a := case<x | 0: "zero", 1: "one", 2: "two", default: "another">;
a;
// Result: two
```


## `for`文
`for`文は`for ... in ... do ... end for;`または`for 変数 := 最小値 to 最大値 by 更新幅 do ... end for;`と記述します．

```
for i in [5, 2, 7] do
    printf "%o ", i;
end for;
// Result: 5 2 7 
```

```
for i in {5, 2, 5, 2, 7} do
    printf "%o ", i;
end for;
// Result: 2 5 7 
```

```
for i := 4 to 7 do
    printf "%o ", i;
end for;
// Result: 4 5 6 7 
```

次のように，複数変数の指定も可能です．

```
for i, j, k in [0, 1] do
    printf "%o %o %o, ", i, j, k;
end for;
"";
for i in [0, 1], j in [2, 3], k in [4, 5] do
    printf "%o %o %o, ", i, j, k;
end for;
"";
/* Result:
0 0 0, 0 0 1, 0 1 0, 0 1 1, 1 0 0, 1 0 1, 1 1 0, 1 1 1,
0 2 4, 0 2 5, 0 3 4, 0 3 5, 1 2 4, 1 2 5, 1 3 4, 1 3 5,
*/
```


さらに，`for random`文を使用すると，リストから要素をランダムにとるループ文が簡単に作成できます．ただし，**無限ループになってしまう**ので，途中でループから抜け出す **`break`文が必要です．**

```
n := 0;
for random i in [1, 2, 3, 4, 5] do
    printf "%o ", i;
    n +:= 1;
    if n ge 20 then
        break;
    end if;
end for;
"";
// Result: 3 1 1 3 3 3 3 4 5 5 3 2 1 3 2 4 3 1 5 2 
```

## `while`文
`while`文は`while ... do ... end while;`と記述します．`while`文の直後の条件式を満たす限り繰り返します（C言語における`while`文と同じです）．

```
i := 1;
while i le 10 do
    printf "%o ", i;
    i := i + 1;
end while;
"";
// Result: 1 2 3 4 5 6 7 8 9 10 
```

## `repeat`文
`repeat`文は`repeat ... until ...;`と記述します．C言語の`do .. while();`と似た構文ですが，`repeat`文の方は**条件式を満たすまで**繰り返します（条件式を満たした時点で終了）．

```
i := -1;
repeat
    print i;
    i := i * 3;
until i lt 10;
// Result: -1
```


# Step 5. 関数

関数にはいくつかの書き方があります．

## `function`構文

一般のプログラム言語における「関数」です．ただし何らかの返り値を要求します．
返り値は**複数指定できます**（カンマ区切り）．
返り値を変数に代入する際は，下記の通り，返り値の順番通りに変数を指定する必要があります．途中の値は使わない，という時は，Step 2の補足で紹介した `_` を使うとよいでしょう．

```
f := function(x)
    if x gt 0 then
        return x;
    else
        return -x;
    end if;
end function;
f(4);  // -> 4
f(-3); // -> 3
```

```
f := function()
    return 1, 3;
end function;
a, b := f();
a, b; // -> 1 3
```

<details>
<summary>補足</summary>
<div>

- キーワード引数を定義することもできます．

```
f := function(x, y: key1 := 1, key2 := 2)
    return (x * y) + (key1 * key2);
end function;
f(3, 2);                       // -> 8
f(1, 5: key1 := 2);            // -> 9
f(4, 6: key2 := 1, key1 := 5); // -> 29
```


- 可変長引数を定義することもできます．

```
f := function(x, args, ...)
    return [x + v : v in args];
end function;
f(3); // -> エラー（0個は認めない）
f(1, 5); // -> [ 6 ]
f(1, 2, 3, 4, 5, 6); // -> [ 3, 4, 5, 6, 7 ]
```

- 複数の値を返すとき，先頭以外の返り値に値を指定しない，ということもできます．（このとき，代入する変数は未定義となります．）

```
c := 4;
function f()
    return 1, 2, _, 3, 4; // _ が値を返さない記号（NULL値を返すようなもの）
end function;
a, b, c, d, e := f();
assigned a; // -> true
assigned c; // -> false (先頭のc := 4;のデータも消去される)
```

</div>
</details>

## `func`文

`function`の簡略verです．

複雑な処理なく計算式1個で表現できる場合などに便利な記法です．
また，同様にキーワード引数が使用できます．

```
f := func<x | 3 * x>;
f(3); // -> 9
g := func<x, y, z | x^3 + y^2 + z>;
g(1, 2, 3); // -> 8
```


## `procedure`構文

いわゆる`void`型返り値の関数，すなわち値を返さない関数はこちらの`procedure`（プロシージャ）を使用します．

```
proc := procedure(a)
    if a lt 0 then
        a *:= -1;
    end if;
    for i := 1 to a do
        printf "OK: %o, ", i;
    end for;
    "";
end procedure;
proc(7);
proc(3);
/* Result:
OK: 1, OK: 2, OK: 3, OK: 4, OK: 5, OK: 6, OK: 7,
OK: 1, OK: 2, OK: 3,
*/
```

なお，前項`function`の補足にて説明したキーワード引数や可変長引数は`procedure`でも使用できます．

<details>
<summary>補足</summary>
<div>

**`procedure`に限っては，** いわゆる **参照渡し** を実現することができます．参照渡しは，仮引数と実引数のそれぞれの変数名の頭に`~`（チルダ）を挿入します．

```
a := procedure(~b, c)
    "b: "; b; b := 1; b;
    "c: "; c; c := 1; c;
end procedure;
d := 4; e := 5;
a(~d, e);
d, e;
/* Result:
b:
4
1
c:
5
1
1 5
*/
```

</div>
</details>

## `proc`文

`procedure`の簡略verです．

こちらは1文のみの実行に対して有効な書式です．`func`と違って返り値を必要としません．

```
PrintValue := procedure(x)
    print x;
end procedure;
f := proc<x | PrintValue(x + 1)>;
f(3);
/* Result:
4
*/
```

## 関数名と前方宣言

`function`，`procedure`は，次のように関数名を後に書くこともできます．

```
function f(x)
    return x+1;
end function;
procedure p(x)
    print x-1;
end procedure;
f(3);
p(3);
/*Result:
4
2
*/
```

また，関数を呼び出す際はすでにその関数が定義されていなければなりません．これは，関数内で別の関数を使用する際も同じです．

```
function f(x)
    p(x+1); // この時点で関数pが定義されていなければならない -> 定義は後ろに記述されてあるのでエラーとなる
    return x+1;
end function;
procedure p(x)
    print x*2;
end procedure;
f(3);
/* Result:
>>     p(x+1);
       ^
User error: Identifier 'p' has not been declared or assigned

>> f(3);
   ^
User error: Identifier 'f' has not been declared or assigned
*/
```

このエラーを回避するために，C言語でいうプロトタイプ宣言にあたる`forward`文を実行する必要があります．

```
forward p; // 追加！関数の外に記述すること．
function f(x)
    p(x+1);
    return x+1;
end function;
procedure p(x)
    print x*2;
end procedure;
f(3);
/* Result:
8
4
*/
```

## 型とストラクチャについて

`func`，`function`，`proc`，`procedure`はすべて`UserProgram`型として扱われます．

また，これらを含むストラクチャは`Parent(何らかの関数)`として得ることができます．ストラクチャは，演算子 `!` によるいわゆる型変換(coerce)を行う際や，後述するタプルの型指定などで使われます．

```
UserPrograms := Parent(func<x|x>);
function do_func(f, x)
    // fの実引数として関数しか認めない(procedureも認めることになる)
    f := UserPrograms ! f;
    /* あるいは
    valid, f := IsCoercible(UserPrograms, f);
    if(not valid) then ... end if;
    */
    return f(x);
end function;
```

なお，上記の関数とは異なる関数として，[`Intrinsic`型の関数(組み込み関数)](http://magma.maths.usyd.edu.au/magma/handbook/text/24)があります．これは，パッケージという特別なファイルにおいて定義される関数であり，引数や返り値に型を指定してオーバーロードを実現することができます．Magmaにデフォルトで用意されている`IsPrime`などの関数はすべて`Intrinsic`型として定義されています．

`Intrinsic`についても同様で，`Parent(何らかのintrinsic関数)`としてストラクチャを得ることができます．

```
Intrinsics := Parent(IsPrime);
```

`UserProgram`と`Intrinsic`の両方の型を受け入れたい場合は，後述の余積(Coproducts)を使用するとよいでしょう．

※両方の型を受け入れるものとして他にも`Program`型というのもありますが，これはパッケージにおいて使用可能なものと思ってください．また，パッケージはユーザ定義のものを用いることもできますが，Magma Calculatorではユーザ定義のパッケージを取り扱えないことに注意してください．

※組み込み関数として定義されていても，例えば`IsPrime := func<x | true>;`として上書きすることができます．意図しない上書きを避けたいならば，`assert not assigned (関数名)`や`assert not IsIntrinsic("関数名");`などとしておくとよいでしょう．

# Step 6. リスト・数列・集合など

配列や集合の構造はいくつかの種類・記法があります．それらを見ていきましょう．また，この部分は[公式ハンドブック](http://magma.maths.usyd.edu.au/magma/handbook/part/2)もご参照ください．

## 基本書式

### 数列 Sequences

```
[ 1, 2, 3, 4 ];
```

Pythonのような**基本の記法**です．型は`SeqEnum`．
もちろん要素に変数を導入できます．

この形式では，**要素はすべて同じ型**となります．
同じ型となれないものはエラーとなってしまいます．

<details>
<summary>参考コード</summary>
<div>

```
f := procedure(list)
    for i in list do
        Type(i), i;
    end for;
end procedure;

f([1, 2 ,3]); "";
f([1, 2, 3.0]); "";
f([1, 2, "3"]); "";
f([1, 2, true]); "";
/* Result:
RngIntElt 1
RngIntElt 2
RngIntElt 3

FldReElt 1.00000000000000000000000000000
FldReElt 2.00000000000000000000000000000
FldReElt 3.00000000000000000000000000000

FldRatElt 1
FldRatElt 2
FldRatElt 3/7


>> f([1, 2, "3"]); "";
            ^
User error: Could not find a valid universe for the sequence

>> f([1, 2, true]); "";
            ^
User error: Could not find a valid universe for the sequence
*/
```

</div>
</details>


### リスト Lists

```
[* 1, 2, 3, 4 *];
```

``[* ... *]``で囲む記法です．型は`List`．
こちらは**異なる型を要素に指定できます．**

<details>
<summary>参考コード</summary>
<div>

```
f := procedure(list)
    for i in list do
        Type(i), i;
    end for;
end procedure;

f([* 1, 2 ,3 *]); "";
f([* 1, 2, 3.0 *]); "";
f([* 1, 2, 3/7 *]); "";
f([* 1, 2, "3" *]); "";
f([* 1, 2, true *]); "";
/* Result:
RngIntElt 1
RngIntElt 2
RngIntElt 3

RngIntElt 1
RngIntElt 2
FldReElt 3.00000000000000000000000000000

RngIntElt 1
RngIntElt 2
FldRatElt 3/7

RngIntElt 1
RngIntElt 2
MonStgElt 3

RngIntElt 1
RngIntElt 2
BoolElt true

*/
```

</div>
</details>


### 集合 Sets

重複や順序を無視する通常の集合構造です．型は`SetEnum`．
**要素はすべて同じ型にされます**．

```
{ 1, 2, 3 };
```

重複は自動で消去され，順序も自動でソートされます．

```
{ 1, 4, 3 }; // -> { 1, 3, 4 }
{ 1, 1, 3, 3, 5 } // -> { 1, 3, 5 }
```

### 順序付き集合 Indexed Sets

指定された順序を保存する集合構造です．型は`SetIndx`．
重複は自動で消去され，一番最初のものだけが残されます．

```
{@ 5, 2, 3, 7 @};
```

### 多重集合 Multisets

値が重複した際に重複した個数を保存する集合構造です．型は`SetMulti`．
順序は保存されません．

```
{* 1^^3, 2, 4^^5, 6 *};
```

<details>
<summary>参考コード</summary>
<div>

```
a := { 1, 4, 3, 3 };
Type(a), a; // -> SetEnum { 1, 3, 4 }
b := {@ 4, 1, 4, 6, 3 @};
Type(b), b; // -> SetIndx {@ 4, 1, 6, 3 @}
c := {* 5, 3, 1, 5, 7, 3, 2, 6, 7 *};
Type(c), c; // -> SetMulti {* 1, 2, 3^^2, 5^^2, 6, 7^^2 *}
```


</div>
</details>


## 特別な表記

### リストや集合の個数

先頭に`#`を入れます．

```
a := [1, 2, 3, 4, 5];
#a; // -> 5
G := {1, 2, 3, 4};
print #G; // -> 4
F := {* 5, 2, 5, 7, 8, 3, 2, 3, 6 *};
#F; // -> 9
```

### 区間表記

最小値と最大値を指定する書式です．

```
[ 3 .. 8 ] // = [ 3, 4, 5, 6, 7, 8 ]
[ 3 .. 8 by 2 ] // = [ 3 .. 7 by 2 ] = [ 3, 5, 7 ]
{ 3 .. 8 } // = { 3, 4, 5, 6, 7, 8 }
{ 3 .. 8 by 2 } // = { 3 .. 7 by 2 } = { 3, 5, 7 }
```

`print`出力だと区間指定の書式`[ m .. M by s ]`のまま出力されますが，`[]`での添字指定や次の内包表記，`in`によるループなどを使うと各要素を取り出せます．


### 内包表記

Magmaにおける正式名称は知りませんが，Pythonにおける内包表記に近いものが使用できます．定義域と条件を指定して，条件を満たすものだけを抽出します．

```
F := { 4, 2, 1, 6, 4 };
[ x : x in F | x gt 1 ];
// [ 値 : 左値で使用する変数の定義(in構文) | その変数に関する条件式 ];
// { 値 : 左値で使用する変数の定義(in構文) | その変数に関する条件式 };
```

<details>
<summary>参考コード</summary>
<div>

```
// 素数を取得
[ x : x in [2 .. 50] | IsPrime(x) ];
// Result: [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 ]
```

```
[[x, y] : x in [2..10], y in [2..10] | x lt y and IsPrime(x) and IsPrime(y)];
/* Result:
[
    [ 2, 3 ],
    [ 2, 5 ],
    [ 3, 5 ],
    [ 2, 7 ],
    [ 3, 7 ],
    [ 5, 7 ]
]
*/
```

```
[x + 1 : x in [2..100] | IsPrime(x) and IsPrime(x + 2)];
// Result: [ 4, 6, 12, 18, 30, 42, 60, 72 ]
```

```
{* x mod 2 : x in [2..100] | IsPrime(x) *};
// Result: {* 0, 1^^24 *}
```

条件を設けず，すべての要素を抽出する場合は，`|` 以降を省略できます．

```
X := [ 1 .. 6 ];
XX := [ x : x in X ];
X, XX;
/* Result:
[ 1 .. 6 ]
[ 1, 2, 3, 4, 5, 6 ]
*/
```

</div>
</details>

## 配列操作などに関する関数

<!-- 
http://magma.maths.usyd.edu.au/magma/handbook/text/116
http://magma.maths.usyd.edu.au/magma/handbook/text/106#854
より．
-->

|関数|概要|
|:-:|:-:|
|`Universe(S)`<sup>\[S\]\[I\]\[M\]</sup>|`S`の要素が属する型（ストラクチャ）を取得する|
|`#S`<sup>\[L\]\[S\]\[I\]\[M\]</sup>|`S`の要素の個数を取得する|
|`S cat T`<sup>\[L\]</sup>|2つの配列`S`，`T`を結合|
|`Min(S)`，`Max(S)`<sup>\[S\]\[I\]\[M\]</sup><br>`Minimum(S)`，`Maximum(S)`|最小値・最大値を取得<br>（第2返り値に添字が返される）<br>（演算`lt`と`eq`の両方が定義されている場合に限る）|
|`Index(S, x)`<sup>\[I\]\[L\]</sup>|配列`S`から要素`x`を探してその最小の添字を返す<br>（存在しないときは0を返す）|
|`Rep(S)`<sup>\[S\]\[I\]\[M\]</sup><br>`Representative(S)`|`S`内の何らかの要素を返す<br>（どの要素を用いてもよいときに使用．）|
|`Random(S)`<sup>\[S\]\[I\]\[M\]</sup>|`S`内から無作為に選んだ要素を返す<br>`random{ x : x in F \| (条件式) }`の書式もある<br>（ただし`random`は見つかるまで無限ループする！）|
|`Explode(S)`<sup>\[L\]</sup>|`S`の要素を関数の複数返り値に変換<br>（配列の各要素を別の変数に代入するときに便利）|
|`Append(S, x)`<sup>~\[L\]</sup>|要素`x`を末尾に追加する．|
|`Include(S, x)`<sup>~\[S\]\[I\]\[M\]</sup>|要素が配列内に無いときのみ，要素を末尾に追加する．|
|`Insert(S, i, x)`<sup>~\[L\]</sup>|`i`の添字の手前に`x`を挿入する<br>（`i`の値が`x`になるように挿入）．|
|`Insert(S, k, m, T)`<sup>~</sup>|`S`の添字`k`から`m`までの部分を`T`に移し替える．|
|`Exclude(S, x)`<sup>~\[S\]\[M\]</sup>|`S`から`x`を探し，最小の添字のものを削除する．|
|`Remove(S, i)`<sup>~\[L\]</sup>|`S`の添字`i`の要素を削除する．|
|`Prune(S)`<sup>~\[L\]</sup>|`S`の末尾要素を削除する．|
|`Reverse(S)`<sup>~\[L\]</sup>|`S`を反転させる．|
|`Rotate(S, p)`<sup>~</sup>|`S`を`p`個分右方向に巡回シフトさせる．|
|`Sort(S)`<sup>~\[I\]</sup>|`S`を昇順ソートする．<br>（演算`lt`と`eq`の両方が定義されている場合に限る）|
|`Sort(S, f)`<sup>~</sup>|第2引数に比較関数`f`を指定して昇順ソートする<br>`f(x, y) le 0`のとき`x le y`，<br>`f(x, y) eq 0`のとき`x eq y`とされる|
|`ParallelSort(~S, ~T)`|`S`を昇順ソートし，<br>ソートにおける並び替えに合わせて`T`を並び替える．<br>`#S eq #T`が条件．|
|`ChangeUniverse(S, V)`<sup>\[S\]\[I\]\[M\]</sup>|`S`の要素のストラクチャを`V`に変更する|
|`CanChangeUniverse(S, V)`<sup>\[S\]\[I\]\[M\]</sup>|`ChangeUniverse(S, V)`を試みる<br>変更可能なら第1引数に`true`，第2引数に変更後の配列．<br>不可能なら`false`．|
|`Partition(S, p)`|`S`を`p`個ずつに分割<br>（`p`は`#S`の約数とする）|
|`Partition(S, P)`|`S`を`P[1]`個，`P[2]`個，`P[3]`個...に分割|
|`Setseq(S)`<sup>\[S\]\[I\]</sup><br>`SetToSequence(S)`|集合`S`を配列に直す|
|`Seqset(S)`<br>`SequenceToSet(S)`|配列`S`を集合に直す|

※：<sup>~</sup>が付いている関数は，第1引数に渡す配列を参照渡し`~S`とすると，その配列に変更を加えます（procedure）．一方，値渡しとすると，新たな配列を作成してそれを返します（function）．参照渡しの場合だと，配列のコピーを作成しないので，巨大な配列を扱う際などに有効です．

※：記号\[L\]，\[S\]，\[I\]，\[M\]は，それぞれリスト，集合，順序付き集合，多重集合に対しても有効であることを表します．


また，Boolean型（`BoolElt`）の要素からなる同じ長さの配列`S`，`T`に対しては関数`And(S, T)`，`Or(S, T)`，`Xor(S, T)`が使用できます．これらの関数は，同じ添字の要素同士をそれぞれ`and`，`or`，`xor`した配列を返します．`And(~S, T)`などとすることで，結果を`S`に上書きすることもできます（配列のコピーを作成しない）．他にも`S`に対して`Not(S)`もあり，これは各要素に`not`をかけます．



## その他のデータ構造

### 文字列

文字列も変数に格納して使用することができます．型は`MonStgElt`で，ストラクチャは`Strings()`で入手できます．

なお，ASCII文字のみ使用可能で，ギリシャ文字や日本語などは使用できないので注意してください．

<details>
<summary>文字列操作などに関する関数まとめ</summary>
<div>

|関数|概要|
|:-:|:-:|
|`s cat t`<br>`s * t`|2つの文字列を結合<br>（`&*`も使用可能）|
|`Sprint(x)`|任意型の`x`が`print`で出力する内容を取得|
|`Sprintf(format, x1, x2, ...)`|`printf`文のようにフォーマット`format`を<br>指定して出力内容を返す|
|`s ^ n`|文字列`s`を`n`回繰り返す|
|`s[n]`|文字列`s`の`n`番目の文字を取得|
|`#s`|文字数を取得|
|`Eltseq(s)`<br>`ElementToSequence(s)`|1文字ずつに分割|
|`m in s`|文字列`m`が文字列`s`に含まれているか|
|`Index(s, m)`<br>`Position(s, m)`|文字列`s`から文字列`m`を検索し，<br>最初に見つかった添字を返す|
|`StringToInteger(s)`|文字列を整数値と解釈して変換する|
|`StringToIntegerSequence(s)`|半角スペースで区切られた整数値を解釈する|
|`IntegerToString(n)`|整数値を文字列に変換する|
|`StringToCode(s)`|1文字目のASCIIコードを返す|
|`CodeToString(n)`|ASCIIコードから文字に変換|
|`Substring(s, n, k)`|文字列`s`の添字`n`から`k`個文を抽出|
|`Split(s, d)`|文字列`s`を`Eltseq(d)`を区切り文字として分割<br>（キーワード引数`IncludeEmpty := true`<br>としない限り空白文字は消去される）|
|`RegExp(pattern, s)`|`pattern`に文字列`s`が<br>マッチするか否か，マッチ部分，グループ<br>使用できる特殊文字は`\|*+?^$[]`のみ|




</div>
</details>

### タプル Tuples

`<1, 2.5, "str", 3/7>`と表現します．型は`Tup`です．

タプルは配列と似ていますが，各要素の型がストラクチャによって管理されています．

```
t := <1, 2.5, "str", 3/7>;
Parent(t);
// Cartesian Product<Integer Ring, Real field of precision 30, String structure, Rational Field>

t[1] := 3; // OK
t[1] := "three"; // ストラクチャが異なるのでエラー
```

タプルへの操作として，`Append`，`Prune`，`Explode`のほか，`Flat`，`TupleToList`などが使えます．

また，後述のデカルト積によって要素の型を明示的に指定することもできます．

```
c := car<Integers(), RealField(), Strings()>;
print elt<c | 1, 2.5, "4">; // 
print c ! (<a : a in [1..4] | a gt 2> cat <"OK">);
function f(args)
    types := car<Integers(), RationalField(), Parent(func<x|x>)>;
    args := types ! args;
    return args[3](args[1], args[2]);
end function;
print f(<3, 1, func<a, b | a + b>>);
/* Result:
<1, 2.50000000000000000000000000000, "4">
<3, 4.00000000000000000000000000000, "OK">
4
*/
```

なお，要素型を指定しない場合には内包表記が可能です．

```
<p : p in [1..100] | IsPrime(p)>;
```

### デカルト積（直積） Cartesian products

ストラクチャの(順序付きの)組という構造を与えることができます．

```
C := car<Integers()>;

// CartesianProduct関数を使うと配列としてストラクチャを渡せる
C := CartesianProduct(<GF(p) : p in [1..10] | IsPrime(p)>);

// デカルト積自体もストラクチャなので入れ子が可能
C := car<Integers(), car<Integers(), Integers()>>;
```

### 余積 Coproducts

ストラクチャの余積(多分ここでは直和と呼んでもよい)を与えます．

プログラミング的観点から言えば，TypeScriptのUnion型 `string || number` のようなことが実現できます．

```
procedure f(args)
    IntOrBool := cop<Integers(), Booleans()>;
    types := car<IntOrBool, IntOrBool>;
    args := types ! args;
    a := Retrieve(args[1]); // 元の型に戻す
    ai := Index(args[1]); // 何番目の型にマッチしたか
    b := Retrieve(args[2]);
    bi := Index(args[2]);
    print a, Type(a), ai, b, Type(b), bi;
end procedure;
f(<4, 1>); // 4 RngIntElt 1 1 RngIntElt 1
f(<true, 7>); // true BoolElt 2 7 RngIntElt 1
```

<!--

<details>
<summary>もちろん数学的な道具としても使われます．</summary>
<div>


複数の集合$S_1, \dots, S_n$の余積$\coprod_{i = 1}^n S_i$と$S_i$から$R$への射$f_i: S_i \to R$に対し，一意存在する射$f = \coprod_{i=1}^n f_i: \coprod_{i=1}^n S_i \to R$は`UniversalMap`を用いることで得られます．

```
S := Strings(); I := Integers();
C := cop<S, I>;
codomain := RealField(5);
fS := map<S -> codomain | s :-> #s>;
fI := map<I -> codomain | v :-> v+1>;
fC := UniversalMap(C, codomain, [fS, fI]);
fC;
fC("OK"), fC(1);
/*
Mapping from: Cop: C to FldRe: codomain given by a rule [no inverse]
2.0000 2.0000
*/
```

---


</div>
</details>

-->

### レコード（構造体） Records

C言語でいう構造体に近いものが使用できます．型は`Rec`です．（`recformat`で返される方は`RecFrmt`．）
つまり，要素の名前とその型（ストラクチャ）をそれぞれ指定することができます．

`recformat`で要素の名前とそのストラクチャ(あるいは型)を指定し，`rec`で構造を生成します．

```
Customer := recformat< name: Strings(), age: Integers(), isVIP: Booleans() >;
// ストラクチャ Strings(), Integers() の代わりに型 MonStgElt, RngIntElt を用いてもよい
alice := rec<Customer | name := "Alice", age := 15, isVIP := true>;
Names(alice);
alice`name;
alice ` age;
n := "name";
if alice`isVIP then
    alice``n, "is VIP";
else
    alice``n, " is not VIP";
end if;
/* Result:
[ name, age, isVIP ]
Alice
15
Alice is VIP
*/
```

各要素にアクセスするにはバッククォート `` ` `` を使用します．（ドット `.` でもアロー `->` でもありません！）

ただし，アクセスする要素名を文字列として渡す場合は，バッククォート2個 ``` `` ``` を使用します．

### 連想配列 Associative arrays

連想配列を定義することができます．連想配列に使うキーは同じストラクチャに属さないといけません．

```
a := AssociativeArray();
a[2] := 5; // ストラクチャが Integers() になる
a["OK"] := 5; // エラー
a[3/5] := 2; // ストラクチャが RationalField() に更新される
// 異なる型でもCoveringStructureが存在すればエラーにならない

print a[2]; // 5
Keys(a); // { 3/5, 2 }
```

```
a := AssociativeArray(Strings()); // 初めにストラクチャを指定する
a["a"] := "m";
a["b"] := func<x | x + 1>;
for key -> value in a do
    printf "%o -> %o (%o)\n", key, value, Type(value);
end for;
/* Result:
b -> function(x) ... end function (UserProgram)
a -> m (MonStgElt)
*/
```

### 写像 Maps

写像を定義することもできます．
写像の定義には`map`を用い，`|`をはさんで左に入力が属するストラクチャと出力のストラクチャを指定し，右に入力に対する出力値を指定します．

例（ $f: \mathbb{Z} \rightarrow \mathbb{Q},\ x \mapsto x/7$ を表す）

```
f := map<Integers() -> RationalField() | x :-> x / 7 >;
f(3); // -> 3/7
```

<details>
<summary>補足</summary>
<div>

- 引数に`SeqEnum`（通常の配列），`SetEnum`（通常の集合），`SetIndx`（順序付き集合）が使用できます．
- 演算子`@`が使用できます．（ただし写像`f`が後ろになることに注意！）

```
f := map<Integers() -> Integers() | x :-> x+1>;
a := [1, 5, -32]; // SeqEnum
b := { 5, 1, 3, 6 }; // SetEnum
c := {@ 5, 1, 3 @}; // SetIndx
f(a);
b @ f;
c @ f;
/* Result:
[ 2, 6, -31 ]
{ 2, 4, 6, 7 }
{@ 6, 2, 4 @}
*/
```

</div>
</details>


# Step 7. デバッグ・エラー処理・その他構文

## `assert`文

`assert (条件式);`とします．条件式が`true`のときは何も起きませんが，`false`のときはエラーが発生します．

```
a := 5;
assert a gt 3;
a := 3;
assert a gt 5;
/*
>> assert a gt 5;
   ^
Runtime error in assert: Assertion failed
*/
```

`assert`以外にも，`assert2`，`assert3`が用意されています．これらはアサーションレベルが異なり，それぞれ1，2，3です．
デフォルトではアサーションレベル1までをエラーとし，2，3はエラー扱いとしませんが，`SetAssertions(2);`とするとレベル2までをエラー扱いに，`SetAssertions(3);`とするとすべてをエラーとすることができます．

```
p := procedure(n)
    n, "(", GetAssertions(), ")";
end procedure;
f := procedure()
    try assert false; catch e p(1); end try;
    try assert2 false; catch e p(2); end try;
    try assert3 false; catch e p(3); end try;
end procedure;
f();
SetAssertions(2); f();
SetAssertions(3); f();
/* Result:
1 ( 1 )
1 ( 2 )
2 ( 2 )
1 ( 3 )
2 ( 3 )
3 ( 3 )
*/
```

なお，エラー扱いとされないとき，条件式は評価されないので，その分だけ実行時間が短縮されます．

```
// SetAssertions(2);
assert2 forall{a : a in [1..99999999] | a mod 1 eq 0};
// 実行時間の違いに注目！
```

## `error`文

`error 文1, 文2, ..., 文n;`とすると，Javaの`throw`やpythonの`raise`のようにエラーを発生させられます．

また，`error if (条件式), 文1, ..., 文n;`とすると，条件式を満たす場合のみにエラーが発生します．

このエラーは，次の`try`によって捕捉されます．

```
error 2,3,4;
error if 4 gt 3, "ERROR";
/* Result: 
Runtime error: 2 3 4
Runtime error: ERROR
*/
```

## `try`-`catch`文

他のプログラミング言語と同様に，上記のエラー発生を監視し，例外処理を設けることができます．エラーが発生した時点で`catch`の内容に進むので，`try`内のその後の処理は実行されません．

```
a := 4;
try
    error "OK";
    error "NG";
    a := 0;
catch e
    print "Error: ", e`Object;
end try;
a;
/* Result:
Error:  OK
4
*/
```

エラーを捕捉してくれるのは `Runtime error` （`error`文はこちらのエラーに該当）であり， `User error` （宣言していない変数を使おうとするなど）は対象外であることに注意してください！

## `exists`文

存在判断を行う構文が使用できます．

`exists(変数){ 式 : 定義 | 条件 }`あるいは`exists{ 式 : 定義 | 条件 }`の書式に従います．

条件を満たす値が存在するとき，変数に式の評価値が格納され，`true`を返します．

存在しない場合は，`false`が返され，変数は**未定義**の状態になります．（内部で`delete`が実行されている？）


<details>
<summary>参考コード</summary>
<div>

- 素体の生成元の存在判定

```
p := Random([x : x in [2..100] | IsPrime(x)]);
p;
F := GF(p);
function IsGenerator(F, x)
    assert x in F;
    return x ne 0 and Order(x) eq #F-1;
end function;
exists(g){x : x in F | IsGenerator(F, x)};
g;
/* Result:
89
3
*/
```

- 式は`x`以外でもOK

```
exists(t){x + 10 : x in [0..9] | x eq 4};
t; // -> 14
```

</div>
</details>


## `forall`文

全称判断を行う構文が使用できます．

`forall(変数){ 式 : 定義 | 条件 }`あるいは`forall{ 式 : 定義 | 条件 }`の書式に従います．（`exists`と同じ）

すべての要素が条件を満たすときに`true`を返し，変数は未定義の状態になります．

一方，条件を満たさない要素が存在するときは，その1つに対する式の評価値を変数に格納し，`false`を返します．


<details>
<summary>参考コード</summary>
<div>

- 2以上の整数$n$に対して$\forall a \in (\mathbb Z / n \mathbb Z)^\ast, a^{\varphi(n)} = 1$を検証（オイラーの定理）

```
n := Random([2..100]);
n;
F := {x : x in Integers(n) | x ne 0 and GCD(x, n) eq 1 };
forall{x : x in F | x^EulerPhi(n) eq 1};
/* Result:
84
true
*/
```

- 同様に，式は`x`以外でもOK

```
forall(t){x + 10 : x in [0..16] | x^16 mod 17 eq 1};
t; // -> 10
```

</div>
</details>


# Step 8. 組み込み関数を使ってみよう

Magmaには，たくさんの関数が用意されています．
ここでは，いくつかの関数を，[公式ハンドブック][of-full]を見ながら実際に試してみましょう！

基本的に，関数の名前は**英語名をそのまま**使うことが多いらしいです．ただし，略名が有名な場合（`Sqrt`や`GCD`など）は，略名を使用することがあります．
また，関数の名前は**大文字から始まる**ことが多いです．
使用したい処理の名前を英語に直した後は，公式ハンドブックの[索引](http://magma.maths.usyd.edu.au/magma/handbook/ind/)を使いましょう！

## 素数判定

「素数」は"**Prime**"です．「～であるかどうか」「～を持つかどうか」などといったYes/No判定には，よく`is`や`has`などが使われます．
ここでは`IsPrime`という関数が用意されています．
早速索引で調べてみましょう！

次の項目が見つかりました．

> <dl>
> <dt>IsPrime</dt>
> <dd><code>IsPrime(D) : DivSchElt -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : OMIdl -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : OMIdl -> BoolElt</code></dd>
> <dd><code>IsPrime(x) : RngElt -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : RngFunOrdIdl -> BoolElt</code></dd>
> <dd><code>IsPrime(n) : RngIntElt -> BoolElt</code></dd>
> <dd><code>IsPrime(n) : RngIntElt -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : RngMPol -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : RngMPolRes -> BoolElt</code></dd>
> <dd><code>IsPrime(I) : RngOrdIdl -> BoolElt, RngOrdIdl</code></dd>
> <dd>RngInt_IsPrime (Example H19E4)</dd>
> </dl>

このように，`IsPrime`関数と一口に言ってもたくさんのオーバーロードが行われています．
コロンで区切られた左側は，その関数の記法が示され，右側には引数の型（ストラクチャ）と返り値のストラクチャが示されています．
どうやら返り値は，下から2番目の特殊そうなケースを除いて，すべて`BoolElt`，すなわち通常のBoolean型のようですね．
また，一番下のMonospaceになっていない項目が，その関数の実行例となっています．実行例にはそれぞれIDが付されており，この実行例は「H19E4」らしいですね．

さて，通常のいわゆるint型は`RngIntElt`です．
「整数(Int: Integer)環(Rng: Ring)の要素(Elt: Element)」ということですね．

これを引数にしたものが第6項と第7項にあります．
Webサイトでこちらをクリックすると，該当のページに飛ぶことができます．

試しに第6項の方をクリックしてみましょう．

すると（英語ですが）整数を引数にとる`IsPrime`関数の説明文が現れます．
さらに嬉しいことに実行例も載ってますね！この実行例は「H19E4」のIDが付記されているので，上記の「RngInt_IsPrime (Example H19E4)」に該当することが分かります．


「引数は一つ」，「通常の整数型が引数にとれる」ことが分かったので，早速実行してみましょう！コードと実行結果は次の通りです．

```
IsPrime(97); // -> true
IsPrime(93); // -> false
IsPrime(0);  // -> false
IsPrime(1);  // -> false
IsPrime(-3); // -> true
```


## 最大公約数（GCD）・最小公倍数（LCM）

組み込み関数`GCD`，`LCM`が用意されています（すべて大文字です）．

索引で探していたら，ちょうどよい[ページ](http://magma.maths.usyd.edu.au/magma/handbook/text/178)が見つかりました．

どうやら`Gcd`，`GCD`，`GeatestCommonDivisor`の3通りの書き方があるようですね．それらの違いって一体何なんだろう...

引数は，2つの整数値，または整数値の数列だそうです（集合でもいけるっぽい？多分要素の型がすべて同じであるようなデータ構造ならOK）．

早速使ってみましょう！

```
GCD(4, 6); // -> 2
Gcd(4, 6); // -> 2
GreatestCommonDivisor(4, 6); // -> 2
LCM(4, 6); // -> 12
Lcm(4, 6); // -> 12
LeastCommonMultiple(4, 6); // -> 12
GCD([4, 6, 8, 10]); // -> 2
LeastCommonMultiple({3, 4, 5, 6}); // -> 60
```


## 素因数分解

素因数分解は英語で"**Factorization** in prime numbers"とか"Prime factorization"と言います（"Factorization"で「因数分解」）．

Magmaでは関数`Factorization`が用意されています．

同じく索引で調べてみると...

> <dl>
> <dt>Factorization</dt>
> <dd><code>DistinctDegreeFactorization(f) : RngUPolElt -> [ &lt;RngIntElt, RngUPolElt&gt; ]</code></dd>
> <dd><code>EqualDegreeFactorization(f, d, g) : RngUPolElt, RngIntElt, RngUPolElt -> [ RngUPolElt ]</code></dd>
> <dd><code>Facint(f) : RngIntEltFact -> RngIntElt</code></dd>
> <dd><code>Factorisation(A) : ModAbVar -> List</code></dd>
> <dd><code>Factorisation(L) : RngDiffOpElt -> SeqEnum, SeqEnum</code></dd>
> <dd><code>Factorization(I) : AlgQuatOrdIdl -> SeqEnum</code></dd>
> <dd><code>Factorization(A) : GalRep -> List,GalRep</code></dd>
> <dd><code>Factorization(L) : LSer -> SeqEnum[Tup]</code></dd>
> <dd><code>Factorization(I) : OMIdl -> SeqEnum</code></dd>
> <dd><code>Factorization(I) : OMIdl -> SeqEnum</code></dd>
> <dd><code>Factorization(I) : RngFunOrdIdl -> [ &lt;RngFunOrdIdl, RngIntElt&gt; ]</code></dd>
> <dd><code>Factorization(n) : RngIntElt -> RngIntEltFact, RngIntElt, SeqEnum</code></dd>
> <dd><code>Factorization(f) : RngMPolElt -> [ &lt; RngMPolElt, RngIntElt &gt;], RngElt</code></dd>
> <dd><code>Factorization(I) : RngOrdFracIdl -> [&lt;RngOrdIdl, RngIntElt&gt;]</code></dd>
> <dd><code>Factorization(n) : RngQuadElt -> SeqEnum, Tup</code></dd>
> <dd><code>Factorization(f) : RngUPolElt -> [ &lt; RngUPolElt, RngIntElt &gt; ]</code></dd>
> <dd><code>Factorization(f) : RngUPolElt -> [ &lt; RngUPolElt, RngIntElt &gt;], RngElt</code></dd>
> <dd><code>Factorization(f) : RngUPolElt[RngLocA] -> SeqEnum, RngElt, Any</code></dd>
> <dd><code>Factorization(f) : RngUPolElt[RngSerPow[FldFin]] -> [ &lt; RngUPolElt[RngSerPow], RngIntElt &gt; ], RngSerPowElt</code></dd>
> <dd><code>Factorization(f, R) : RngUPolXPadElt, FldXPad -> SeqEnum, RngXPadElt, SeqEnum</code></dd>
> <dd><code>FactorizationOverSplittingField(f) : RngUPolElt[FldFin] -> [&lt;RngUPolElt, RngIntElt&gt;], FldFin</code></dd>
> <dd><code>FactorizationToInteger(s) : [ &lt;RngIntElt, RngIntElt&gt; ] -> RngIntElt</code></dd>
> <dd><code>HasPolynomialFactorization(R) : Rng -> BoolElt</code></dd>
> <dd><code>IsUFD(R) : Rng -> BoolElt</code></dd>
> <dd><code>PartialFactorization(S) : [ RngIntElt ] -> [ RngIntEltFact ]</code></dd>
> <dd><code>SeqFact(s) : SeqEnum -> RngIntEltFact</code></dd>
> <dd><code>SquareFreeFactorization(f) : RngUPolElt -> [ &lt; RngUPolElt, RngIntElt &gt; ]</code></dd>
> <dd><code>SquarefreeFactorization(n) : RngIntElt -> RngIntElt, RngIntElt</code></dd>
> <dd><code>SquarefreeFactorization(f) : RngMPolElt -> [ &lt;RngMPolElt, RngIntElt&gt; ]</code></dd>
> <dd><code>SquarefreeFactorization(f) : RngUPolElt -> [ &lt;RngUPolElt, RngIntElt&gt; ]</code></dd>
> <dd>AlgAff_Factorization (Example H115E5)</dd>
> </dl>

たくさんありますね．ここでは整数の素因数分解を行うこととして，引数が`RngIntElt`であるものを探しましょう．

12項目にありました．

> <dd><code>Factorization(n) : RngIntElt -> RngIntEltFact, RngIntElt, SeqEnum</code></dd>

どうやら3つの返り値を返すようですね．早速実行してみましょう！


```
a, b, c := Factorization(96);
a; b; c;
/* Result:
[ <2, 5>, <3, 1> ]
1

>> a; b; c;
         ^
User error: Identifier 'c' has not been declared or assigned

*/
```

おや？エラーが出ましたね．マニュアルに戻ってみましょう．
マニュアルを読むと，どうやら3つ目の返り値は素因数分解が完了できなかったときに返されるみたいです．

> If the factorization could not be completed, a third sequence is returned, containing composite factors that could not be decomposed with the given values of the parameters; this can only happen if both ECMLimit and MPQSLimit have been set. (Note that the third variable will remain unassigned if the full factorization is found.)

また2つ目の返り値には引数の正負が返され，実際に行われる素因数分解は引数の絶対値に対して行われるそうですね．

ちなみに，1つ目の返り値は2つの値の組の数列になっており，その組のうち1つ目がべき根，2つ目が指数に対応するらしいです．つまり，`[ <2, 5>, <3, 1> ]`は$2^5 \times 3^1$に対応しています．


## ビット操作

C言語では演算子`&`や`|`などをでしたが，Magmaでは**関数を用いてビット操作**を行います．

### 16進の入力（代入）・出力

```
a := 0xff; // 0xFF や 0b11111111 も可
Type(a); // -> RngIntElt
a; // -> 256
a : Hex; // -> 0xFF
```


### シフト操作

|関数|概要|
|:-:|:-:|
|`ShiftLeft(n, b)`|整数`n`を`b`ビット左にシフトします（`n * 2^b`と等価）．|
|`ShiftRight(n, b)`|整数`n`を`b`ビット右にシフトします（`n div 2^b`と等価）．|
|`ModByPowerOf2(n, b)`|`n mod 2^b`を計算します．<br>`n`が正なら下位`b`ビットだけ切り抜く処理と等価です．|

### ビットワイズ操作

|関数|概要|
|:-:|:-:|
|`BitwiseNot(x)`|ビット単位の否定を行います．|
|`BitwiseAnd(x, y)`|ビット単位の論理積を行います．|
|`BitwiseOr(x, y)`|ビット単位の論理和を行います．|
|`BitwiseXor(x, y)`|ビット単位の排他的論理和を行います．|


<details>
<summary>参考コード</summary>
<div>

```
ShiftLeft(6, 2);   // -> 24
ShiftRight(15, 2); // -> 3
ModByPowerOf2(65535, 6); // -> 63
BitwiseNot(1); // -> -2 (1 -> 01 -> 10 -> -2)
BitwiseNot(5); // -> -6 (5 -> 0101 -> 1010 -> -6)
BitwiseAnd(0xFFF0, 0xF0FF) : Hex; // -> 0xF0F0
BitwiseOr(0xFFF0, 0xF0FF) : Hex;  // -> 0xFFFF
BitwiseXor(0xFFF0, 0xF0FF) : Hex; // -> 0xF0F
```

</div>
</details>

## 時間計測

Magmaでは，時間計測を行う次の関数が用意されています．

### `Cputime`関数

実行にかかったCPU時間を計測できます．返り値は実数`FldReElt`です．

返される値は，セッション開始からの経過時間（秒）を表します．経過時間を引数に渡すことで，その時間からの差分を得ることができます．

```
t0 := Cputime();
a := [ i: i in [10^1..10^6] | IsPrime(i) ];
t1 := Cputime(t0);
"t0:", t0; // -> t0: 0.010
"Cputime:", t1, "sec"; // -> Cputime: 0.360 sec
```

### `Realtime`関数

現在時刻を取得できます．返り値は実数`FldReElt`です．

返される値は，グリニッジ標準時刻（GMT）の1970/01/01 00:00:00を基準にした経過時刻（秒）を表します．`Cputime`と同様，前に取得した現在時刻の値を引数に渡すことで，その時刻からの差分を得ることができます．

```
t0 := Realtime();
a := [ i: i in [10^1..10^6] | IsPrime(i) ];
t1 := Realtime(t0);
"t0:", t0; // -> t0: 1661836107.400
"Realtime:", t1, "sec"; // -> Realtime: 0.370 sec
```

### `ClockCycles`関数

Magmaのスタートアップ終了時からの経過クロックサイクル数を取得できます．こちらの引数は整数`RngIntElt`です．

上の2関数と違って，引数を指定することはできません．

また，このクロックサイクル数は，プロセスのユーザー・システム時間ではなく，実時間に則しています．

プロセッサが機能をサポートせず，クロックサイクル数を取得できない場合，0の値を返します．

```
t0 := ClockCycles();
a := [ i: i in [10^1..10^6] | IsPrime(i) ];
t1 := ClockCycles();
"t0:", t0; // -> t0: 358638270514
"t1:", t1; // -> t1: 359798462972
"ClockCycles:", t1-t0; // -> ClockCycles: 1160192458
```

### `Time`関数

`Cputime`，`Realtime`の同じように使用しますが，返り値が文字列`MonStgElt`であることと，並列処理時の挙動に特徴があります（実時間とCPU時間の短い方を返却し，実時間の場合は`[r]`タグが付けられるらしい）．

```
t0:=Time();
t0; // -> 0.02000 1661837326.68000 0
a := [ i: i in [10^1..10^6] | IsPrime(i) ];
t1:=Time(t0);
t1; // -> 0.360
```

### `time`文

`time (文);`とすると，1文の実行時間を簡単に出力することができます．

`SetShowRealTime(true);`とすると，CPU時間だけでなく実時間も出力することができます．（デフォルトはCPU時間のみ．現在の状態は`GetShowRealTime()`で取得可能．）

```
procedure f() a:=[ i: i in [10^1..10^6] | IsPrime(i) ]; end procedure;
"false(default):"; SetShowRealTime(false);
time f();
"\ntrue:"; SetShowRealTime(true);
time f();
/* Result:
false(default):
Time: 0.360

true:
 CPU time: 0.360
Real time: 0.360
*/
```

# おわりに

いかがでしたでしょうか？
ここでは，プログラミング言語の側面からMagmaの書き方を取り上げました．

しかしまだまだ基本を触っただけ．
Magmaの真髄である代数処理はこれからです．
さらに難しくなりますが，頑張って学習を進めていきましょう！

また，本記事で紹介したもの以外にも，Magmaにはさらにたくさんの関数や構造があります．

本記事の内容も活用しつつ，あとはマニュアルなどで学習を深めていってください！


<!--  余白  -->
<div style='height: 5em;'></div>

# 参考サイト

- [Magma 公式オンラインハンドブック][of-full]
- [First Steps in Magma][of-first]
- [計算機代数のためのソフトウェア][harada]

# 関連リンク

- [Magma 入門60分速修コース - YouTube](https://www.youtube.com/watch?v=--08Wkg7uPU)
- [数論研究者のためのMagma入門](https://slideshowjp.com/doc/2391660/%E6%95%B0%E8%AB%96%E7%A0%94%E7%A9%B6%E8%80%85%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEMagma%E5%85%A5%E9%96%80)
- [楕円曲線の計算法入門:実践編](https://core.ac.uk/download/pdf/161657768.pdf)
- [Magmaで広がる数学の世界](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.648.5055&rep=rep1&type=pdf) （ページ数が多いです）

[harada]: http://www.jssac.org/Editor/Suushiki/V18/No2/V18N2_126.pdf
[of-full]: http://magma.maths.usyd.edu.au/magma/handbook/
[of-first]: http://magma.maths.usyd.edu.au/magma/pdf/first.pdf
[of-calc]: http://magma.maths.usyd.edu.au/calc/