witchcrafters / type_class

(Semi-)principled type classes for Elixir
https://hex.pm/packages/type_class
MIT License
140 stars 16 forks source link

Q: How to work with functional values #14

Closed alfert closed 5 years ago

alfert commented 6 years ago

I am implementing a Witchcraft.Functor, having an unary function as data inside a struct:

defclass Generator extends Witchcraft.Functor
where do
    @type gen_fun_t :: (non_neg_integer -> any)
    defdata do
      gen_fun :: gen_fun_t() 
    end
end

The Functor's map function is implemented as

def map(gen, map_fun) do
  fun seed ->
        gen.gen_fun.(seed)
        |> map_fun.()
   end
   |> Generator.new()
end

The property testing of Functor fails because the struct is compared and the functional values are different by definition of Erlang/Elixir (which is the reason why equals implements specific comparison method).

Is this the case for @force_type_instance true, is this a bug of equals (where it could be useful to define a equal function the type class instance) or am I missing an alternative approach?

expede commented 6 years ago

Yes, unfortunately comparing functions is a limitation in most languages. You need to apply a function to get a value, and compare those. I've run across this in a few places in Algae (ex. State). There are two workarounds:

  1. Write a wrapping struct for functions of certain arities, and write generators for those
  2. Use @force_type_instance true, and test the properties elsewhere

I recommend the second one, most for pragmatism. You'll get a warning message during compilation that automatic prop testing is being overwritten, but the instance will be lawful if your manual tests pass.

expede commented 6 years ago

Ah, actually I had also forgotten about custom_generator, which lets you define your own instance-specific generator. This is very useful for testing functions since you need to make them concrete, but that depends on things like arity.

Example