Я только что перенес голову вокруг монад (по крайней мере, я хотел бы думать, что я имею), и более конкретно монада состояния, который некоторые люди, которые являются путем, более умным затем меня вычисленный, таким образом, я - вероятно, способ с этим вопросом.
Так или иначе монада состояния обычно реализуется с 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
Монада состояния по существу работает с типом '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
Первая сигнатура типа более четко говорит о том, что мы пишем функцию в некоторой монаде. Второй пример - это просто функция, которая принимает два значения int
. Я думаю, что главное преимущество первого состоит в том, что вам легче понять, что тип может использоваться из других монадических вычислений (написанных с использованием состояния {...}
).
Однако, как я уже отмечал, это не техническое требование. Люди, вероятно, используют этот стиль, потому что многие монады пришли из Haskell, где монады связаны с типом (например, State <'state,' res>
), а не с построитель вычислений (например, state
), поэтому кажется хорошей идеей определить новый тип для каждой монады в Haskell.
Тип монадического значения в вашем примере - это не просто кортеж - это функция, возвращающая кортеж:
'state -> 'res * 'state
Если вы спрашиваете, действительно ли вы можете использовать просто 'state *' res
в качестве типа монадических вычислений, тогда ответ будет отрицательным. Это не сработает, потому что нет способа (безопасно) реализовать операцию возврата, которая должна иметь следующую сигнатуру типа:
// how would we get a value of type 'state in the implementation?
val return : 'a -> 'state * 'a
Ах, да, если возникает вопрос: следует ли мне использовать одинарный тег Discriminated Union, который несет значение данных типа T, или я должен просто использовать T, тогда вы можете использовать любой из них.
В Haskell вам необходимо использовать тег данных с монадами, поскольку синтаксис Haskell do
определяет тип монады на основе типов значений (представление кортежа может быть экземпляром не более одной монады) . В то время как в F # выражения вычислений явно относятся к типу монады (например, состояние {...}
или async {...}
или что-то еще), поэтому в этом ограничении нет необходимости, один и тот же тип представления может использоваться для нескольких монад.