README.md

# RTypes

RTypes is an Elixir library which helps automatically create a validation function for
a given user type. The function can be used to check the shape of the data after
de-serialisation or in unit-tests.

Let's suppose we have a type

```elixir
@type t :: 0..255
```

and we have a value `x`. To ensure that our value corresponds to the type `t` we
can use the function

```elixir
def is_t(x) when is_integer(x) and x >= 0 and x <= 255
```

Now, if we have a compound type

```elixir
@type list_of_ts :: [t]
```

and a value `xs`, we can use `is_list/1` guard on `xs` and then ensure that all
elements of the list conform to `t`. And if we have a more complex structure

```elixir
@type state(a, b) :: %{key1: {a, b}, key2: list_of_ts()}
```

and a value `s`, we can check that `s` is a map which has keys `key1` and
`key2`, apply the logic above for the value of `key2` and for any concrete types
`a` and `b` we can check that he value of `key1` is a tuple of length 2 and its
elements conform to `a` and `b` respectively. So we just recursively apply those
checks.

That's the gist of it.

## Usage

The library defines `make_validator/1` and `make_predicate/1` macros, and
`make_validator/3` and `make_predicate/3` functions which can be used to
build the functions at run time.  The difference between the two is that a
`validator` returns `:ok` or `{:error, reason}` where `reason` explains what went
wrong, while a `predicate` returns only `true` or `false`.

  ```elixir
  iex> require RTypes
  iex> port_number? = RTypes.make_predicate(:inet.port_number())
  iex> port_number?.(8080)
  true
  iex> port_number?.(80000)
  false
  iex> validate_is_kwlist = RTypes.make_validator(Keyword, :t, [{:type, 0, :pos_integer, []}])
  iex> validate_is_kwlist.(key1: 4, key2: 5)
  :ok
  iex> {:error, _reason} = validate_is_kwlist.([1, 2, 3])
  ```

## Data Generators

The library provides `Generator` module which defines the `make/4`
function and `make/2` macro to be used with property-based
frameworks. The
[`StreamData`](https://hexdocs.pm/stream_data/StreamData.html) backend
is provided with the library, while
[`PropCheck`](https://hexdocs.pm/propcheck/readme.html) backend can be
found in `rtypes_propcheck` library.

For example, to write a unit test for a pure function with a given
spec to use with `StreamData` framework one could write something
along the lines:

  ```elixir
  defmodule MyTest do
    use ExUnit.Case
    use ExUnitProperties

    require RTypes
    require RTypes.Generator, as: Generator

    # @spec f(arg_type) :: result_type
    arg_type_gen = Generator.make(arg_type, Generator.StreamData)
    result_type? = RTypes.make_predicate(result_type)

    property \"for any parameter `f/1` returs value of `result_type`\" do
      check all value <- arg_type_gen do
        assert result_type?.(f(value))
      end
    end
  end
  ```

## Implementation

A generated validation function is essentially a walk-the-tree interpreter
of the expanded AST that represents the type. However, instead of evaluating the
AST it applies basic type checks.

A generated predicate uses a different approach. It builds up a chain of
suspended function calls (closures) which mirrors the type's AST. The benchmarks
in `bench/` directory have shown that it works approximately 2x faster than the
interpreted version. The downside is that it provides no explanation or failed
cases.

## Notes

 - The type must be fully instantiated, that is, all the type parameters should
   be of a concrete type.

 - For practical reasons the generated function does not recurse down to
   `iolist()`, making only some simplified tests.

## TODO

 - Handle recursive types.

 - ~~Data generator~~ *DONE*.

 - Better error messages.