この記事はググって解決しづらかったこと Advent Calendar 2021の25日目の記事です。Haskell-jp WikiのHaskellの歩き方というページにもほぼ同じことを書きましたが、今回はよい機会なので実例を加えつつ詳しく紹介させてください。
Link to
here二項演算子(記号関数)の調べ方
よく知られているとおり、Haskellには二項演算子をプログラマーがかなり自由に定義できるという、とても変わった特徴があります。他のプログラミング言語でも使う標準的なもの(例: +
, *
, &&
など)を名前空間を絞って置き換えるほか、例えばかのlensパッケージのように、ライブラリーの作者があたかも新しい構文を作り上げるかのごとく独自の二項演算子を提供することができます。
これは面白い機能ではあるものの、しばしば混乱を招く機能でもあります。後述するその他の記号との区別がつきにくいですし、一般的な検索エンジンで検索することさえままなりません。Googleはプログラミングでよく使われる記号による検索をサポートはしているものの、Haskellでしか見ないような記号の組み合わせは到底無理でしょう。
そんな背景もあり、Haskellを使う人はしばしばHoogleなどの、関数名で検索できる検索エンジンを使用することになります。こちらは二項演算子の名前での検索もサポートしています。
例えばlensパッケージでおなじみの^.
で検索すると次のような結果になりました:
lensパッケージ以外でも、同様の^.
が定義されているのが分かりますね。lensパッケージは依存関係がとても大きい一方、^.
などの定義は十分単純でコピペしてもいいくらい小さいので、このようにいくつものパッケージで定義されています。
また、特によく使われる二項演算子はFPCompleteのウェブサイトでもまとめられています:
Link to
hereユーザーが定義した二項演算子ではないものの調べ方
Haskell、というかそのデファクトスタンダードな処理系であるGHCでは、言語拡張という形で長年新しい構文が提案されています1。その中には、当然これまでにない方法で記号を使っているものもあります。そうした記号はプログラマーが定義した関数ではないので、前述のHoogleなどを使った方法が通用しません。そこで、当ブログにも何度も寄稿いただいた@takenobu_hsさんが、言語拡張によるものも含めた、Haskellの構文における記号の一覧を作ってくださいました!
takenobu-hs/haskell-symbol-search-cheatsheet
実は日本語版もQiitaにあるのですが、上記のGitHub版の方が更新されているようです。そこで、今回はおまけとして、GitHub版の方にも載っている、GHCに最近(バージョン9.2.1以降に)追加された、新しいピリオド .
の使い方を紹介しましょう。
従来、Haskellでピリオドといえば関数合成を表す二項演算子でした:
> f x = x + 1
ghci> g x = x * 3
ghci> h = g . f
ghci> h 2
ghci9 -- 2 に + 1 して * 3 した結果
数学における関数合成の記号「g ∘ f」に似せてピリオドを採用したのでしょう。しかし、世は今まさに大「ピリオドといえばフィールド2へのアクセス演算子じゃろがい」時代です。それでなくてもHaskellのレコード型は扱いにくいと言われているのに、フィールドへのアクセスまで変なやり方でした3:
data SomeRecord =
SomeRecord { field1 :: String, field2 :: Int }
= SomeRecord "value1" 2
someRecord
> field1 someRecord
ghci"value1"
> field2 someRecord
ghci2
そこで、GHC 9.2からはOverloadedRecordDot
という言語拡張が導入され、これを有効にしたファイルではおなじみの言語のようにピリオドでレコードのフィールドにアクセスできるようになりました:
(以下はGHCiで使用した例です)
> :set -XOverloadedRecordDot
ghci
> someRecord.field1
ghci"value1"
> someRecord.field2
ghci2
-- ⚠️ピリオドの前後に空白を入れると関数合成として解釈されてしまう!
> someRecord . field2
ghci
<interactive>:5:1: error:
? Couldn't match expected type ‘Int -> c’
type ‘SomeRecord’
with actual ? 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まとめ
- 他のプログラマーが定義した、二項演算子(記号関数)を調べるときは:
- それ以外の場合は:
- Haskellのレコード型に嫌気が差したら:
- Haskell Day 2021における、fumievalさんの発表を観て
OverloadedRecordDot
拡張について勉強しましょう。
- Haskell Day 2021における、fumievalさんの発表を観て
🎁それでは2022年もHappy Haskell Hacking!!🎅
余談: ghc-proposalsに送られたPull requestを見ると、今どのような提案が議論されているか分かります。↩︎
他のプログラミング言語では「プロパティー」と呼ばれることも多いですが、ここではHaskellのレコード型における用語に合わせました。↩︎
個人的にはゲッターが関数になるのはとても直感的な気がして割と好きでしたが、確かにデメリットもとても多い仕様でした。セッターは単純な関数になってないですしね。↩︎