#f# #computation-expression
#f# #вычисление-выражение
Вопрос:
Мне нужно другое поведение для do! и let! в моем пользовательском вычислительном выражении.
Я пытаюсь достичь этого следующим образом:
type FooBuilder() = class
member b.Bind<'T, 'U>(x:'T, f:unit->'U):'U = failwith "not implemented" //do! implementation
member b.Bind<'T, 'U>(x:'T, f:'T->'U):'U = failwith "not implemented" //let! implementation
member b.Return<'T>(x:'T):'T = failwith "not implemented" //return implementation
end
let foo = FooBuilder()
let x = foo {
do! ()
return 2
}
Но компилятор выдает мне ошибку:
Не удалось определить уникальную перегрузку для метода ‘Bind’ на основе информации о типе до этого программного пункта. Доступные перегрузки показаны ниже (или в окне списка ошибок). Может потребоваться аннотация типа.
Есть ли способ создать другую реализацию do! и let!?
Ответ №1:
Если вы хотите сохранить Bind
операцию, используемую в let!
generic, то нет способа сказать, что 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!"
Однако при этом все равно будет использоваться do!
перегрузка при записи let! () = bar
.
Ответ №2:
Вы могли бы попробовать что-то другое, немного уродливое, но должно сработать:
let bindU (x, f) = f x // you must use x, or it'll make the Bind method less generic.
let bindG (x, f) = f x
member b.Bind(x : 'a, f : 'a -> 'b) =
match box x with
| :? unit -> bindU (x, f)
| _ -> bindG (x, f)
Он выделяет a (преобразует его в obj
) и проверяет, имеет ли он тип unit
, затем перенаправляет на правильную перегрузку.
Комментарии:
1. Возможно, вы также захотите взглянуть на ramon.org.il/wp/2011/04 /… . Это дает вам немного больше свободы при использовании ключевых слов привязки.