haskell - Powerset-over-Reader monad 是否存在?

标签 haskell monads reader-monad

用于环境共享和不确定性的规范“Monad 实例”如下(使用伪 Haskell,因为 Haskell 的 Data.Set 当然不是 Monadic):

eta :: a -> r -> {a} -- '{a}' means the type of a set of a's
eta x = \r -> {x}

bind :: (r -> {a}) -> (a -> r -> {b}) -> r -> {b}
m `bind` f = \r -> {v | x ∈ m r, v ∈ f x r}

通常,当尝试将 Powerset(List、Writer 等)等“容器”单子(monad)与第二个单子(monad) m(此处大致为 Reader)组合时,一个“包装” m 围绕容器 monad,如上所示。

那么,我想知道以下潜在的 Powerset-over-Reader 规范:

eta' :: a -> {r -> a}
eta' x = {\r -> x}

bind' :: {r -> a} -> (a -> {r -> b}) -> {r -> b}
m `bind'` f = {rb | x <- m, ∀r: ∃rb' ∈ f (x r): rb r == rb' r}

这看起来并不明显疯狂(我确实意识到 GHCi 无法检查许多 rbrb' 的 rb r == rb' r ),但是 bind' 足够复杂,以至于(对我来说)很难检查 monad 定律是否成立。

我的问题是,eta'bind' 是否真的是一元的——如果不是,那么违反了哪一条法律,以及这可能对应于什么样的意外行为?

第二个问题是,假设 eta'bind' 不是单子(monad),我们如何确定是否存在具有这些类型是什么?

最佳答案

有趣的问题。这是我的看法——让我们看看我是否有任何失误!

首先,我将用(稍微不那么伪的)Haskell 拼写你的签名:

return :: a -> PSet (r -> a)
(>>=) :: PSet (r -> a) -> (a -> PSet (r -> b)) -> PSet (r -> b))

在继续之前,值得一提的是两个实际的复杂情况。首先,正如您已经观察到的,由于 Eq 和/或 Ord 约束,给予集合 FunctorMonad 实例并不简单;无论如何,there are ways around it。其次,更令人担忧的是,对于您为 (>>=) 建议的类型,有必要从 a 中提取 PSet (r -> a) ,而没有任何明显的 r s 供应 - 或者,换句话说,您的 (>>=) 需要遍历函数仿函数 (->) r 的。当然,这在一般情况下是不可能的,而且即使可能也是不切实际的——至少就 haskell 而言是这样。无论如何,出于我们的推测目的,可以假设我们可以通过将函数应用于所有可能的 (->) r 值来遍历 r。我将通过手动波浪形 universe :: PSet r 集来表明这一点,该集的命名是为了纪念 this package 。我还将利用 universe :: PSet (r -> b) ,并假设我们可以判断两个 r -> b 函数是否在某个 r 上一致,即使不需要 Eq 约束。 (伪 Haskell 确实变得非常假!)

初步评论,这是我的方法的伪 Haskell 版本:

return :: a -> PSet (r -> a)
return x = singleton (const x)

(>>=) :: PSet (r -> a) -> (a -> PSet (r -> b)) -> PSet (r -> b))
m >>= f = unionMap (\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            any (\rb' -> rb' r == rb r) (f (x r)))
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) m
    where
    unionMap f = unions . map f
    intersectionMap f = intersections . map f

接下来,单子(monad)定律:

m >>= return = m
return y >>= f = f y
m >>= f >>= g = m >>= \y -> f y >>= g

(顺便说一句,在做这类事情时,最好记住我们正在使用的类的其他表示 - 在本例中,我们有 join(>=>) 作为 (>>=) 的替代 - 作为切换演示文稿可能会让您选择的实例的使用更加愉快。这里我将坚持使用 (>>=)Monad 演示文稿。)

从第一定律开始......

m >>= return = m
m >>= return -- LHS
unionMap (\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            any (\rb' -> rb' r == rb r) (singleton (const (x r))))
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) m
unionMap (\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            const (x r) r == rb r)
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) m
unionMap (\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            x r == rb r)
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) m
-- In other words, rb has to agree with x for all r. 
unionMap (\x -> singleton x) m
m -- RHS

一关下来,还有两关。

return y >>= f = f y
return y -- LHS
unionMap (\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            any (\rb' -> rb' r == rb r) (f (x r)))
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) (singleton (const y))
(\x ->
    intersectionMap (\r ->
        filter (\rb -> 
            any (\rb' -> rb' r == rb r) (f (x r)))
            (universe :: PSet (r -> b)))
        (universe :: PSet r)) (const y)
intersectionMap (\r ->
    filter (\rb -> 
        any (\rb' -> rb' r == rb r) (f (const y r)))
        (universe :: PSet (r -> b)))
    (universe :: PSet r)
intersectionMap (\r ->
    filter (\rb -> 
        any (\rb' -> rb' r == rb r) (f y)))
        (universe :: PSet (r -> b)))
    (universe :: PSet r)
-- This set includes all functions that agree with at least one function
-- from (f y) at each r.
因此,

return y >>= f 可能是一个比 f y 大得多的集合。我们违反了第二定律;因此,我们没有 monad —— 至少这里提出的实例没有。

<小时/>

附录:这是一个实际的、可运行的函数实现,它至少对于处理小型类型来说足够有用。它利用了前面提到的 universe 包。

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module FunSet where

import Data.Universe
import Data.Map (Map)
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
import Data.Int
import Data.Bool

-- FunSet and its would-be monad instance

newtype FunSet r a = FunSet { runFunSet :: Set (Fun r a) }
    deriving (Eq, Ord, Show)

fsreturn :: (Finite a, Finite r, Ord r) => a -> FunSet r a
fsreturn x = FunSet (S.singleton (toFun (const x)))

-- Perhaps we should think of a better name for this...
fsbind :: forall r a b.
    (Ord r, Finite r, Ord a, Ord b, Finite b, Eq b)
    => FunSet r a -> (a -> FunSet r b) -> FunSet r b
fsbind (FunSet s) f = FunSet $
    unionMap (\x ->
        intersectionMap (\r ->
            S.filter (\rb ->
                any (\rb' -> funApply rb' r == funApply rb r)
                    ((runFunSet . f) (funApply x r)))
                (universeF' :: Set (Fun r b)))
            (universeF' :: Set r)) s

toFunSet :: (Finite r, Finite a, Ord r, Ord a) => [r -> a] -> FunSet r a
toFunSet = FunSet . S.fromList . fmap toFun

-- Materialised functions

newtype Fun r a = Fun { unFun :: Map r a }
    deriving (Eq, Ord, Show, Functor)

instance (Finite r, Ord r, Universe a) => Universe (Fun r a) where
    universe = fmap (Fun . (\f ->
        foldr (\x m ->
            M.insert x (f x) m) M.empty universe))
        universe

instance (Finite r, Ord r, Finite a) => Finite (Fun r a) where
    universeF = universe

funApply :: Ord r => Fun r a -> r -> a
funApply f r = maybe
    (error "funApply: Partial functions are not fun")
    id (M.lookup r (unFun f))

toFun :: (Finite r, Finite a, Ord r) => (r -> a) -> Fun r a
toFun f = Fun (M.fromList (fmap ((,) <$> id <*> f) universeF))

-- Set utilities

unionMap :: (Ord a, Ord b) => (a -> Set b) -> (Set a -> Set b)
unionMap f = S.foldl S.union S.empty . S.map f

-- Note that this is partial. Since for our immediate purposes the only
-- consequence is that r in FunSet r a cannot be Void, I didn't bother
-- with making it cleaner.
intersectionMap :: (Ord a, Ord b) => (a -> Set b) -> (Set a -> Set b)
intersectionMap f s = case ss of
    [] -> error "intersectionMap: Intersection of empty set of sets"
    _ -> foldl1 S.intersection ss
    where
    ss = S.toList (S.map f s)

universeF' :: (Finite a, Ord a) => Set a
universeF' = S.fromList universeF

-- Demo

main :: IO ()
main = do
    let andor = toFunSet [uncurry (&&), uncurry (||)]
    print andor -- Two truth tables
    print $ funApply (toFun (2+)) (3 :: Int8) -- 5
    print $ (S.map (flip funApply (7 :: Int8)) . runFunSet)
        (fsreturn (Just True)) -- fromList [Just True]
    -- First monad law demo
    print $ fsbind andor fsreturn == andor -- True
    -- Second monad law demo
    let twoToFour = [ bool (Left False) (Left True)
                    , bool (Left False) (Right False)]
        decider b = toFunSet
            (fmap (. bool (uncurry (&&)) (uncurry (||)) b) twoToFour)
    print $ fsbind (fsreturn True) decider == decider True -- False (!)

关于haskell - Powerset-over-Reader monad 是否存在?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42262575/

相关文章:

haskell - 我可以在这里使用绑定(bind)/fmap吗

haskell创建一棵不平衡树

f# - 在 F# 的计算表达式中定义新关键字

haskell - 为什么 Haskell 标准库中没有 <<?

haskell - 理解 Reader monad

haskell - 测试和应用

haskell - 类型族实例中的约束

state - MonadState get 和 put 的原因?

haskell - 如何查看Monad Reader的代码?

haskell - 修改变压器堆栈中的内部读取器