Есть ли способ сделать разные реализации сделать! и разреши! в выражении вычисления? - PullRequest
1 голос
/ 06 апреля 2011

Мне нужно другое поведение для дел! и разреши! в моем собственном выражении вычисления.

Я пытаюсь добиться этого следующим образом:

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! и пусть!?

Ответы [ 2 ]

2 голосов
/ 06 апреля 2011

Если вы хотите сохранить общую операцию 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!.
1 голос
/ 06 апреля 2011

Вы можете попробовать что-то еще, немного некрасиво, но должно сработать:

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)

Он упаковывает (преобразовывает его в obj) и проверяет, имеет ли он тип unit, затем перенаправляетдо правильной перегрузки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...