Croma.Defun (croma v0.12.0)

View Source

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

Summary

Functions

Defines a function together with its typespec.

Defines a private function together with its typespec.

Defines a unit-testable private function together with its typespec.

Functions

defun(arg1, arg2)

(macro)

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.

Guard generation can be disabled by setting application config at compile time. For example, by putting the following into config/config.exs,

config :croma, [
  defun_generate_guard: false
]

then g[String.t] becomes semantically the same as String.t.

Validating arguments and return value based on their types

You can instrument check of pre/post conditions by specifying 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 valid?/1 at the top of its body:

@spec f(MyString.t) :: atom
def f(s)
def f(s) do
  if !MyString.valid?(s) do
    raise "..."
  end
  String.to_atom(s)
end

The generated code assumes that valid?/1 function is defined in the type module of the specified type. For primitive types croma defines their type modules and thus you can freely use e.g. v[integer].

Generating validation of arguments and return values can be disabled by setting application config during compilation.

config :croma, [
  defun_generate_validation: false
]

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.
  • try block is not implicitly started in body of defun, in contrast to def.

defunp(arg1, arg2)

(macro)

Defines a private function together with its typespec.

See defun/2 for usage of this macro.

defunpt(arg1, arg2)

(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.