Если вы хотите сохранить общую операцию Bind
, используемую в let!
, то нельзя сказать, что F # должна использовать другую реализацию при переводе do!
(перегрузки обязательно должны перекрываться).
В общем, если вы хотите получить различное поведение для let!
и для do!
, то это говорит о том, что ваше вычислительное выражение, вероятно, определено неправильно. Эта концепция довольно гибкая, и ее можно использовать не только для объявления монад, но и для других целей, но, возможно, вы слишком растягиваете ее. Если вы можете написать больше информации о том, чего вы хотите достичь, это было бы полезно. Во всяком случае, вот некоторые возможные обходные пути ...
Вы можете добавить дополнительную упаковку и написать что-то вроде do! wrap <| expr
.
type Wrapped<'T> = W of 'T
type WrappedDo<'T> = WD of 'T
type FooBuilder() =
member b.Bind<'T, 'U>(x:Wrapped<'T>, f:'T->'U):'U = failwith "let!"
member b.Bind<'T, 'U>(x:WrappedDo<unit>, f:unit->'U):'U = failwith "do!"
member b.Return<'T>(x:'T):Wrapped<'T> = failwith "return"
let wrap (W a) = WD a
let bar arg = W arg
let foo = FooBuilder()
// Thanks to the added `wrap` call, this will use the second overload
foo { do! wrap <| bar()
return 1 }
// But if you forget to add `wrap` then you still get the usual `let!` implementation
foo { do! wrap <| bar()
return 1 }
Другой альтернативой может быть использование динамических типовых тестов. Это немного неэффективно (и немного не элегантно), но это может помочь, в зависимости от вашего сценария:
member b.Bind<'T, 'U>(x:Wrapped<'T>, f:'T->'U):'U =
if typeof<'T> = typeof<unit> then
failwith "do!"
else
failwith "let!"
Однако при записи let! () = bar
.
все равно будет использоваться перегрузка
do!
.