Haskell:使用Monad类

3

作为一项任务,我需要在Haskell中使用单子并创建一个赌博游戏,该游戏有一个简单的规则:抛6个硬币,计算正面朝上的硬币数,掷骰子,如果其结果等于或大于计算出的正面朝上的硬币数,则获胜,否则失败。我被给予以下定义赌博单子的“框架”:

data Coin = H | T
    deriving (Bounded, Eq, Enum, Ord, Show)

data Dice = D1 | D2 | D3 | D4 | D5 | D6
    deriving (Bounded, Eq, Enum, Ord, Show)

data Outcome = Win | Lose
    deriving (Eq, Ord, Show)

class Monad m => MonadGamble m where
    toss :: m Coin
    roll :: m Dice

game :: MonadGamble m => m Outcome
game = undefined

然而,我对单子仍然很陌生,不知道如何使用它们。例如:游戏定义应该实现上述我解释过的游戏,但是我应该如何使用这个“赌博单子”,例如执行一个或多个投掷/摇动并获得结果值,以便我可以使用/处理它们?
此外,据我了解,单子总是有两个默认函数:返回和(>>=),但是我不明白这如何适用于MonadGable单子?
如果有人能帮助我,将不胜感激!
最好的问候, Skyfe。

你可以在没有实现 MonadGamble 的情况下编写整个游戏的逻辑,然后稍后编写 instance MonadGamble some_existing_monad where ... 以获得它运行的实际行为。 - bheklilr
只是为了明确:在Haskell中,“类型”(大致上)是一组值。而“类”(稍微粗略一些)是一组类型(而不是一组值)。因此,MonadGamble是一组类型,每个类型都具有>>=return操作,以及MonadGamble操作tossroll - Jonathan Cast
2个回答

8
首先,MonadGamble 在这里不是一个技术上的单子,而是一种类型类,扩展了一个单子,使其具有两个与之相关联的东西:tossroll,分别表示投掷或滚动的值。在 game 的类型签名中,m 是单子,并且它是 MonadGamble 的一个实例,因此我们自动拥有 tossroll
您可以在这里使用 Haskell 的 do 符号。我不会深入介绍,因为我不想做整个作业,但这是您编写测试两个硬币抛出相同结果的单子的方法:
twoFlips :: MonadGamble m => m Bool
twoFlips = do
    coin1 <- toss
    coin2 <- toss
    return (coin1 == coin2)

你可能会发现Control.Monad中的replicateM函数很有用,它允许我们重复一个单子操作并将结果返回一个列表:
import Control.Monad (replicateM)

tenCoins :: MonadGamble m => m [Coin]
tenCoins = replicateM 10 toss

谢谢!我想我应该使用Haskell的结构<-,尽管我不确定它除了将结果值存储以供最终函数使用外还有什么作用,但我不知道如何获取(例如)coin1和coin2的实际值? 因为这些是单子,但是我如何获得其中包含的真实值? (例如f coin1 = H或T)? - user2999349
这实际上就是 <- 的作用。它提取了单子中包含的“真实”值并为其赋予一个名称(当我们说我们“绑定”一个单子值时,这就是我们的意思)。我们只能在 do 块中执行此操作,因为我们被迫在块的末尾将该值重新包装成单子。 - Josh Kirklin

4
你可以将MonadGamble视为一种迷你语言,具有以下四个结构:
do
    x <- a
    b

该程序运行a程序,然后再运行b程序(在b中,变量x指的是a的结果)。

return x

这是一个简单的程序,它只返回x

toss

这是一个简单的程序,只会翻转一次硬币并返回结果(正面或反面),

roll

这是一个简单的程序,它掷一次骰子并返回结果(六个面之一:D1 - D6)。
请注意,Monad 构造中的 doreturn 也是 MonadGamble 语言的构造;这就是在 MonadGamble 的声明中含有 Monad m => 的原因。
你需要编写一个使用上述四个“构造”实现所描述游戏的程序。由于你还不熟悉单子,所以可能要仅使用这四个构造编写游戏,考虑如何通过编写自己的辅助函数来简化它,然后查看标准 Monad 库,以了解它为你的辅助函数提供了哪些名称(我怀疑你不需要任何它没有的东西)。
以下是一个程序,它掷骰子,然后翻转一次或两次硬币,具体取决于结果。
-- | Roll the die, then if the result is 1-3 flip the coin once, otherwise twice,
-- returning a list of the results.
roller = do
    d <- roll
    if d `elem` [ D1, D2, D3 ]
        then do
            c <- flip
            return [ c ]
        else do
            c0 <- flip
            c1 <- flip
            return [ c0, c1 ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接