Croma.Defun

Module that provides Croma.Defun.defun/2 macro.

Summary

Macros

Defines a function together with its typespec. This provides a lighter-weight syntax for functions with type specifications and functions with multiple clauses

Defines a private function together with its typespec. See defun/2 for usage of this macro

Defines a unit-testable private function together with its typespec. See defun/2 for usage of this macro. See also Croma.Defpt.defpt/2

Macros

defun(arg, list)

Defines a function together with its typespec. This provides a lighter-weight syntax for functions with type specifications and functions with multiple clauses.

Example

The following examples assume that Croma.Defun is imported (you can import it by use Croma).

defun f(a :: integer, b :: String.t) :: String.t do
  "#{a} #{b}"
end

The code above is expanded to the following function definition.

@spec f(integer, String.t) :: String.t
def f(a, b) do
  "#{a} #{b}"
end

Function with multiple clauses and/or pattern matching on parameters can be defined in the same way as case do ... end:

defun dumbmap(as :: [a], f :: (a -> b)) :: [b] when a: term, b: term do
  ([]     , _) -> []
  ([h | t], f) -> [f.(h) | dumbmap(t, f)]
end

is converted to

@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

Pattern matching on function parameter and omitting parameter's type

If you omit parameter’s type, its type is infered from the parameter’s expression. Suppose we have the following function:

defun f(%MyStruct{field1: field1, field2: field2}) :: String.t do
  "#{field1} #{field2}"
end

then the parameter type becomes MyStruct.t.

@spec f(MyStruct.t) :: String.t
def f(a1)
def f(%MyStruct{field1: field1, field2: field2}) do
  "#{field1} #{field2}"
end

Generating guards from argument types

Simple guard expressions can be generated by defun/2 using g[type] syntax. For example,

defun f(s :: g[String.t], i :: g[integer]) :: String.t do
  "#{s} #{i}"
end

is converted to the following function with when is_integer(i) guard.

@spec f(String.t, integer) :: String.t
def f(s, i)
def f(s, i) when is_binary(s) and is_integer(i) do
  "#{s} #{i}"
end

For supported types of guard-generation please refer to the source code of Croma.Guard.make/3.

Validating arguments based on their types

You can instrument check of preconditions on arguments by specifying argument’s type as v[type]. For instance,

defmodule MyString do
  use Croma.SubtypeOfString, pattern: ~r/^foo|bar$/
end

defun f(s :: v[MyString.t]) :: atom do
  String.to_atom(s)
end

becomes the following function definition that calls validate/1 at the top of its body:

@spec f(MyString.t) :: atom
def f(s)
def f(s) do
  s = case MyString.validate(s) do
    {:ok   , value } -> value
    {:error, reason} -> raise "..."
  end
  String.to_atom(s)
end

The generated code assumes that validate/1 function is defined in the same module as the specified type.

Known limitations

  • Overloaded typespecs are not supported.
  • Guard generation and validation are not allowed to be used with multi-clause syntax.
  • Using unquote fragment in parameter list is not fully supported.
defunp(arg, list)

Defines a private function together with its typespec. See defun/2 for usage of this macro.

defunpt(arg, list)

Defines a unit-testable private function together with its typespec. See defun/2 for usage of this macro. See also Croma.Defpt.defpt/2.