【问题标题】:Why isn't the state monad traversable?为什么状态单子不可遍历?
【发布时间】:2015-11-16 13:25:14
【问题描述】:

直观地说,在我看来,可以将 traverse 与状态单子一起使用,例如:

traverse (\a -> [a, a+1]) (state (\s -> (1, s + 1))) 
  = [state (\s -> (1, s + 1), state (\s -> (2, s + 1)]

但是 state monad 没有实现 Traversable 类型类。这是为什么?我试图找出一种实现状态单子遍历的方法,似乎困难在于提取状态单子的结果,以便将其传递给作为遍历的第一个参数给出的函数。不可能吗?

【问题讨论】:

  • 我很想标记这是 dfeuer 建议链接的副本,因为那里的答案讨论了这个确切的事情。纪尧姆,你有什么异议吗?
  • @DanielWagner:将这两个问题联系起来是个好主意,但我不会将这个问题标记为另一个问题。这个更具体地是关于状态单子(也许正因为如此,至少我更容易理解)。此外,下面的答案和您对链接问题的回答是相反的(如果我很好理解的话)。下面的答案解释了为什么不可能遍历状态单子,而在您对链接问题的回答中,您提出了一种方法。

标签: haskell


【解决方案1】:

要实现Traversable t,需要实现这个类型的函数:

sequenceA :: forall f a. Applicative f => t (f a) -> f (t a)

tState s 时,变为:

sequenceA :: forall f a. Applicative f => State s (f a) -> f (State s a)

显然我们不能为所有s 编写实现,因为我们必须知道如何无中生有地创建sf a继续。这将是一个矛盾。

但是对于s的某些类,可以编写满足类型的实例。如果我们假设sMonoid,我们可以这样做:

instance (Monoid m) => Traversable (State m) where
  sequenceA (State run) = fmap (\a -> State (\s' -> (mappend s s', a)) fa 
    where (s, fa) = run mempty

但这确实满足Traversable 法律。其中一项法律是:

traverse Identity = Identity

(回想一下traverse f = sequence . fmap f

这个定律显然不成立,因为输出动作mappends 而输入动作不成立。好的,我们不要mappend

instance (Monoid m) => Traversable (State m) where
  sequenceA (State run) = fmap (\a -> State (\_ -> (s, a)) fa 
    where (s, fa) = run mempty

这无济于事,因为现在输出操作忽略其输入并替换 mempty 而输入操作没有。

这并不意味着没有类型 s 我们无法为其构造合法的 Traverse (State s) 实例。例如,State () 简化为 Identity,这绝对是可遍历的。

如果我们能做到State (),为什么不能State Bool?我们可以在runStateTrueFalse 上的原始操作runState,将结果存储在地图中,然后让生成的状态操作在该地图中执行查找。这适用于任何无限可枚举状态域。详情请见this answer

【讨论】:

【解决方案2】:

成为Traversable 需要成为Foldable;但是State monad 不是——你不能foldMap 它,因为它的值很简单,不在这里。

type State s = StateT s Indentity
newtype StateT s m a = State { runState :: s -> m (s, a) }

如您所见,此处没有立即折叠的 a,它是函数的唯一结果。

【讨论】:

  • 可以很简单的做成Foldable;任何类似* -> * 的东西都可以。 instance Foldable (StateT s m) where foldMap _ _ = mempty。但是Traversable 要求更高。
  • 我展示了如何为State here 实现FoldableTraversable
【解决方案3】:

是的,这确实是不可能的。考虑以下State 的定义:

newtype State s a = State { runState :: s -> (a, s) }

extract 这个数据类型的值,我们首先需要提供一些状态。

如果我们知道状态的类型,我们可以创建一个专门的extract 函数。例如:

extract' :: State () a -> a
extract' (State f) = f ()

extractT :: State Bool a -> a
extractT (State f) = f True

extractF :: State Bool a -> a
extractF (State f) = f False

但是,我们无法创建通用提取函数。例如:

extract :: State s a -> a
extract (State f) = f undefined

上面的extract 函数是通用的。我们唯一能提供的状态是 ⊥这是不正确的。只有当函数 f :: s -> (a, s) 透明地传递其输入时,它才是安全的(即 f = (,) a 用于某些值 a)。但是,f 可能会获取一些状态并使用它来生成一些值和新状态。因此,f 可以不透明地使用其输入,并且如果输入是 ⊥然后我们得到一个错误。

因此,我们无法为 State 数据类型创建通用 extract 函数。

现在,要成为Traversable 的实例的数据类型首先需要是Foldable 的实例。因此,要使State 成为Traversable 的实例,我们首先需要定义以下实例:

instance Foldable (State s) where
    foldMap f (State g) = mempty
    -- or
    foldMap f (State g) = let x = f (extract g) in mconcat [x]
    -- or
    foldMap f (State g) = let x = f (extract g) in mconcat [x,x]
    -- or
    foldMap f (State g) = let x = f (extract g) in mconcat [x,x,x]
    -- ad infinitum

请注意,foldMap 的类型为 Monoid m => (a -> m) -> State s a -> m。因此,表达式foldMap f (State g) 必须返回Monoid m => m 类型的值。简单地说,我们总是可以通过定义foldMap = const (const mempty) 来返回mempty。但是,在我看来这是不正确的,因为:

  1. 我们始终返回 mempty 并不是真的折叠任何东西。
  2. 通过始终返回mempty,可以轻松地将每种数据类型变成Foldable 的实例。

产生Monoid m => m 类型值的唯一其他方法是将f 应用于x 类型a 的某个值。但是,我们没有任何 a 类型的值。如果我们可以将extractaState s a 应用到该值,那么我们可以将f 应用于该值,但我们已经证明不可能为State s a 定义一个永不崩溃的通用extract 函数。

因此,State s 不能成为Foldable 的实例,因此它不能成为Traversable 的实例。

【讨论】:

  • 我不认为“extract 无法定义”意味着“foldMap 无法定义”。比如我不能写extract :: Unit a -> a where data Unit a = Unit,但是我可以写foldMap f _ = mempty
  • @DanielWagner 我们正在谈论状态单子。我从来没有说过 foldMap 不能为 any 数据类型定义,如果 extract 不能为该数据类型定义。
  • State 有什么特别之处,这意味着我必须能够定义extract 才能定义foldMap?换句话说,如果有一些类型我可以定义foldMap 而没有extract,我为什么要相信我不能为State 做呢?
  • 或者,更重要的是:State Void a 在道德上与Unit a 相同。那么我为什么要相信我不能为State Void a 定义foldMap,即使我不能为State Void a 定义extract
  • 那么,你想定义foldMap f (State g) = mempty?你可以这样做,我明白你在做什么(在我的回答中,我说foldMap 只能根据extract 来定义,这确实是错误的)。事实上,您可以通过定义foldMap = const (const mempty) 轻松地将任何数据类型设为Foldable 的实例。但是,这不是很有用。当您将foldMap 定义为const (const mempty) 时,您不会“折叠”任何数据结构。因此,我想说,这个定义不仅微不足道,而且是错误的。不过,感谢您指出这一点。我会更新的。
【解决方案4】:

这样的功能不可能存在。

您正在请求具有此签名的函数:

trav :: (a -> [a]) -> (s -> (a,s)) -> [ s -> (a,s) ]

也就是说,给定一个函数f :: a -> [a] 和一个状态计算,trav 返回一个列表。特别是,如果我给你f 和一个状态计算,你应该能够告诉我结果列表是否为空。

现在考虑这个f

f :: Int -> [Int]
f 0 = []
f a = [a]

并尝试评估trav f (\s -> (s,s)),甚至尝试确定它是否为空列表。

你可以写这个函数:

trav' :: [ a -> a ] -> (s -> (a,s)) -> [ s -> (a,s) ]

因为结果列表将始终具有相同的长度。

【讨论】:

  • 您说函数trav 不存在,然后您要求读者尝试评估trav f (\s -> (s, s))。你的答案一开始就很好,但你只是挥舞着你的结论。
  • 我要求 OP 告诉我结果应该是什么,以证明 trav 应该做什么的概念定义不明确。您对如何改进我的回答有什么建议吗?
  • 我接受你的挑战。我声明trav f (\s -> (s, s)) 的长度为0。现在呢?
猜你喜欢
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-17
  • 1970-01-01
相关资源
最近更新 更多