PureScript の演算子って? 優先度は? 結合規則はあるの? 調べてみました!

みなさんこんにちは! Qiita ユーザーの青井ひかりです!

JavaScript エンジン上で動作する純粋函数型スクリプト言語の PureScript はご存知でしょうか? あの Haskell に影響を受けて設計された言語で, 表現力豊かな型システムと実行時エラーが発生しづらい高い安全性が特徴です!

ところで, プログラミングには演算子がつきものです! だけど, 初めての言語でよくわからない, という人も多いのではないでしょうか?

今回はそんな PureScript の演算子について調べてみました!

## JavaScript の演算子との対応表

それでは, JavaScript と PureScript の演算子がどのように対応しているのかを見ていきましょう!

<!-- バッククォートでパイプがエスケープされないのマジでクソだと思う -->

<table>
<thead>
  <tr>
    <th></th>
    <th>JavaScript 表記</th>
    <th>JavaScript 特性</th>
    <th>PureScript 表記</th>
    <th>PureScript 特性</th>
    <th>パッケージ</th>
    <th>モジュール</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>冪乗</td>
    <td><code>**</code></td>
    <td>L15</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>乗法</td>
    <td><code>*</code></td>
    <td>L14</td>
    <td><code>*</code></td>
    <td>L7</td>
    <td>purescript-prelude</td>
    <td>Data.Semiring</td>
  </tr>
  <tr>
    <td>除法</td>
    <td><code>/</code></td>
    <td>L14</td>
    <td><code>/</code></td>
    <td>L7</td>
    <td>purescript-prelude</td>
    <td>Data.EuclideanRing</td>
  </tr>
  <tr>
    <td>剰余</td>
    <td><code>%</code></td>
    <td>L14</td>
    <td><code>%</code></td>
    <td>L7</td>
    <td>purescript-math</td>
    <td>Math</td>
  </tr>
  <tr>
    <td>加法</td>
    <td><code>+</code></td>
    <td>L13</td>
    <td><code>+</code></td>
    <td>L6</td>
    <td>purescript-prelude</td>
    <td>Data.Semiring</td>
  </tr>
  <tr>
    <td>減法</td>
    <td><code>-</code></td>
    <td>L13</td>
    <td><code>-</code></td>
    <td>L6</td>
    <td>purescript-prelude</td>
    <td>Data.Ring</td>
  </tr>
  <tr>
    <td>結合</td>
    <td><code>+</code></td>
    <td>L13</td>
    <td><code>&lt;&gt;</code></td>
    <td>R5</td>
    <td>purescript-prelude</td>
    <td>Data.Semigroup</td>
  </tr>
  <tr>
    <td>左転移</td>
    <td><code>&lt;&lt;</code></td>
    <td>L12</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>右転移</td>
    <td><code>&gt;&gt;</code></td>
    <td>L12</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
    <td>-</td>
  </tr>
  <tr>
    <td>未満</td>
    <td><code>&lt;</code></td>
    <td>L11</td>
    <td><code>&lt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>以下</td>
    <td><code>&lt;=</code></td>
    <td>L11</td>
    <td><code>&lt;=</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>超</td>
    <td><code>&gt;</code></td>
    <td>L11</td>
    <td><code>&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>以上</td>
    <td><code>&gt;=</code></td>
    <td>L11</td>
    <td><code>&gt;=</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>等価</td>
    <td><code>==</code></td>
    <td>L10</td>
    <td><code>==</code></td>
    <td>N4</td>
    <td>purescript-prelude</td>
    <td>Data.Eq</td>
  </tr>
  <tr>
    <td>不等価</td>
    <td><code>!=</code></td>
    <td>L10</td>
    <td><code>/=</code></td>
    <td>N4</td>
    <td>purescript-prelude</td>
    <td>Data.Eq</td>
  </tr>
  <tr>
    <td>交叉</td>
    <td><code>&amp;</code></td>
    <td>L9</td>
    <td><code>.&amp;.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>対称差</td>
    <td><code>^</code></td>
    <td>L8</td>
    <td><code>.^.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>合併</td>
    <td><code>|</code></td>
    <td>L7</td>
    <td><code>.|.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>連言</td>
    <td><code>&amp;&amp;</code></td>
    <td>L6</td>
    <td><code>&amp;&amp;</code></td>
    <td>R3</td>
    <td>purescript-prelude</td>
    <td>Data.HeytingAlgebra</td>
  </tr>
  <tr>
    <td>選言</td>
    <td><code>||</code></td>
    <td>L5</td>
    <td><code>||</code></td>
    <td>R2</td>
    <td>purescript-prelude</td>
    <td>Data.HeytingAlgebra</td>
  </tr>
</tbody>
</table>

### 一部存在しない演算子がある!

表を見てもらうと分かる通り, べき乗とシフトは演算子が用意されていません!

ただし, べき乗は`Int`型に対する函数として purescript-integers パッケージの` Data.Int `モジュールに, `Number`型に対する函数として purescript-math パッケージの`Math`モジュールに`pow`函数が, シフトは`Int`型に対する函数として purescript-integers パッケージの`Data.Int.Bits`モジュールに`shl`函数と`shr`函数が, それぞれ定義されています!

PureScript は自由に演算子を定義できる言語なので, 必要そうなら自分で定義して使ってもいいかもしれませんね!

### “あまり” の計算に注意!

“あまり” の計算については JavaScript の演算子と同じく`%`演算子が用意されています。

ただし, この演算子は`Number`型に対して単相的に定義されています! つまり, `Int`型に対して`%`演算子を使用することはできません!

かわりに, `EuclideanRing`型クラスのインスタンスをもつ型は`mod`函数を使うことができます! `Int`型の “あまり” を計算するときはこちらを使用しましょう!

ところで, `Number`もユークリッド環ですが, `mod`函数を使うとどうなるのでしょうか? 実は, `Number`型に対して`mod`函数を使用すると, ほとんどの場合`0.0`が返ってきます! これはユークリッド除算の性質によるもので, `Number`は常に “割り切れる” ためなのです! `%`演算子は JavaScript の`%`演算子と同じ感覚で使えるので便利ですね!

### 文字列の結合は`<>`で!

JavaScript では数値の足し算も文字列の結合も同じ`+`演算子を使っていますが, PureScript では文字列の結合には`<>`演算子を使います!

JavaScript の感覚で`+`演算子を使おうとすると “文字列は半環ではありませんよ” という意味のビルドエラーが発生しますので注意しましょう!

### “等しくない” は数学風!

JavaScript では “等しくない” ことを表すのに`!=`演算子を使いますが, PureScript では同じ意味の演算子は`/=`演算子になっています!

数学記号の $\ne$ をイメージしたデザインで分かりやすいですね!

### ビット演算子はドットで挟もう!

合併, 交叉, 対称差といった Bitwise 演算を行う演算子は, PureScript ではドット (ピリオド) で挟まれています! おそらく 1 文字のパイプ (`|`) が PureScript では重要な意味を持つため, かぶらないようにしたのだと思います。

ところで, `.^.`って顔文字みたいでかわいいですね! 🙃

### 結合規則に注意!

今回挙げた演算子は, JavaScript ではすべて左結合ですが, PureScript では右結合や無結合 (結合しない, 並べて書けないということ) のものも多くあります!

右結合とされる演算子の多くは結合規則を満たしているためあまり気にしなくてもよいかもしれませんが, 頭の片隅に入れておいてくださいね!

### 優先度の違いに注意!

JavaScript と PureScript では, 演算子の優先度の並びが異なることがあります!

たとえば, 文字列の結合を行う`<>`演算子は足し算や引き算よりも優先度が低く設定されています! また, Bitwise 演算は PureScript では優先度が非常に高く, `.&.`演算子, `.|.`演算子, `.^.`演算子の間で優先度の違いがありません!

これらの違いはしっかり覚えておきましょう!

## PureScript の主要な演算子, 一挙公開!

PureScript でよく用いられるそのほかの演算子も加えたうえで, 優先度にしたがって並べたのが以下の表になります!

<table>
<thead>
  <tr>
    <th></th>
    <th>表記</th>
    <th>特性</th>
    <th>パッケージ</th>
    <th>モジュール</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>合併</td>
    <td><code>.|.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>交叉</td>
    <td><code>.&amp;.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>対称差</td>
    <td><code>.^.</code></td>
    <td>L10</td>
    <td>purescript-integers</td>
    <td>Data.Int.Bits</td>
  </tr>
  <tr>
    <td>合成</td>
    <td><code>&lt;&lt;&lt;</code></td>
    <td>R9</td>
    <td>purescript-prelude</td>
    <td>Control.Semigroupoid</td>
  </tr>
  <tr>
    <td>逆合成</td>
    <td><code>&gt;&gt;&gt;</code></td>
    <td>R9</td>
    <td>purescript-prelude</td>
    <td>Control.Semigroupoid</td>
  </tr>
  <tr>
    <td>乗法</td>
    <td><code>*</code></td>
    <td>L7</td>
    <td>purescript-prelude</td>
    <td>Data.Semiring</td>
  </tr>
  <tr>
    <td>除法</td>
    <td><code>/</code></td>
    <td>L7</td>
    <td>purescript-prelude</td>
    <td>Data.EuclideanRing</td>
  </tr>
  <tr>
    <td>剰余</td>
    <td><code>%</code></td>
    <td>L7</td>
    <td>purescript-math</td>
    <td>Math</td>
  </tr>
  <tr>
    <td>加法</td>
    <td><code>+</code></td>
    <td>L6</td>
    <td>purescript-prelude</td>
    <td>Data.Semiring</td>
  </tr>
  <tr>
    <td>減法</td>
    <td><code>-</code></td>
    <td>L6</td>
    <td>purescript-prelude</td>
    <td>Data.Ring</td>
  </tr>
  <tr>
    <td>結合</td>
    <td><code>&lt;&gt;</code></td>
    <td>R5</td>
    <td>purescript-prelude</td>
    <td>Data.Semigroup</td>
  </tr>
  <tr>
    <td>等価</td>
    <td><code>==</code></td>
    <td>N4</td>
    <td>purescript-prelude</td>
    <td>Data.Eq</td>
  </tr>
  <tr>
    <td>不等価</td>
    <td><code>/=</code></td>
    <td>N4</td>
    <td>purescript-prelude</td>
    <td>Data.Eq</td>
  </tr>
  <tr>
    <td>未満</td>
    <td><code>&lt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>以下</td>
    <td><code>&lt;=</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>超</td>
    <td><code>&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>以上</td>
    <td><code>&gt;=</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Ord</td>
  </tr>
  <tr>
    <td>写像</td>
    <td><code>&lt;$&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Functor</td>
  </tr>
  <tr>
    <td>闕右</td>
    <td><code>&lt;$</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Functor</td>
  </tr>
  <tr>
    <td>闕左</td>
    <td><code>$&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Data.Functor</td>
  </tr>
  <tr>
    <td>適用</td>
    <td><code>&lt;*&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Control.Apply</td>
  </tr>
  <tr>
    <td>適用一次</td>
    <td><code>&lt;*</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Control.Apply</td>
  </tr>
  <tr>
    <td>適用二次</td>
    <td><code>*&gt;</code></td>
    <td>L4</td>
    <td>purescript-prelude</td>
    <td>Control.Apply</td>
  </tr>
  <tr>
    <td>連言</td>
    <td><code>&amp;&amp;</code></td>
    <td>R3</td>
    <td>purescript-prelude</td>
    <td>Data.HeytingAlgebra</td>
  </tr>
  <tr>
    <td>選言</td>
    <td><code>||</code></td>
    <td>R2</td>
    <td>purescript-prelude</td>
    <td>Data.HeytingAlgebra</td>
  </tr>
  <tr>
    <td>逆写像</td>
    <td><code>&lt;#&gt;</code></td>
    <td>L1</td>
    <td>purescript-prelude</td>
    <td>Data.Functor</td>
  </tr>
  <tr>
    <td>逆函数適用</td>
    <td><code>#</code></td>
    <td>L1</td>
    <td>purescript-prelude</td>
    <td>Data.Function</td>
  </tr>
  <tr>
    <td>束縛</td>
    <td><code>&gt;&gt;=</code></td>
    <td>L1</td>
    <td>purescript-prelude</td>
    <td>Control.Bind</td>
  </tr>
  <tr>
    <td>逆束縛</td>
    <td><code>=&lt;&lt;</code></td>
    <td>R1</td>
    <td>purescript-prelude</td>
    <td>Control.Bind</td>
  </tr>
  <tr>
    <td>クライスリ合成</td>
    <td><code>&gt;=&gt;</code></td>
    <td>R1</td>
    <td>purescript-prelude</td>
    <td>Control.Bind</td>
  </tr>
  <tr>
    <td>逆クライスリ合成</td>
    <td><code>&lt;=&lt;</code></td>
    <td>R1</td>
    <td>purescript-prelude</td>
    <td>Control.Bind</td>
  </tr>
  <tr>
    <td>函数適用</td>
    <td><code>$</code></td>
    <td>R0</td>
    <td>purescript-prelude</td>
    <td>Data.Function</td>
  </tr>
</tbody>
</table>

見慣れない演算子も多くありますが, そういうところが PureScript の楽しいところです! ~~はよう`>>=`まみれになろうや。 `a -> m b`,溜めて待つぜ。~~

## まとめ

いかがでしたか?

調べてみましたが, PureScript には言語組み込みの演算子がないためよくわかりませんでした。

しかし, PureScript には演算子を定義する機構が存在しており, よく使われるものに関しては purescript-prelude というパッケージで定義されています。

そのほか, 公開されているパッケージにどのような函数や演算子が定義されているかは専用の検索エンジン [Pursuit](https://pursuit.purescript.org/) で検索することができます! 今後もわからない演算子があったら調べていこうと思います!

ここまで読んでいただきありがとうございました! よかったら下か左にある LGTM ボタンを押してくださいね!