Haskellにおける記号の調べ方

Posted by YAMAMOTO Yuji(@igrep) on December 25, 2021

この記事はググって解決しづらかったこと Advent Calendar 202125日目の記事です。Haskell-jp WikiHaskellの歩き方というページにもほぼ同じことを書きましたが、今回はよい機会なので実例を加えつつ詳しく紹介させてください。

Link to
here
二項演算子(記号関数)の調べ方

よく知られているとおり、Haskellには二項演算子をプログラマーがかなり自由に定義できるという、とても変わった特徴があります。他のプログラミング言語でも使う標準的なもの(例: +, *, &&など)を名前空間を絞って置き換えるほか、例えばかのlensパッケージのように、ライブラリーの作者があたかも新しい構文を作り上げるかのごとく独自の二項演算子を提供することができます。

これは面白い機能ではあるものの、しばしば混乱を招く機能でもあります。後述するその他の記号との区別がつきにくいですし、一般的な検索エンジンで検索することさえままなりません。Googleはプログラミングでよく使われる記号による検索をサポートはしているものの、Haskellでしか見ないような記号の組み合わせは到底無理でしょう。

そんな背景もあり、Haskellを使う人はしばしばHoogleなどの、関数名で検索できる検索エンジンを使用することになります。こちらは二項演算子の名前での検索もサポートしています。

例えばlensパッケージでおなじみの^.で検索すると次のような結果になりました:

Hoogleによる検索結果の例

lensパッケージ以外でも、同様の^.が定義されているのが分かりますね。lensパッケージは依存関係がとても大きい一方、^.などの定義は十分単純でコピペしてもいいくらい小さいので、このようにいくつものパッケージで定義されています。

また、特によく使われる二項演算子はFPCompleteのウェブサイトでもまとめられています:

Operator Glossary

Link to
here
ユーザーが定義した二項演算子ではないものの調べ方

Haskell、というかそのデファクトスタンダードな処理系であるGHCでは、言語拡張という形で長年新しい構文が提案されています1。その中には、当然これまでにない方法で記号を使っているものもあります。そうした記号はプログラマーが定義した関数ではないので、前述のHoogleなどを使った方法が通用しません。そこで、当ブログにも何度も寄稿いただいた@takenobu_hsさんが、言語拡張によるものも含めた、Haskellの構文における記号の一覧を作ってくださいました!

takenobu-hs/haskell-symbol-search-cheatsheet

実は日本語版もQiitaあるのですが、上記のGitHub版の方が更新されているようです。そこで、今回はおまけとして、GitHub版の方にも載っているGHCに最近(バージョン9.2.1以降に)追加された、新しいピリオド . の使い方を紹介しましょう。

従来、Haskellでピリオドといえば関数合成を表す二項演算子でした:

ghci> f x = x + 1
ghci> g x = x * 3
ghci> h = g . f
ghci> h 2
9 -- 2 に + 1 して * 3 した結果

数学における関数合成の記号「gf」に似せてピリオドを採用したのでしょう。しかし、世は今まさに大「ピリオドといえばフィールド2へのアクセス演算子じゃろがい」時代です。それでなくてもHaskellのレコード型は扱いにくいと言われているのに、フィールドへのアクセスまで変なやり方でした3:

data SomeRecord =
  SomeRecord { field1 :: String, field2 :: Int }

someRecord = SomeRecord "value1" 2

ghci> field1 someRecord
"value1"

ghci> field2 someRecord
2

そこで、GHC 9.2からはOverloadedRecordDotという言語拡張が導入され、これを有効にしたファイルではおなじみの言語のようにピリオドでレコードのフィールドにアクセスできるようになりました:

(以下はGHCiで使用した例です)

ghci> :set -XOverloadedRecordDot

ghci> someRecord.field1
"value1"

ghci> someRecord.field2
2

-- ⚠️ピリオドの前後に空白を入れると関数合成として解釈されてしまう!
ghci> someRecord . field2

<interactive>:5:1: error:
    ? Couldn't match expected typeInt -> c’
                  with actual typeSomeRecord
    ? In the first argument of ‘(.)’, namely ‘someRecord’
      In the expression: someRecord . field2
      In an equation for ‘it’: it = someRecord . field2
    ? Relevant bindings include
        it :: SomeRecord -> c (bound at <interactive>:5:1)

OverloadedRecordDotについてのより詳しい解説は、Haskell Day 2021における、fumievalさんの発表をご覧ください。

Link to
here
まとめ

🎁それでは2022年もHappy Haskell Hacking!!🎅


  1. 余談: ghc-proposalsに送られたPull requestを見ると、今どのような提案が議論されているか分かります。↩︎

  2. 他のプログラミング言語では「プロパティー」と呼ばれることも多いですが、ここではHaskellのレコード型における用語に合わせました。↩︎

  3. 個人的にはゲッターが関数になるのはとても直感的な気がして割と好きでしたが、確かにデメリットもとても多い仕様でした。セッターは単純な関数になってないですしね。↩︎