Repository: keathley/norm (elixir)

Issue:
Library is super fussy about paramater-less functions
A couple of examples come to mind. My concern is more with the error messages than the philosophy behind it.

---
(1) Calling Norm functions without `()`

The following is, of course, fine:

```elixir
"test" |> conform!(spec(is_binary()))
```

But when you drop the parentheses on `is_binary`...:

```elixir
"test" |> conform!(spec(is_binary))
```

you get the cryptic error message:

```elixir
** (ArgumentError) Norm can't build a spec from: is_binary
    (norm) lib/norm/spec.ex:74: Norm.Spec.build/1
    (norm) expanding macro: Norm.spec/1
    iex:8: (file)
    (elixir) expanding macro: Kernel.|>/2
    iex:8: (file)
```

---
(2) One's own functions in modules that use Norm:

In the following code, note that `new` is not written as `new()`:

```elixir
defmodule Movie do
  use Norm

  defstruct title: "The Fly", year: 1986

  def s(),
    do:
      schema(%__MODULE__{
        title: spec(is_binary()),
        year: spec(is_integer())
      })

  def new, do: %__MODULE__{}
end
```

When compiling, I get the cryptic error:

```elixir
** (ArgumentError) argument error
    :erlang.length(nil)
    (norm) lib/norm/contract.ex:166: Norm.Contract.fa/1
    (norm) expanding macro: Norm.Contract.def/2
    lib/movie.ex:13: Movie (module)
```

It does detect the correct line number (13) though.

Pull request description:
Allow ellision of parens on single arity functions
Closes #33 

Patch:
diff --git a/lib/norm/spec.ex b/lib/norm/spec.ex
index a11cedc..7bdcdf7 100644
--- a/lib/norm/spec.ex
+++ b/lib/norm/spec.ex
@@ -55,6 +55,19 @@ defmodule Norm.Spec do
     end
   end
 
+  # Function without parens
+  def build(quoted = {a, _, _}) when is_atom(a) do
+    predicate = Macro.to_string(quoted) <> "()"
+
+    quote do
+      run = fn input ->
+        input |> unquote(quoted)
+      end
+
+      %Spec{predicate: unquote(predicate), f: run, generator: unquote(a)}
+    end
+  end
+
   # Remote call
   def build({{:., _, _}, _, _} = quoted) do
     predicate = Macro.to_string(quoted)


Test patch:
diff --git a/test/norm/spec_test.exs b/test/norm/spec_test.exs
index 4dc084b..e2b608d 100644
--- a/test/norm/spec_test.exs
+++ b/test/norm/spec_test.exs
@@ -43,6 +43,24 @@ defmodule Norm.SpecTest do
       assert "foo" == conform!("foo", foo)
       assert {:error, [%{spec: "Foo.match?(\"foo\")", input: "bar", path: []}]} == conform("bar", foo)
     end
+
+    test "supports eliding of parenthesis around functions" do
+      require Integer
+
+      evens = spec(is_integer and Integer.is_even)
+      assert conform!(2, evens) == 2
+      assert {:error, errors} = conform("1", evens)
+      assert errors == [
+        %{input: "1", path: [], spec: "is_integer()"}
+      ]
+
+      matcher = spec(Foo.match?("foo") and is_binary)
+      assert conform!("foo", matcher) == "foo"
+      assert {:error, errors} = conform("1", matcher)
+      assert errors == [
+        %{input: "1", path: [], spec: "Foo.match?(\"foo\")"}
+      ]
+    end
   end
 
   describe "generation" do