Croma
=====
Elixir macro utilities.
- [API Documentation](http://hexdocs.pm/croma/)
- [Hex package information](https://hex.pm/packages/croma)
[](https://hex.pm/packages/croma)
[](https://hex.pm/packages/croma)
[](https://travis-ci.org/skirino/croma)
[](https://coveralls.io/r/skirino/croma?branch=master)
[](http://inch-ci.org/github/skirino/croma)
[](https://github.com/skirino/croma/issues)
[](https://github.com/skirino/croma/pulls)
## Usage
- Add `:croma` as a mix dependency.
- `$ mix deps.get`
- Add `use Croma` to import macros defined in this package.
- Hack!
## Defining functions
### `Croma.Defpt.defpt`
- Unit-testable `defp` that is simply converted to
- `def` if `Mix.env == :test`,
- `defp` otherwise.
- This is particularly useful when e.g. you want to test your module's internal logic
which is implemented as a pure function and thus easily testable.
### `Croma.Defun`
- Type specification oriented function definition
- Example 1
```ex
import Croma.Defun
defun f(a :: integer, b :: String.t) :: String.t do
"#{a} #{b}"
end
```
is expanded to
```ex
@spec f(integer, String.t) :: String.t
def f(a, b) do
"#{a} #{b}"
end
```
- Example 2
```ex
import Croma.Defun
defun dumbmap(as :: [a], f :: (a -> b)) :: [b] when a: term, b: term do
([] , _) -> []
([h | t], f) -> [f.(h) | dumbmap(t, f)]
end
```
is expanded to
```ex
@spec dumbmap([a], (a -> b)) :: [b] when a: term, b: term
def dumbmap(as, f)
def dumbmap([], _) do
[]
end
def dumbmap([h | t], f) do
[f.(h) | dumbmap(t, f)]
end
```
- There are also `defunp` and `defunpt` macros for private functions.
- Limitations:
- Pattern matching against function parameters should use `(param1, param2) when guards -> block` style.
- Overloaded typespecs are not supported.
- Using unquote fragment in parameter list is not fully supported.
## `Croma.Monad`
- An interface definition of the monad typeclass.
- Modules that `use Croma.Monad` must implement the following interface:
- `@type t(a)` with a type parameter `a`.
- `@spec pure(a: a) :: t(a) when a: any`
- `@spec bind(t(a), (a -> t(b))) :: t(b) when a: any, b: any`
- By using the concrete implementations of the above interface, `Croma.Monad` provides the default implementations of the following functions:
- As Functor:
- `@spec map(t(a), (a -> b)) :: t(b) when a: any, b: any`
- As Applicative:
- `@spec ap(t(a), t((a -> b))) :: t(b) when a: any, b: any`
- `@spec sequence([t(a)]) :: t([a]) when a: any`
- Note that the order of parameters in `map`/`ap` is different from that of Haskell counterparts, in order to leverage Elixir's pipe operator `|>`.
- `Croma.Monad` also provides `bind`-less syntax similar to Haskell's do-notation.
For example,
```ex
MonadImpl.m do
x <- mx
y <- my
pure f(x, y)
end
```
is converted to
```ex
MonadImpl.bind(mx, fn x ->
MonadImpl.bind(my, fn y ->
MonadImpl.pure f(x, y)
end)
end)
```
### `Croma.Result`
- `Corma.Result.t(a)` is defined as `@type t(a) :: {:ok, a} | {:error, any}`.
This module implements `Croma.Monad` interface.
### `Croma.ListMonad`
- Implementation of `Croma.Monad` for lists.
`Croma.ListMonad.t(a)` is just an alias to `[a]`.
## Working with structs
### `Croma.Struct`
- Utility module to define structs with type specification and validation functions.
```ex
iex> defmodule I do
...> @type t :: integer
...> def validate(i) when is_integer(i), do: {:ok, i}
...> def validate(_), do: {:error, {:invalid_value, [__MODULE__]}}
...> def default, do: 0
...> end
...> defmodule S do
...> use Croma.Struct, fields: [i: I]
...> end
...> S.validate([i: 5])
{:ok, %S{i: 5}}
...> S.validate(%{i: "not_an_integer"})
{:error, {:invalid_value, [S, I]}}
...> {:ok, s} = S.new([])
{:ok, %S{i: 0}}
...> S.update(s, [i: 2])
{:ok, %S{i: 2}}
...> S.update(s, %{"i" => "not_an_integer"})
{:error, {:invalid_value, [S, I]}}
```
- Some helper modules for "per-field module"s that are passed as options to `use Croma.Struct` (e.g. `I` in the above example) are available.
- Wrappers of built-in types such as `Croma.String`, `Croma.Integer`, etc.
- Utility modules such as `Croma.SubtypeOfString` to define "subtypes" of existing types.
- Ad-hoc module generators defined in `Croma.TypeGen`.
### `Croma.StructCallSyntax`
- A new syntax (which uses `~>` operator) for calls to functions that take structs as 1st arguments.
```ex
iex> defmodule S do
...> defstruct [:a, :b]
...> def f(s, i) do
...> s.a + s.b + i
...> end
...> end
...> import Croma.StructCallSyntax
...> s = %S{a: 1, b: 2}
...> s~>f(3) # => S.f(s, 3)
6
```