これは何?
Haskellのtakeの実装を見ていたら、!_という書き方がされていて面白かったので簡単にまとめる。
実装を書き換えつつ、手元で動かしてみる
{-# LANGUAGE BangPatterns #-}
take' :: Int -> [a] -> [a]
take' n xs
| 0 < n = unsafeTake n xs
| otherwise = []
-- A version of take that takes the whole list if it's given an argument less
-- than 1.
{-# NOINLINE [0] unsafeTake #-} -- See Note [Inline FB functions]
unsafeTake :: Int -> [a] -> [a]
unsafeTake !_ [] = []
-- unsafeTake _ [] = []
unsafeTake 1 (x : _) = [x]
unsafeTake m (x : xs) = x : unsafeTake (m - 1) xs
main :: IO ()
main = do
print $ take' 3 [0 ..]
unsafeTakeの中に!_というワイルドカードを正格評価にしている箇所がある。
takeを使用する場合、これをunsafeTake _ [] = []に変更しても動作に影響しない。
しかし、unsafaTakeを直接呼ぶ場合には話が違ってくる。
_を使うとprint (unsafeTake undefined ([] :: [Int]))の結果が[]になるのに対して、
!_を使用するとエラーが発生する。
-- _を使う例
{-# LANGUAGE BangPatterns #-}
-- A version of take that takes the whole list if it's given an argument less
-- than 1.
{-# NOINLINE [0] unsafeTake #-} -- See Note [Inline FB functions]
unsafeTake :: Int -> [a] -> [a]
-- unsafeTake !_ [] = []
unsafeTake _ [] = []
unsafeTake 1 (x : _) = [x]
unsafeTake m (x : xs) = x : unsafeTake (m - 1) xs
main :: IO ()
main = do
print (unsafeTake undefined ([0..] :: [Int])) -- []
-- !_を使う例
{-# LANGUAGE BangPatterns #-}
-- A version of take that takes the whole list if it's given an argument less
-- than 1.
{-# NOINLINE [0] unsafeTake #-} -- See Note [Inline FB functions]
unsafeTake :: Int -> [a] -> [a]
unsafeTake !_ [] = []
-- unsafeTake _ [] = []
unsafeTake 1 (x : _) = [x]
unsafeTake m (x : xs) = x : unsafeTake (m - 1) xs
main :: IO ()
main = do
print (unsafeTake undefined ([0..] :: [Int])) -- 実行時エラー
test.hs: Prelude.undefined
CallStack (from HasCallStack):
undefined, called at test.hs:19:21 in main:Main
なぜ、これが起こるのか
Haskellは遅延評価型の言語であるため、_は評価されずに終了する。そのため、undefinedが投入可能になる。
一方で!_はundefinedが評価されるため、実行時エラーが発生する。
では、_に対してなんでも正格評価を使うべきなのか
意味によると思っている。
takeの場合には指定された数だけリストから要素を取り出すのでundefined個取り出すというのは例外として扱うべきだと思う。
逆にconst関数のような、_の値が関係ない関数の場合には遅延評価を使うべきなのかと思った。
-- 2つ引数を受取り、1つ目だけを返す関数
const :: a -> b -> a
const x _ = x
constって何につかうんだろうと思った方がいれば、Haskellのconst関数って何に使うんだろ...。を読むと良い。
つまり、意味的に適切になるのはどちらなのかで使い分けると良さそう。