
2026/04/01 4:57
(2024)レコードを構築するときは、Applicative 演算子よりも `do` 記法の使用を優先してください。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
改善された要約
主なポイントは、モナド型コンストラクタでもアプリカティブ型コンストラクタでもレコードを構築する際に、従来の
<$> と <*> 演算子よりも do 記法(ApplicativeDo)を使用した方が好ましいということです。この手法は、より明確で複数行のコードを生成し、フィールドを書き込む順序に依存せず(順序非感度)、欠落しているフィールドについて有益なコンパイラ診断情報を提供します。演算子構文では見つけにくい点です。このアドバイスは特にモナドでないアプリカティブ(例:
optparse-applicative の Parser)に適用され、ApplicativeDo、レコードワイルドカード、および厳格なフィールドチェック(-Werror=missing-fields)などの GHC 拡張に依存します。このパターンは他のレコードベースのコンストラクタにも拡張可能ですが、位置付きまたは任意関数コンストラクタには適用できません。このスタイルを使用すると、保守性が高く、エラーメッセージが明確で、安全なライブラリを開発者とユーザーの両方に提供できます。
本文
以下は、レコードを組み立てる際に Applicative 演算子(
<$> / <*>)よりも do 記法を使うべき理由を簡潔に説明した投稿です。このアドバイスは、
Monad を実装している型コンストラクタ(例:IO)だけでなく、Applicative は実装しているが Monad ではない型コンストラクタ(例:optparse‑applicative パッケージの Parser 型コンストラクタ)にも適用できます。後者の場合は ApplicativeDo 言語拡張を有効にする必要があります。
ガイダンス
次のように書く代わりに:
data Person = Person { firstName :: String , lastName :: String } getPerson :: IO Person getPerson = Person <$> getLine <*> getLine
以下のように書くべきです。
{-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Werror=missing-fields #-} data Person = Person { firstName :: String , lastName :: String } getPerson :: IO Person getPerson = do firstName <- getLine lastName <- getLine return Person{..}
なぜ後者の方が優れているか
1. エルゴノミクス(扱いやすさ)
do 記法を使うと、レコード構築ロジックを一つの長い式に詰め込む必要がなくなるため、より扱いやすくなります。例えばユーザーに名前を入力してもらう場合:
-- Applicative 演算子(長い式) getPerson :: IO Person getPerson = Person <$> (putStrLn "Enter your first name:" *> getLine) <*> (putStrLn "Enter your last name:" *> getLine) -- do 記法(見やすい) getPerson :: IO Person getPerson = do putStrLn "Enter your first name:" firstName <- getLine putStrLn "Enter your last name:" lastName <- getLine return Person{..}
do 版は明確で、初心者にも扱いやすいです。
2. 順序に対する無感覚性
データ型のフィールド順を変えても:
data Person = Person { lastName :: String , firstName :: String }
Applicative バージョンは「名前を逆順で読み込む」ために壊れますが、
do バージョンは影響を受けません。一般に do 記法を使えばフィールド順の変更による挙動変化は起こりません。
3. より良いエラーメッセージ
新しいフィールドを追加した場合:
data Person = Person { alive :: Bool , firstName :: String , lastName :: String }
Applicative バージョンは型不一致に関する暗いエラーを出しますが、
do バージョンは次のような明確なメッセージを返します。
Example.hs:… • Fields of ‘Person’ not initialised: alive :: Bool • In the first argument of ‘return’, namely ‘Person {..}’ …
エラーが直接
alive フィールドの初期化不足であることを示します。
注意点
- このアドバイスはレコード構文で定義されたデータ型にのみ適用されます。位置引数や任意関数には当てはまりません。
- Applicative だけを実装している型コンストラクタ(
を持たない)ではMonad
拡張を有効にしてください。ApplicativeDo
以下は optparse‑applicative パッケージでコマンドライン引数パーサーを定義する例です。ここでも同じ
do スタイルのレコード構築がスムーズに機能します。
{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Werror=missing-fields #-} import Options.Applicative (Parser, ParserInfo) import qualified Options.Applicative as Options data Person = Person { firstName :: String , lastName :: String } deriving Show parsePerson :: Parser Person parsePerson = do firstName <- Options.strOption (Options.long "first-name" <> Options.help "Your first name" <> Options.metavar "NAME") lastName <- Options.strOption (Options.long "last-name" <> Options.help "Your last name" <> Options.metavar "NAME") return Person{..} parserInfo :: ParserInfo Person parserInfo = Options.info parsePerson (Options.progDesc "Parse and display a person's first and last name") main :: IO () main = do person <- Options.execParser parserInfo print person
この例は、Applicative でも同じ
do 記法でレコードを組み立てられることを示しています。