Idris面白機能:シンタックスシュガーとオーバーロード、あとユーザ定義構文
このエントリはIdris Advent Calendar 2020の17日目の記事です。
κeenです。今回は小ネタとしてシンタックスシュガー(糖衣構文)やオーバーロードについて紹介します。 あとIdrisにはユーザが構文を拡張できる機能もあるのでそれも紹介します。
if
式
Idrisでは if expr1 then expr2 else expr3
が糖衣構文として定義されています。
では展開すると何になるかというと、 ifThenElse
関数です。
「えっ?無理じゃない?」と思った方、正しいです。普通の言語では if
は関数では書けません。
しかしIdrisでは関数で書けるのです。
昔紹介したようにIdrisではLazyという機能を使うと遅延評価ができます
(IdrisはHaskellと違ってデフォルトではEager Evaluationをします)。
この Lazy
を使って ifThenElse
関数はこう定義されています。
ifThenElse : Bool -> Lazy a -> Lazy a -> a
ifThenElse True t e = t
ifThenElse False t e = e
ところで、Idrisでは関数をオーバーロードできるのでした。
ifThenElse
をオーバーロードするとどうなると思いますか?
そう、 if
式をオーバーロードできるのです。
少し試してみましょう。
以下の関数を定義します。
ifThenElse: Maybe a -> (a -> b) -> Lazy b -> b
ifThenElse (Just a) f _ = f a
ifThenElse Nothing _ b = b
これを読み込んだREPLでは Maybe
型の式に対して if
が使えます。
Idris> if Just 1 then (+1) else -1
2 : Integer
Idris> if Nothing then (+1) else -1
-1 : Integer
面白いですね。
因みにLazyの記事で紹介しましたがショートサーキットする論理積の &&
も Lazy
を使ったただの関数です。
Idrisでは演算子もユーザが定義できるのでこういった部分もユーザランドで定義できてしまうんですね。
do
記法
do
記法もシンタックシュガーです。
以下の do
式は
do
x <- hoge
y <- fuga x
z <- piyo y
pure $ chun z
以下のように展開されます。
hoge >>= (\x =>
fuga x >>= (\y =>
piyo y >>= (\z =>
pure $ chun z
)))
ここで (>>=)
もオーバーロード可能なので変テコな定義も可能なはずなんですが、パースエラーになってしまいました。
(>>=) : String -> a -> String
(>>=) s _ = s
hoge : String
hoge = do
"This is string"
"Others will be ignored"
["Nothing Happen"]
Type checking ./doNotation.idr
doNotation.idr:6:8-9:20:
|
6 | hoge = do
| ~~ ...
Incomplete term do "This is string"
"Others will be ignored"
["Nothing Happen"]
Holes: Main.hoge
謎です。
その他モナド関連記法
Idrisには何故か do
記法と役割の被る構文がいっぱいあります。
以下の関数を書き換えながら紹介します。
addDo : Maybe Int -> Maybe Int -> Maybe Int
addDo xs ys = do
x <- xs
y <- ys
pure $ x + y
内包表記
以前リスト内包表記と紹介しましたが、より一般にはモナド内包表記です。
例えば Maybe
に対しても使えます。
addComplehensions : Maybe Int -> Maybe Int -> Maybe Int
addComplehensions xs ys = [x + y | x <- xs, y <- ys]
ガード式も書けるんですが、Alternativeに触れないといけなくなるのでここでは流します。
!
記法
もうちょっと簡潔に書く方法として !
記法もあります。
addBang : Maybe Int -> Maybe Int -> Maybe Int
addBang x y = pure $ !x + !y
熟語括弧(idiom brackets)
これはMonadではなくApplicativeの記法です。
addBracket : Maybe Int -> Maybe Int -> Maybe Int
addBracket xs ys = [| xs + ys |]
これは以下に同じです。
addBracket : Maybe Int -> Maybe Int -> Maybe Int
addBracket xs ys = (+) <$> xs <*> ys
これはまあ、分かる。
ユーザ定義構文
Idrisには中置演算子をユーザが定義できる機能があるというのはAdvent Calendarで紹介した通りですが、もうちょっと一般にn項演算子を定義する機能があります。syntax 構文.... = 内容
の構文です。構文のところにはダブルクォートで囲ったキーワード ("キーワード"
)か、式を表わす [仮引数]
、 変数を表わす {仮変数}
が書けます 1。
内容のところにはIdrisの式を書きます。
if then else
は既にあるので unless then else
を定義してみましょう。
syntax "unless" [test] "then" [t] "else" [e] = ifThenElse test e t
Idris> unless True then 1 else 2
2 : Integer
Idris> unless False then 1 else 2
1 : Integer
次に変数を使うユーザ定義構文も定義してみましょう。 for
式です。
syntax for {x} "in" [xs] ":" [body] = forLoop xs (\x => body)
forLoop : List a -> (a -> b) -> List b
forLoop l f = map f l
{x}
と仮変数を使っていますね。
REPLで試すと動いているのが分かります。
Idris> for x in [1, 2, 3]: x + 1
[2, 3, 4] : List Integer
因みに syntax
で定義した構文はパターンとしても使えます。
例えば以下のように定義すると、 Some
を Just
の代わりに使えます。
syntax "Some" [x] = Just x
syntax "None" = Nothing
パターンとして使うときはこうですね。
hoge : Maybe Int -> Int
hoge x =
case x of
Some x => x
None => -1
値として使うときはこうです。
Idris> hoge (Some 1)
1 : Int
Idris> hoge None
-1 : Int
パターンでだけ、値でだけ定義したいときは pattern syntax
と term syntax
という構文もあります。
詳しくは syntax
rulesのドキュメントを参照して下さい。文法にあいまい性が生じそうなケースはエラーになるようです。
リテラルのオーバーロード
\x => body
と書いたらユーザ定義データの Lambda x body
になったり f x
と書いたら App f x
になったりのヤバそうな機能もあるようです。詳細は論文読んでねスタイルなのでここではスルーします。
興味のある方は dsl
notationのドキュメント を読んでみて下さい。
まとめ
Idrisのシンタックスシュガーとそれをオーバーロードする方法を紹介しました。
また、 syntax
rulesというn項演算子を作れる機能も紹介しました。
-
ドキュメントには言語のキーワードならダブルクォートなしで書けるとあるのですが、実際に試したらエラーになりました。 ↩︎