【问题标题】:Understanding `bind` of `newtype Prob`了解`newtype Prob`的`bind`
【发布时间】:2014-10-25 05:43:14
【问题描述】:

Learn You a Haskell 呈现 Prob 新类型:

newtype Prob a = Prob { getProb :: [(a,Rational)] } deriving Show

这是Prob 的定义:

instance Functor Prob where  
    fmap f (Prob xs) = Prob $ map (\(x,p) -> (f x,p)) xs  

instance Monad Prob where
    return x = Prob [(x, 1%1)]
    p >>= f  = flatten (fmap f p)  

然后是辅助功能:

flatten :: Prob (Prob a) -> Prob a
flatten = Prob . convert . getProb 

convert :: [(Prob a, Rational)] -> [(a, Rational)]
convert = concat . (map f)

f :: (Prob a, Rational) -> [(a, Rational)]
f (p, r) = map (mult r) (getProb p)

mult :: Rational -> (a, Rational) -> (a, Rational)
mult r (x, y) = (x, r*y)

我编写了 flatten、convert、f 和 mult 函数,所以我对它们很满意。

那么我们将>>=应用到下面的例子中,涉及到一个数据类型Coin

data Coin = Heads | Tails deriving (Show, Eq)

coin :: Prob Coin 
coin = Prob [(Heads, 1%2), (Tails, 1%2)]

loadedCoin :: Prob Coin
loadedCoin = Prob [(Heads, 1%10), (Tails, 9%10)]

LYAH 说,If we throw all the coins at once, what are the odds of all of them landing tails?

flipTwo:: Prob Bool
flipTwo= do
  a <- coin       -- a has type `Coin`
  b <- loadedCoin -- similarly
  return (all (== Tails) [a,b])

调用flipTwo 返回:

Prob {getProb = [(False,1 % 20),(False,9 % 20),(False,1 % 20),(True,9 % 20)]}

flipTwo可以用&gt;&gt;=重写:

flipTwoBind' :: Prob Bool
flipTwoBind' = coin >>= 
                    \x -> loadedCoin   >>= 
                                       \y -> return (all (== Tails) [x,y])

我不了解return (all (== Tails) [x,y]) 的类型。由于它是&gt;&gt;= 的右侧,那么它的类型必须是a -&gt; m b(其中Monad m)。

我的理解是(all (==Tails) [x,y])返回True or False,但是return是如何导致上述结果的:

Prob {getProb = [(False,1 % 20),(False,9 % 20),(False,1 % 20),(True,9 % 20)]}?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    (我会打电话给你的coinfairCoin)你有:

    flipTwoBind' :: Prob Bool
    flipTwoBind' = fairCoin     >>=  g   where
       g x       = loadedCoin   >>=  h   where
         h y     = return z              where 
           z     = all (== Tails) [x,y]
    

    (&gt;&gt;=)的类型我们得到:

    fairCoin ::         Prob Coin
    (>>=) :: Monad m => m    a    ->  (a -> m b) -> m b       | m ~ Prob, a ~ Coin
                        fairCoin  >>=    g       :: m b       | g :: Coin -> Prob b
    flipTwoBind'                              :: Prob Bool    | m ~ Prob, b ~ Bool
    

    所以g :: Coin -&gt; Prob Boolg x :: Prob Bool 提供x :: Coin

    自从g x = loadedCoin &gt;&gt;= h,我们有

    loadedCoin ::       Prob Coin
    (>>=) :: Monad m => m    a    ->  (a -> m b) -> m    b 
                      loadedCoin  >>=    h       :: Prob Bool
    

    所以,h :: Coin -&gt; Prob Boolz :: Boolreturn z :: Prob Bool

    all ::  (a -> Bool) -> [a] -> Bool
    all        p           []  :: Bool
    
    return :: (Monad m) => a -> m a
    z      ::           Bool
    return                 z :: m Bool           | m ~ Prob so return z :: Prob Bool
    

    由于Prob a 本质上是a 结果对及其相应概率的标记关联列表,因此Prob BoolBool 结果及其概率对的列表。


    用具体的Prob一元代码翻译,内联所有函数,flipTwoBind'变成

    flipTwoBind' = fairCoin     >>=  g
       = flatten (fmap g fairCoin)
       = Prob . convert . getProb $ 
                 Prob $ map (\(x,p) -> (g x,p)) $ getProb fairCoin
       = Prob . concat . map (\(x,p) -> map (\(x, y) -> (x, p*y)) $ getProb x)
                      . map (\(x,p) -> (g x,p)) $ getProb fairCoin
    

    (看看ProbgetProb 在内部相互抵消的效果如何......)。

    切换到基于列表的普通代码(使用gL xs = getProb (g (Prob xs))fairCoinL = getProb fairCoin 等),相当于

       = concat . map (\(x,p) -> map (second (p*)) x)
                . map (\(x,p) -> (gL x,p)) $ fairCoinL
       = concat . map (\(x,p) -> map (second (p*)) $ gL x) $ fairCoinL
       = [(v,p*q) | (x,p) <- fairCoinL, (v,q) <- gL x]
       = ....
       = [(z,r)   | (x,p) <- [(Heads,   1%2),  (Tails,   1%2 )],   -- do a <- fairCoin
                    (y,q) <- [(Heads, p*1%10), (Tails, p*9%10)],   --    b <- loadedCoin
                    (z,r) <- [(all (== Tails) [x,y],   q*1%1 )] ]  --    return ... all ...
       = [(False,1 % 20),(False,9 % 20),(False,1 % 20),(True,9 % 20)]
    

    当然,上面推导中最后一行之前的那一行也可以写成

       = [(all (== Tails) [x,y], q)                                -- ... all ... <$>
                  | (x,p) <- [(Heads,   1%2),  (Tails,   1%2 )],   --   fairCoin <*>
                    (y,q) <- [(Heads, p*1%10), (Tails, p*9%10)] ]  --   loadedCoin
    

    因为(&gt;&gt;= return . f) === fmap f

    【讨论】:

      【解决方案2】:

      注意&gt;&gt;=运算符的RHS是一个lambda表达式,不是return的应用:

      \y -> return (all (== Tails) [x,y])
      

      此 lambda 具有预期的类型 (Monad m) =&gt; a -&gt; m b

      让我们从底部开始构建类型:

      正如你所说,all (== Tails) [x,y] 返回TrueFalse。换句话说,它的类型是Bool

      现在,检查 ghci 中 return 的类型,我们看到:

      Prelude> :t return
      return :: Monad m => a -> m a
      

      所以return (all (==Tails) [x,y])Monad m =&gt; m Boolean 类型。

      将其包装在 lambda 中,然后给出类型 (Monad m) =&gt; a -&gt; m Boolean

      (请注意,在此过程中,编译器会推断出具体的 monad 类型是 Prob。)

      您应该将return 视为采用常规值并将其包装到Monad 中。

      加法:

      我们来分析一下类型

      flipTwoBind' = coin >>= 
                      \x -> loadedCoin   >>= 
                                         \y -> return (all (== Tails) [x,y])
      

      我们首先注意到这里最外层的表达式是(&gt;&gt;=) 的应用程序,其类型为:

      Prelude> :t (>>=)
      (>>=) :: Monad m => m a -> (a -> m b) -> m b
      

      LHS 是coin,其类型为Prob Coin,因此我们立即推断出mProbaCoin。这意味着对于某些类型b,RHS 必须具有类型Coin -&gt; Prob b。现在让我们看看 RHS:

      \x -> loadedCoin >>= \y -> return (all (== Tails) [x,y])
      

      这里我们有一个 lambda,它返回 (&gt;&gt;=) 的应用结果,所以 lambda 有类型

      (Monad m) => a -> m b
      

      这与第一个(&gt;&gt;=) 的应用程序的预期类型相匹配,因此aCoinmProb

      现在分析(&gt;&gt;=)的内部应用,我们看到它的类型推断为

      (>>=) :: Prob Coin -> (Prob -> Prob b) -> Prob b
      

      我们已经分析了第二个(&gt;&gt;=)的RHS,所以b被推断为Bool

      (请注意,这可能不是编译器用来推断类型的确切顺序。它恰好是我在分析此答案的类型时所遵循的顺序。)

      【讨论】:

      • 谢谢,代码学徒。你能说更多关于Note that somewhere along the way, the compiler will deduce that the concrete monad type is Prob的信息吗?我相信Prob 的定义,即&gt;&gt;=flatten (fmap f p) 正在发挥作用,但我并不清楚。
      • @KevinMeredith 我不完全确定这些细节。我的猜测是您将不得不分析flipTwoBind' 的完整定义的类型。很可能会从coinloadedCoin 的类型中扣除。
      • @KevinMeredith 类型推导我加了更详细的分析
      猜你喜欢
      • 2022-11-16
      • 1970-01-01
      • 2012-02-20
      • 1970-01-01
      • 2010-09-05
      • 1970-01-01
      • 2011-04-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多