是的,这确实是不可能的。考虑以下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。但是,在我看来这是不正确的,因为:
- 我们始终返回
mempty 并不是真的折叠任何东西。
- 通过始终返回
mempty,可以轻松地将每种数据类型变成Foldable 的实例。
产生Monoid m => m 类型值的唯一其他方法是将f 应用于x 类型a 的某个值。但是,我们没有任何 a 类型的值。如果我们可以将extract 值a 从State s a 应用到该值,那么我们可以将f 应用于该值,但我们已经证明不可能为State s a 定义一个永不崩溃的通用extract 函数。
因此,State s 不能成为Foldable 的实例,因此它不能成为Traversable 的实例。