【发布时间】:2014-11-07 20:35:43
【问题描述】:
TL;DR:我如何确保randomRIO(来自System.Random)生成的值在给定的@987654324 中的持久性@ 声明?
如何在 IO Monad 中使用可变结构?
我最初的问题是(非常)错误的 - 我正在更新标题,以便希望了解在 IO monad 中使用可变结构的未来读者可以找到这篇文章。
加长版:
提醒:
这看起来很长,但其中很多只是我概述了exercism.io 的工作原理。 (更新:最后两个代码块是我的代码的旧版本,作为参考包含在内,以防将来的读者希望根据 cmets/answers 跟随代码中的迭代。)
练习概述:
我正在研究来自(极具指导意义的)exercism.io 的Robot Name 练习。该练习涉及创建一个Robot 数据类型,该数据类型能够存储随机生成的名称(练习Readme 包含在下面)。
对于不熟悉它的人,exercism.io 学习模型基于对学生生成的代码的自动测试。每个练习都包含一系列测试(由测试作者编写),并且解决方案代码必须能够通过所有测试。我们的代码必须通过给定练习的测试文件中的所有测试,然后才能进入下一个练习——一个有效的模型,imo。 (Robot Name 是练习#20 左右。)
在这个特定的练习中,我们被要求创建一个 Robot 数据类型和三个附带的函数:mkRobot、robotName 和 resetName。
-
mkRobot生成Robot的实例 -
robotName为未命名的Robot生成并“返回”一个唯一名称(即,robotName不会覆盖预先存在的名称);如果Robot已经有了名字,它只会“返回”现有的名字 -
resetName用新名称覆盖现有名称。
在这个特定的练习中,有 7 个测试。测试检查:
- 0)
robotName生成符合指定模式的名称(a 名称长度为 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) 由 @ 生成的名称987654352@是持久的
作为参考,这里是测试本身:https://github.com/dchaudh/exercism-haskell-solutions/blob/master/robot-name/robot-name_test.hs
我被困在哪里:
版本 1(原帖): 目前,我的代码在三个测试(#1、#4 和 #6)上都失败了,所有这些都与机器人的名字。.
版本 2:(临时) 现在我的代码仅在一个测试(#5)中失败 - 测试 5 与更改我们已经创建的机器人的名称有关
(感谢 bheklikr 帮助我清理版本 1 的有用 cmets)
第 3 版(最终版):感谢 Cirdec 在下面的详尽帖子,代码现已修复(并通过了所有测试)。为了将来读者的利益,我将代码的最终版本与两个早期版本一起包括在内(以便它们可以跟随各种 cmets/answers)。
第 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 的 cmets 清理 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']
【问题讨论】:
-
你不应该有
data Robot = Robot String和mkRobot = mkRobotName >>= Robot吗?您现在正在做的是在Robot构造函数中存储一个 IO 操作,我想您想要做的是返回一个带有随机String的Robot,而不是返回随机字符串的操作. -
还有
mkRobotName = do { l <- replicateM 2 getRandLetter; n <- replicateM 3 getRandNumber; return $ l ++ n。 -
还有
getRandNumber = fmap getNumber $ randomRIO (1, 10)和类似的getRandLetter = fmap getLetter $ randomRIO (1, 26) -
我很惊讶你在我发帖后的大约 2 分钟内想出了
mkRobotName、getRandNumber和getRandLetter的更短版本。脱帽致敬...另外,关于Robot的类型,我选择使用Robot (IO String)而不是Robot String(我同意这更有意义),因为测试文件所期望的类型...跨度> -
测试文件是否期望您拥有
Robot (IO String)?如果是这样,请执行mkRobot = mkRobotName >>= Robot . return。那么robotName可以不用管,Robot里面的action就是return一个常量。