我正试图很好地掌握state -Monad(以及一般的Monad),但我正在努力使用state Monad和do符号重写下面的函数,这导致了我在这里提出的练习
import Control.Monad
import System.Random
import Data.Complex
import qualified System.Random as R
import Control.Monad.Trans.State.Lazy
giveRandomElement :: [a] -> State R.StdGen a
giveRandomElement lst = do
let n = length lst
rand <- state $ randomR (0, n-1)
return $ lst !! rand
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- state $ randomR (0, 1) :: StateT StdGen Data.Functor.Identity.Identity a
if (tal == 0) then true_answer
else giveRandomElement answers
很明显,tal
-变量出现在if
-子句和do
-表达式的第一行时,存在一些类型问题。从代码中可以看出,我试图通过一个特定的类型强制使用后者,以便使它对我自己来说也更加明确和清晰。我这样做是根据我第一次尝试将其强制为Int
类型时得到的编译器建议。然而,我不能在if语句中使用该值,并且我不确定如何转换或解包该值,以便我将其作为Int
。到目前为止,我已经尝试在tal <- ...
,resp <- get $ tal
之后添加以下行,但我得到了这个输出。
error:
* Couldn't match expected type: t0
-> StateT StdGen Data.Functor.Identity.Identity a1
with actual type: StateT s0 m0 s0
* The first argument of ($) takes one value argument,
but its type `StateT s0 m0 s0' has none
In a stmt of a 'do' block: resp <- get $ tal
In the expression:
do tal <- state $ randomR (0, 1)
resp <- get $ tal
if (resp == 0) then
giveRandomElement answers
else
giveRandomElement answers
* Relevant bindings include tal :: t0
此外,我感到困惑的是,什么是"打印"giveRandomElement
返回的结果的最佳方式,因为该类型是基于为State
-monad声明的类型,据我所知,它也不使用deriving Show
。但这也许可以通过像上面所询问的那样解压缩值来解决。
编辑
我使用了上面的包,尽管上面的代码中可能没有全部使用它们。我不确定哪个是由代码使用的,我怀疑qualified System.Random as R
以下代码行:
tal <- state $ randomR (0, 1) :: StateT StdGen Data.Functor.Identity.Identity a
很长,可能会导致一个水平滑动条出现,至少在我的平台上是这样。
所以很容易忽略,在它的最后,使用了a
类型变量,而它应该只是Int
。
而且,if
结构的两个分支使用不同的类型,使得该结构为非类型。then
分支给出了一个纯a
值,而else
分支给出了一个一元值。这很容易通过更改为:
if (tal == 0) then return true_answer
作为return
库函数将的参数包装到手边的monad中。
下面的代码,试图保持代码行足够短,似乎工作得很好:
import Control.Monad.State
import qualified System.Random as R
import qualified Data.Functor.Identity as DFI
giveRandomElement :: [a] -> State R.StdGen a
giveRandomElement lst = do
let n = length lst
rand <- state $ R.randomR (0, n-1)
return $ lst !! rand
type ActionType = StateT R.StdGen DFI.Identity Int
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- (state $ R.randomR (0, 1) :: ActionType)
if (tal == 0) then return true_answer
else giveRandomElement answers
main :: IO ()
main = do
let g0 = R.mkStdGen 4243
action = random_response_monad 20 [0..9]
(k, g1) = runState action g0
putStrLn $ "k is set to: " ++ (show k)
边注:代码也可以在没有复杂类型注释的情况下编译,像这样:
tal <- state $ R.randomR (0::Int, 1)
像这样的东西似乎可以工作:
random_response_monad :: a -> [a] -> State R.StdGen a
random_response_monad true_answer answers = do
tal <- state $ randomR (0 :: Int, 1)
if (tal == 0) then return true_answer
else giveRandomElement answers
两个变化:
- 使用类型注释告诉编译器
0
和1
是什么意思。一旦告诉编译器0
是哪种类型,那么1
就具有相同的类型。(请记住,在Haskell中,数字是多态的。如果没有更多的信息,Haskell将把0
这样的字面量看作是任何Num
实例。 return
在true_answer
前面
以下是GHCi的几个示例,似乎表明它可以工作:
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
4
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
1
ghci> evalState (random_response_monad 42 [0..9]) <$> newStdGen
42