TL;DR:
如何确保randomRIO
(from System.Random
) 在给定do
语句中生成的值的持久性?
如何在 IO Monad 中使用可变结构?
我最初的问题是(非常)错误 - 我正在更新标题,以便未来想要了解在 IO monad 中使用可变结构的读者可以找到这篇文章。
更长的版本:
提醒:这看起来很长,但其中很多只是我概述了如何exercism.io
工作。(更新:最后两个代码块是我的代码的旧版本,作为参考包含在内,以防未来的读者希望根据评论/答案跟随代码中的迭代。)
运动概述:
我正在Robot Name
(极具指导性的)exercism.io 进行练习。该练习涉及创建一个Robot
能够存储名称的数据类型,该名称是随机生成的(练习Readme
包含在下面)。
对于那些不熟悉它的人来说,exercism.io
学习模型是基于学生生成代码的自动测试。每个练习都包含一系列测试(由测试作者编写),并且解决方案代码必须能够通过所有测试。我们的代码必须通过给定练习的测试文件中的所有测试,然后才能进入下一个练习——一个有效的模型,imo。(Robot Name
是练习#20左右。)
在这个特定的练习中,我们被要求创建一个Robot
数据类型和三个附带的函数mkRobot
:robotName
和resetName
。
mkRobot
生成一个实例Robot
robotName
生成并“返回”未命名的唯一名称Robot
(即,robotName
不覆盖预先存在的名称);如果 aRobot
已经有名称,它只是“返回”现有名称resetName
用一个新名称覆盖预先存在的名称。
在这个特定的练习中,有 7 个测试。测试检查:
- 0)
robotName
生成符合指定模式的名称(名称长度为 5 个字符,由两个字母后跟三个数字组成,例如 AB123、XQ915 等) - 1) 分配的名称
robotName
是持久的(即,假设我们创建机器人 A 并使用 为他(或她)分配一个名称robotName
;第二次调用robotName
(在机器人 A 上)不应覆盖他的名称) - 2)
robotName
为不同的机器人生成唯一的名称(即,它测试我们实际上是在随机化过程) - 3)
resetName
生成符合指定模式的名称(类似于测试#0) - 4) 分配的名称
resetName
是持久的 - 5)
resetName
分配不同的名称(即,resetName
给机器人一个与其当前名称不同的名称) - 6)
resetName
一次只影响一个机器人(即,假设我们有机器人 A 和机器人 B;重置机器人 A 的名称不应影响机器人 B 的名称)并且 (ii) 生成的名称resetName
是持久的
作为参考,这里是测试本身:https ://github.com/dchaudh/exercism-haskell-solutions/blob/master/robot-name/robot-name_test.hs
我被困在哪里:
版本 1(原始帖子): 目前,我的代码在三个测试(#1、#4 和 #6)中失败,所有这些测试都与机器人名称的持久性有关。.
版本 2:(临时) 现在我的代码仅在一个测试(#5)中失败 - 测试 5 与更改我们已经创建的机器人的名称有关
(感谢 bheklikr 的有用评论帮助我清理版本 1)
版本 3(最终版):由于 Cirdec 在下面的详尽帖子,代码现已修复(并通过了所有测试)。为了将来读者的利益,我将代码的最终版本与两个早期版本一起包括在内(以便它们可以跟随各种评论/答案)。
第 3 版(最终版): 这是基于 Cirdec 以下回答的最终版(我强烈建议您阅读)。事实证明,我最初的问题(询问如何使用 System.Random 创建持久变量)是完全错误的,因为我最初的实现是不健全的。相反,我的问题应该是询问如何在 IO monad 中使用可变结构(Cirdec 在下面解释)。
{-# LANGUAGE NoMonomorphismRestriction #-}
module Robot (robotName, mkRobot, resetName) where
import Data.Map (fromList, findWithDefault)
import System.Random (Random, randomRIO)
import Control.Monad (replicateM)
import Data.IORef (IORef, newIORef, modifyIORef, readIORef)
newtype Robot = Robot { name :: String }
mkRobot :: IO (IORef Robot)
mkRobot = mkRobotName >>= return . Robot >>= newIORef
robotName :: IORef Robot -> IO String
robotName rr = readIORef rr >>= return . name
resetName :: IORef Robot -> IO ()
resetName rr = mkRobotName >>=
\newName -> modifyIORef rr (\r -> r {name = newName})
mkRobotName :: IO String
mkRobotName = replicateM 2 getRandLetter >>=
\l -> replicateM 3 getRandNumber >>=
\n -> return $ l ++ n
getRandNumber :: IO Char
getRandNumber = fmap getNumber $ randomRIO (1, 10)
getRandLetter :: IO Char
getRandLetter = fmap getLetter $ randomRIO (1, 26)
getNumber :: Int -> Char
getNumber i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['0'..'9']
getLetter :: Int -> Char
getLetter i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['A'..'Z']
版本 2(临时):
基于 bheklikr 的评论,该评论清理了该mkRobotName
功能并帮助开始修复 mkRobot 功能。此版本的代码仅在测试 #5 中产生错误 - 测试 #5 与更改机器人的名称有关,这激发了对可变结构的需求......
{-# LANGUAGE NoMonomorphismRestriction #-}
module Robot (robotName, mkRobot, resetName) where
import Data.Map (fromList, findWithDefault)
import System.Random (Random, randomRIO)
import Control.Monad (replicateM)
data Robot = Robot (IO String)
resetName :: Robot -> IO String
resetName (Robot _) = mkRobotName >>= \name -> return name
mkRobot :: IO Robot
mkRobot = mkRobotName >>= \name -> return (Robot (return name))
robotName :: Robot -> IO String
robotName (Robot name) = name
-------------------------------------------------------------------------
--Supporting functions:
mkRobotName :: IO String
mkRobotName = replicateM 2 getRandLetter >>=
\l -> replicateM 3 getRandNumber >>=
\n -> return $ l ++ n
getRandNumber :: IO Char
getRandNumber = fmap getNumber $ randomRIO (1, 10)
getRandLetter :: IO Char
getRandLetter = fmap getLetter $ randomRIO (1, 26)
getNumber :: Int -> Char
getNumber i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['0'..'9']
getLetter :: Int -> Char
getLetter i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['A'..'Z']
版本 1(原始):回想起来,这可笑的糟糕。此版本在测试#1、#4 和#6 中失败,所有这些测试都与机器人名称的持久性有关。
{-# LANGUAGE NoMonomorphismRestriction #-}
module Robot (robotName, mkRobot, resetName) where
import Data.Map (fromList, findWithDefault)
import System.Random (Random, randomRIO)
data Robot = Robot (IO String)
resetName :: Robot -> IO Robot
resetName (Robot _) = return $ (Robot mkRobotName)
mkRobot :: IO Robot
mkRobot = return (Robot mkRobotName)
robotName :: Robot -> IO String
robotName (Robot name) = name
--the mass of code below is used to randomly generate names; it's probably
--possible to do it in way fewer lines. but the crux of the main problem lies
--with the three functions above
mkRobotName :: IO String
mkRobotName = getRandLetter >>=
\l1 -> getRandLetter >>=
\l2 -> getRandNumber >>=
\n1 -> getRandNumber >>=
\n2 -> getRandNumber >>=
\n3 -> return (l1:l2:n1:n2:n3:[])
getRandNumber :: IO Char
getRandNumber = randomRIO (1,10) >>= \i -> return $ getNumber i
getNumber :: Int -> Char
getNumber i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['0'..'9']
getRandLetter :: IO Char
getRandLetter = randomRIO (1,26) >>= \i -> return $ getLetter i
getLetter :: Int -> Char
getLetter i = findWithDefault ' ' i alphabet
where alphabet = fromList $ zip [1..] ['A'..'Z']