gpt4 book ai didi

haskell - 如何在 IO monad 中使用可变结构

转载 作者:行者123 更新时间:2023-12-01 09:24:53 25 4
gpt4 key购买 nike

TL;博士:
如何确保 randomRIO 生成的值的持久性(来自 System.Random )在给定的 do 中陈述?
我如何在 IO Monad 中使用可变结构?

我最初的问题是(非常)错误 - 我正在更新标题,以便 future 想要了解在 IO monad 中使用可变结构的读者可以找到这篇文章。

加长版:

一抬头:
这看起来很长,但很多只是我概述了 exercism.io作品。 (更新:最后两个代码块是我的代码的旧版本,作为引用包含在内,以防将来的读者希望根据评论/答案跟随代码中的迭代。)

练习概述:

我正在处理 Robot Name来自(非常有指导意义的)exercism.io 的练习。练习包括创建一个 Robot能够存储随机生成的名称的数据类型(下面包含练习 Readme)。

对于那些不熟悉它的人,exercism.io学习模型基于学生生成代码的自动化测试。每个练习由一系列测试(由测试作者编写)组成,解决方案代码必须能够通过所有这些测试。我们的代码必须通过给定练习的测试文件中的所有测试,然后才能进入下一个练习——一个有效的模型,imo。 ( Robot Name 是练习 #20 左右。)

在这个特定的练习中,我们被要求创建一个 Robot数据类型和三个附带函数:mkRobot , robotNameresetName .

  • mkRobot生成 Robot 的实例
  • robotName为未命名的 Robot 生成并“返回”一个唯一名称(即,robotName 不会覆盖预先存在的名称);如果 Robot已经有一个名字,它只是“返回”现有的名字
  • resetName用新名称覆盖先前存在的名称。

  • 在这个特定的练习中,有 7 个测试。测试检查:
  • 0) robotName生成符合指定模式的名称(a
    名称长度为 5 个字符,由两个字母组成,后跟
    三位数,如AB123、XQ915等)
  • 1) 由 robotName 分配的名称是持久的(例如,假设我们创建机器人 A 并使用 robotName 为他(或她)分配一个名字;第二次(在机器人 A 上)调用 robotName 不应覆盖他的名字)
  • 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']

    最佳答案

    让我们根据测试的要求从类型开始。 mkRobot返回 IO 中的内容

    mkRobot :: IO r
    robotName获取从 mkRobot 返回的内容并返回 IO String .
    robotName :: r -> IO String

    最后, resetName获取从 mkRobot 返回的内容并产生一个 IO行动。从未使用过此操作的返回值,因此我们将使用单位类型 ()对于它这是正常的 IO在 Hasekll 中没有结果的操作。
    resetName :: r -> IO ()

    根据测试,不管 r需要能够表现得像它被 resetName 突变一样.对于在 IO 中表现得像可变的事物,我们有许多选择。 : IORef s, STRef s, MVars s 和软件事务内存。我对简单问题的首选是 IORef .我将采取与您略有不同的策略,并将 IORef 分开。来自什么 Robot是。
    newtype Robot = Robot {name :: String}

    这离开 Robot一种非常纯的数据类型。那我就用 IORef Robot为了什么 r位于测试界面中。
    IORef s 提供了五个非常有用的函数来使用它们,我们将使用其中的三个。 newIORef :: a -> IO (IORef a)做一个新的 IORef持有提供的值。 readIORef :: IORef a -> IO a读取存储在 IORef 中的值. modifyIORef :: IORef a -> (a -> a) -> IO ()将该函数应用于存储在 IORef 中的值.还有另外两个非常有用的函数我们不会使用, writeIORef它设置值而不查看那里的内容,和 atomicModifyIORef它解决了编写多线程程序时大约一半的共享内存问题。我们将导入我们将使用的三个
    import Data.IORef (IORef, newIORef, modifyIORef, readIORef)

    当我们做一个新的 Robot我们将制作一个新的 IORef RobotnewIORef .
    mkRobot :: IO (IORef Robot)
    mkRobot = mkRobotName >>= return . Robot >>= newIORef

    当我们读到名字时,我们会读到 RobotreadIORef ,然后 return Robotname
    robotName :: IORef Robot -> IO String
    robotName rr = readIORef rr >>= return . name

    最后, resetName将变异 IORef .我们将为机器人取一个新名称 mkRobotName ,然后拨打 modifyIORef具有将机器人的名称设置为新名称的功能。
    resetName :: IORef Robot -> IO ()
    resetName rr = mkRobotName >>=
    \newName -> modifyIORef rr (\r -> r {name = newName})

    函数 \r -> r {name = newName}const (Robot newName) 相同,除了它只会改变 name如果我们稍后决定向 Robot 添加一些其他字段数据类型。

    关于haskell - 如何在 IO monad 中使用可变结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26809658/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com