在Haskell中使用get从State-Monad中拆包和打印值



我正试图很好地掌握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

两个变化:

  • 使用类型注释告诉编译器01是什么意思。一旦告诉编译器0是哪种类型,那么1就具有相同的类型。(请记住,在Haskell中,数字是多态的。如果没有更多的信息,Haskell将把0这样的字面量看作是任何Num实例。
  • returntrue_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