ocaml-ppx / ppx_deriving

Type-driven code generation for OCaml
MIT License
466 stars 89 forks source link

Deriving for types with a phantom type parameter #264

Open brokenpylons opened 2 years ago

brokenpylons commented 2 years ago

Is there a way to derive: eq, ord, show, for types that have a phantom type parameter? For example:

type 'a t = Thing

The equals function generated for this type would have a signature ('a -> 'a -> bool) -> 'a t -> 'a t -> bool, is there a way remove the first argument and generate just 'a t -> 'a t -> bool? The passed comparison function will never be used inside anyways.

brokenpylons commented 2 years ago

I later realized you could do:

type phantom = P
[@@deriving eq, ord, show]

type 'a t = Thing
[@@deriving eq, ord, show]

type usage = phantom t
[@@deriving eq, ord, show]

This way, it fills in the dummy argument and it still works. However, this solution is unsatisfactory, because the rest of the code still needs to account for this extra argument, so I'm reopening.

sim642 commented 2 years ago

If t isn't recursive (and thus its derived equal isn't recursive), then you should be able to just do

type 'a t = Thing
[@@deriving eq]
let equal = equal (fun _ _ -> assert false) (* 'a doesn't occur in constructor args, so this function is never called *)
brokenpylons commented 2 years ago

This doesn't work if you use this type inside another type and want to derive eq again.

type phantom = P
[@@deriving eq]

type 'a t = Thing
[@@deriving eq]

let equal x y = equal (fun _ _ -> false) x y

type usage = phantom t
[@@deriving eq]

This fails with This expression should not be a function, the expected type is 'a t.

type 'a t = Thing
[@@deriving eq]

let equal x y = equal (fun _ _ -> false) x y

type 'a usage = 'a t
[@@deriving eq]

This fails with This expression has type 'a -> 'a -> bool but an expression was expected of type 'b usage.

I guess you could combine this with my previous solution:

type phantom = P
[@@deriving eq]

type 'a t = Thing
[@@deriving eq]

let equal' x y = equal (fun _ _ -> false) x y

type 'a usage = 'a t
[@@deriving eq]

let equal_usage' x y = equal_usage (fun _ _ -> false) x y

Is this the best that can be done?

kit-ty-kate commented 2 years ago
type phantom = P
[@@deriving eq]

type 'a t = Thing
[@@deriving eq]

type 'a usage = 'a t
[@@deriving eq]

let equal = equal (fun _ _ -> assert false)
let equal_usage = equal_usage (fun _ _ -> assert false)

no need to define a new equal' function if equal is defined after the type definitions

sim642 commented 2 years ago

I suspect this reveals why such support wouldn't fully work. Even if for type 'a t = Thing we derive val equal: 'a t -> 'a t -> bool, then whenever t is used in another type that we want to derive for (e.g. in usage), it is instantiated with one type parameter and thus val equal: ('a -> 'a -> bool) -> 'a t -> 'a t -> bool is assumed to exist instead. Subsequent uses of t don't know that it's a phantom type parameter.

brokenpylons commented 2 years ago

Couldn't you examine the type, to tell if the type parameters are phantom? I guess that wouldn't work if the type was abstract, though (maybe if you could somehow tag the parameter as phantom).

If the usage is in a separate module, I need to do something like the following, right?

module Thing = struct
  module Deriving = struct
    type phantom = P
    [@@deriving eq]

    type 'a t = Thing
    [@@deriving eq]
  end
  include Deriving
  let equal x y = equal (fun _ _ -> false) x y
end

module Usage = struct
  module Deriving = struct
    type 'a t = 'a Thing.Deriving.t
    [@@deriving eq]
  end
  include Deriving
  let equal x y = equal (fun _ _ -> false) x y
end