haskell-jp / beginners #25 at 2024-08-08 00:06:39 +0900

IOモナドの >>= について質問させてください

https://wiki.haskell.org/99_questions/21_to_28 の "Problem 23" を参考に
以下のようなコードを作成しました。
("abcdefgh" の中からランダムに 3 文字表示するプログラム)

[コード1]
import System.Random

rnd_select :: [a] -> Int -> IO [a]

rnd_select xs n = do
    let l = length xs - 1
    ys <- sequence . replicate n $ (randomRIO (0, l) :: IO Int)
    return $ map (xs !!) ys

これは想定通り動作しました。
ghci> rnd_select "abcdefgh" 3 >>= putStrLn
efc
it :: ()

この関数を少し変えて、`>>=` を使用するように変更しました。
[コード2]
rnd_select xs n = f >>= return . map (xs !!)
    where
        l = length xs - 1
        f = sequence . replicate n $ (randomRIO (0, l) :: IO Int)

この場合も問題ありません。

さらに、上記の f の部分を (where 句ではなく) 直接書いて以下のように変更しました。
[コード3]
rnd_select xs n = sequence . replicate n $ (randomRIO (0, l) :: IO Int) >>= return . map (xs !!)
    where
        l = length xs - 1

すると、以下のようにエラーとなります。
ghci> :l d
[1 of 2] Compiling Main             ( d.hs, interpreted )

d.hs:5:86: error: [GHC-83865]
    • Couldn't match type '[Int]' with 'Int'
      Expected: Int -> a
        Actual: [Int] -> [a]
    • In the second argument of '(.)', namely 'map (xs !!)'
      In the second argument of '(>>=)', namely 'return . map (xs !!)'
      In the second argument of '($)', namely
        '(randomRIO (0, l) :: IO Int) >>= return . map (xs !!)'
  |
5 | rnd_select xs n = sequence . replicate n $ (randomRIO (0, l) :: IO Int) >>= return . map (xs !!)
  |                                                                                      ^^^^^^^^^^^
Failed, no modules loaded.

メッセージから考えて [Int] ではなく Int が要求されているようだったので、以下のように変更したところ
問題なく動作しました。
[コード4]
rnd_select xs n = sequence . replicate n $ (randomRIO (0, l) :: IO Int) >>= return . (xs !!)
    where
        l = length xs - 1

説明が長くなってしまい恐縮ですが、`[コード3]` と [コード4] のような違いが出る理由が理解できませんでした。
sequence . replicate n $ (randomRIO (0, l) :: IO Int) >>= return . map (xs !!)

sequence . replicate n $ 残り全部 という結合の強さになってしまうので、
(randomRIO (0, l) :: IO Int) >>= return . map (xs !!) という部分だけで考えないといけないことになってしまいます。
(sequence . replicate n $ (randomRIO (0, l) :: IO Int)) >>= return . map (xs !!)
のように f 相当の部分を明示的にカッコでくくる必要があると思います。
これは >>= の話というよりは、 $ が最も弱い結合だということに起因するものですね
$ の優先順位の高さの問題だったのですね、理解できました。
今後、似たようなことがあったら () を付けて確認してみます。

長い説明にも関わらず、丁寧な回答をありがとうございました。