SMLでバッククォート記法を実現する

このエントリはML Advent Calendar 2020の3日目の記事です。 前はelpinalさんでStandardMLのwithtypeの挙動でした。

κeenです。Haskelなどにある ` ~ ` の記法(のようなもの)をSMLで実現する話です。

MLやHaskellには中置演算子を作る infix 系の構文があります。 そしてHaskellにはそれとは別に ` ~ ` で普通の関数を中置演算子として使える機能があります。

add :: Integer -> Integer -> Integer
add x y = x + y

1 `add` 2

さらに演算子を部分適用するセクションという機能もあります。

map (2 *) list
map (- 1) list

SMLでもこういう機能ほしいよね、という題材です。

最初に答えを書いてしまうとこういう演算子を定義します。

infix  3 <\     fun x <\ f = fn y => f (x, y)     (* Left section      *)
infix  3 \>     fun f \> y = f y                  (* Left application  *)
infixr 3 />     fun f /> y = fn x => f (x, y)     (* Right section     *)
infixr 3 </     fun x </ f = f x                  (* Right application *)

そうすれば以下のように使えます。

fun add(x, y) = x + y
1 <\add\> 2 <\add\> 3

これは以下と同じ意味になります。

add(add(1, 2), 3)

<\\> の変わりに <//> を使うと右結合になります。

1 </add/> 2 </add/> 3
(* = add(1, add(2, 3)) *)

どうしてこうなるのか詳しく見ていきましょう。

まずは 1 <\add\> 2 からはじめます。これは <\\> が左結合の infix 3 で定義されているので以下のように脱糖されます。

op\>(op<\(1, add), 2)

それではこれを内側から評価していきましょう。 <\ は以下のように定義されているのでした。

fun x <\ f = fn y => f (x, y)

すると上の式はこうなります。

op\>(fn y => add(1, y), 2)

次に \> です。これは以下のように定義されているのでした。

fun f \> y = f y

すると上の式はこうなります。

(fn y => add(1, y)) 2

これを簡約するとこうなる訳です。

add(1, 2)

ちゃんと、関数適用に簡約されましたね。 <//> も開と閉の役割が入れ替わるだけなので気になる人は各自で追ってみて下さい。

さて、これを使うとセクションも実現できます。

List.map (2<\op*) list
List.map (op- /> 1) list

すごい!

因みに余った \></ も役割があります。 \> がHaskellでいう $ 相当で、 </ がF# でいう |> 相当の演算子です。 ただし優先順位がこちらは3なのに対して $|> は1ですが。

ちなみにこの記事にま元ネタがあって、MLtonの中の人が書いた記事です:
InfixingOperators
こちらではもう少し色々紹介されています。

ML Advent Calendarの中で他の面白い記事も紹介していけたらなと思います。

Written by κeen
Older article
Idrisの基本文法