State Monad,为什么不是一个元组?

时间:2010-04-07 20:27:21

标签: .net f# functional-programming monads

我刚刚把头包裹在monad周围(至少我想我有),更具体地说是状态monad,有些人比我想象的更聪明,所以我可能是有了这个问题。

无论如何,状态monad通常用M< a>实现。就像这样(F#):

type State<'a, 'state> = State of ('state -> 'a * 'state)

现在我的问题:你有什么理由不能在这里使用元组吗?除此之外,MonadA<'a, 'b>MonadB<'a, 'b>之间可能存在歧义,这两者都将成为等效的('a * 'b)元组。

编辑:为了清晰起见添加了示例

type StateMonad() =
  member m.Return a = (fun s -> a, s)
  member m.Bind(x, f) = (fun s -> let a, s_ = x s in f a s_)

let state = new StateMonad()
let getState = (fun s -> s, s)
let setState s = (fun _ -> (), s) 
let execute m s = m s |> fst

3 个答案:

答案 0 :(得分:12)

状态monad 基本上使用类型'state -> 'res * 'state,它代表一个计算,它采用一些初始状态并产生结果(连同一个新的值国家)。

如果你问我们是否对这种类型给出一些特殊的名称(例如State<'state, 'res>)是否有任何区别,那么答案是它并不重要。为类型赋予一些特殊名称的唯一目的是使代码更具可读性。例如,让我们看看以下示例的两种可能的类型签名:

let foo n = state {
  let! m = getState()
  do! setState(m + 1)
  return sprintf "Result: %d" (n * m) }

// Using State<'state, 'res> type:
val foo : int -> State<int, string>

// Using the underlying representation:
val foo : int -> int -> int * state

第一种类型的签名更清楚地表明我们在一些monad中编写函数。第二个示例只是一个带有两个int值的函数。我认为第一个的主要好处是你可以更容易地意识到可以从其他monadic计算中使用该类型(使用state { ... }编写)。

但是,正如我已经指出的那样,这不是技术要求。人们可能会使用这种风格,因为许多monad来自Haskell,其中monad与类型(例如State<'state, 'res>)相关联,而不是计算构建器(例如作为state),因此在Haskell中为每个monad定义一个新类型听起来是个好主意。

答案 1 :(得分:6)

示例中monadic值的类型不仅仅是一个元组 - 它是一个返回元组的函数:

'state -> 'res * 'state

如果您问是否可以仅使用'state * 'res作为monadic计算的类型,那么答案是否定的。那是行不通的,因为没有办法(安全地)实现返回操作,返回操作必须具有以下类型签名:

// how would we get a value of type 'state in the implementation?
val return : 'a -> 'state * 'a

答案 2 :(得分:5)

啊,是的,如果问题是:我是否应该使用带有T类型数据值的单标签Discriminated Union,或者我应该使用T,那么您可以使用。

在Haskell中,您需要使用带有monad的数据标记,因为Haskell do语法基于值类型推断monad类型(元组表示可以是最多单个Monad的实例)。而在F#中,计算表达式是明确的monad类型(例如state { ... }async { ... }或其他),因此这种限制不是必需的,相同的表示类型可以用于多个monad。