首页 > 解决方案 > Haskell函数中的通用类型与刚性类型

问题描述

为什么 ghc 不抱怨以下函数中的类型是刚性的?

play :: (Monad m, MonadIO m, Random a) => a -> a -> m a
play r1 r2 = do
  randomRIO (r1, r2)

在上述情况下,m实际上是IO。此代码编译。而其他时候,当我在泛型函数中使用具体类型时,ghc 会抱怨我使用的是具体类型。我在这里想念什么吗?

例如,如果我说:

play :: (Monad m, MonadIO m, Random a) => a -> a -> m a
play r1 r2 = do
  randomRIO (1, 6::Int)

这次 ghc 抱怨我使用的是刚性类型。困扰我的事情是,IO它在某种程度上是一种具体或僵化的类型,因为它是IO而不是Maybe

编辑 1: 编译无错误的完整代码:

{-# LANGUAGE ConstraintKinds #-}
module Main where

-- showing how to program in the mtl style
import           Control.Monad.IO.Class
import           System.Random

type Game m a = (Monad m, MonadIO m, Random a, Show a)

-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
  yell "hi"
  randomRIO (r1, r2)
  --rollDice r1 r2

-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str

-- step 3
rollDice :: (Game m a) =>  a -> a ->  m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)

main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
--   play 1 (6::Int) >>= print
--   play 'a' 'z' >>= print
--   putStrLn "done"

Code that doesn't compile:

{-# LANGUAGE ConstraintKinds #-}
module Main where

-- showing how to program in the mtl style
import           Control.Monad.IO.Class
import           System.Random

type Game m a = (Monad m, MonadIO m, Random a, Show a)

-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
  yell "hi"
  randomIO 'a' 'z'
  --rollDice r1 r2

-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str

-- step 3
rollDice :: (Game m a) =>  a -> a ->  m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)

main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
--   play 1 (6::Int) >>= print
--   play 'a' 'z' >>= print
--   putStrLn "done"

这不编译:

{-# LANGUAGE ConstraintKinds #-}
module Main where

-- showing how to program in the mtl style
import           Control.Monad.IO.Class
import           System.Random

type Game m a = (Monad m, MonadIO m, Random a, Show a)

-- step 1
play :: (Game m a) => a -> a -> m a
play r1 r2 = do
  yell "hi"
  **randomRIO (1, 6::Int)**
  --rollDice r1 r2

-- step 2
yell :: (MonadIO m) => String -> m ()
yell str = liftIO $ putStrLn str

-- step 3
rollDice :: (Game m a) =>  a -> a ->  m a
rollDice a1 a2 = liftIO $ randomRIO (a1,a2)

main :: IO ()
main = putStrLn ""
-- main :: IO ()
-- main = do
--   play 1 (6::Int) >>= print
--   play 'a' 'z' >>= print
--   putStrLn "done"

更新

刚刚意识到我的困惑来自阅读错误的文档,即我在代码中使用 时random 正在阅读文档。random-1.1random-1.2

供人参考:

random-1.1
randomRIO :: (a, a) -> IO a

random-1.2
randomRIO :: (Random a, MonadIO m) => (a, a) -> m a  

标签: haskell

解决方案


我猜你正在使用random-1.2,它使用更通用的类型randomRIO.

在那个更新的包中,在 monad和 value typerandomRIO上都是多态的。它的类型是:ma

randomRIO :: (Random a, MonadIO m) => (a, a) -> m a 

因此,当您编写时(为清楚起见重命名类型变量)

play :: (Monad m1, MonadIO m1, Random a1) => a1 -> a1 -> m1 a1
play r1 r2 = do
  randomRIO (r1, r2)

GHC 推断m a ~ m1 a1,这意味着m ~ m1a ~ a1,因此randomRIO使用这些类型参数调用(m并且a在这里不是严格的)。请注意,IO这里根本不涉及:m可以是IO,但也可以是任何其他 monad(在MonadIO类中)。

相反,当你写

play :: (Monad m1, MonadIO m1, Random a1) => a1 -> a1 -> m1 a1
play r1 r2 = do
  randomRIO (1, 6::Int)

GHC 再次推断m a ~ m1 a1,因此m ~ m1and a ~ a1,但也推断,a ~ Int因为我们将一对Ints 传递给randomRIO。从我们推断出哪个触发了类型错误,因为a ~ a1刚性的。a ~ Inta1 ~ Inta1

如果我们只有一个更具体的类型randomRIO(比如我们之前的那个random-1.2

randomRIO :: (Random a) => (a, a) -> IO a

那么你的推理是正确的:我们会m1 ~ IO在类型推断期间得到,这会导致错误,因为m1它是刚性的。我们没有得到这个,因为randomRIO它更普遍。


推荐阅读