Domo icon indicating copy to clipboard operation
Domo copied to clipboard

How to properly represent [a | b]?

Open bamorim opened this issue 1 year ago • 4 comments

First of all, this library is an amazing work. Having worked with a few type-related libraries on Elixir of my own, I know how hard it is to pull something like this. I started evaluating using Domo to validate some complex nested structures here at work but found a problem that makes using it a little bit harder. I have found a workaround, so nothing is blocking us.

This might not be necessarily a bug or an issue (it can be just that this is a known limitation and no plans to fixing it exist due to the complexity), it is more a conversation starter sharing some of my experience using it.

Currently, whenever we have a type [a | b] Domo treats it as [a] | [b].

Note: For me, this was not totally clear from the docs, but maybe it is the same as the limitation regarding parametric types?

That is, the following data structure

def Apple do
  use Domo
  defstruct []
  @type t() :: %__MODULE__{}
end

def Orange do
  use Domo
  defstruct []
  @type t() :: %__MODULE__{}
end

def FruitBasket do
  use Domo
  defstruct [fruits: []]
  @type t() :: %__MODULE__{fruits: [Apple.t() | Orange.t()]}
end

Will result in

assert {:ok, _} = FruitBasket.ensure_type(%FruitBasket{fruits: [%Apple{}, %Apple{}]})
assert {:ok, _} = FruitBasket.ensure_type(%FruitBasket{fruits: [%Orange{}, %Orange{}]})
assert {:error, _} = FruitBasket.ensure_type(%FruitBasket{fruits: [%Apple{}, %Orange{}]})

My current workaround is to do this weird thing (I'm actually doing something slightly different to propagate the errors, but it is similar):

# Apple and Orange the same

defmodule FruitValidator do
  use Domo
  defstruct [:fruit]
  @type t() :: %__MODULE__{fruit: Apple.t() | Orange.t()}
  
  def valid?(fruit) do
    case ensure_type(%__MODULE__{fruit: fruit}) do
      {:ok, _} -> true
      _ -> false
    end
  end
end

def FruitBasket do
  use Domo
  defstruct [fruits: []]
  
  @opaque fruit() :: map()
  precond(fruit: &FruitValidator.valid?/1)
  
  @type t() :: %__MODULE__{fruits: [fruit()]}
end

So, in the end I'm almost just using Domo underneath but the ergonomics are just much worse. Is that something you think is easily integrated into Domo itself? Do you have a better suggestion on how to deal with this?

bamorim avatar Oct 06 '23 13:10 bamorim

Hmm. Indeed Domo treats [a | b] as [a] | [b]. https://github.com/IvanRublev/Domo/blob/master/test/domo/type_ensurer_factory/resolver/or_test.exs#L200

I agree the better will be [a | b] -> [ all possible combinations of a and b ].

I think I'll be able to have detal look at it on the first week of November. So if you find how to update it earlier PRs are welcome!

P.S. There is a combine_or_args helper function which used to generate all possible combinations of types.

IvanRublev avatar Oct 11 '23 15:10 IvanRublev

I'll try to see if I can figure out a way myself, but probably will only have time for that early November as well, but will give another shot at the source code before that to see if I can spot any obvious way to represent that.

Thank you 🚀

bamorim avatar Oct 12 '23 08:10 bamorim

I gave it a shot today, but there is still a long way to go I guess. Here is the WIP commit: https://github.com/IvanRublev/Domo/commit/231f072454187c48d85cdb9df9f819a554fc6948

What is missing is mainly is how to combine preconditions.

Also, I think I messed up something related to kwlists with only one element, but I guess that's a start.

bamorim avatar Oct 14 '23 20:10 bamorim

@IvanRublev Hey! Is there any update/progress on this issue?

rslota avatar Feb 01 '24 12:02 rslota

Hey @rslota @bamorim, as they say, it's better later than never 😄

Please have a look at version 1.5.15 which supports sum types well, and in particular as a list element.

IvanRublev avatar Apr 29 '24 22:04 IvanRublev