View Source ExPropriate.MarkedFunctions (ExPropriate v0.1.0)

This module handles expropriation of function-level granularity.

It can be set up like this:

defmodule MyModule do
  use ExPropriate

  # Function becomes public
  @expropriate true
  defp expropriated_function,
    do: :am_expropriated

  # Functions with multiple bodies only need to be tagged on the first body
  @expropriate true
  defp divide_by(number) when is_integer(number) and number > 0,
    do: {:ok, div(100, number)}

  defp divide_by(_other),
    do: :error

  # Untagged functions remain private
  defp remains_private,
    do: :am_private
end

MyModule.expropriated_function
# :am_expropriated

MyModule.divide_by(2)
# { :ok, 50 }

MyModule.divide_by(0)
# :error

MyModule.remains_private
# (UndefinedFunctionError) function MyModule.remains_private/0 is undefined or private.

The objective of this module was to be able to explicitly state which functions need to be expropriated. The tradeoff is that this module is more "intrusive" than the module-level granularity, since it overrides both Kernel.def/2 and Kernel.defp/2.

Also a friendly reminder that the functions and macros contained in this module are for internal use of the library and it's advised against using them directly.

Link to this section Summary

Types

AST containing the definition of a function's body.

AST containing the definition of a function's head.

Tuple containing a function's name and arity.

Functions

Defines a public function via Kernel.def/2, and sets the necessary attributes for the following bodies.

Defines a private function via Kernel.defp/2

Defines a public function via Kernel.def/2

Transforms a function's head AST into a tuple containing the name and arity of the function.

Generates AST to prevent warnings for unused @expropriate attributes.

Generates the AST necessary to expropriate only tagged functions on compile time.

Link to this section Types

Specs

fn_body() :: Macro.t() | nil

AST containing the definition of a function's body.

Typically it's a keyword list containing at least a [do: expr]. It also may be nil (if only the function head is being declared), and it may also contain other keys like :rescue, :catch, :after, etc.

[do: {:+, [], [1, 2]}]
|> Macro.to_string
# "[do: 1 + 2]"

[
  do: {
    :/,
    [],
    [{:a, [], nil}, {:b, [], Elixir}]
  },
  rescue: [{
    :->,
    [],
    [
      [{:error, [], nil}],
      {{:., [], [{:__aliases__, [], [:IO]}, :puts]}, [],
      [{:error, [], nil}]}
    ]
  }]
]
|> Macro.to_string
# "[do: a / b, rescue: (error -> IO.puts(error))]"

Specs

fn_head() :: Macro.t()

AST containing the definition of a function's head.

Contains at least the function name, arguments and clauses. Does not contain def or defp.

Examples:

{:with_no_args, [], []}
|> Macro.to_string
# "with_no_args()"

{:with_two_args, [], [{:arg1, [], nil}, {:arg2, [], nil}}
|> Macro.to_string
# "with_two_args(arg1, arg2)"

{
  :when,
  [],
  [
    {:with_when, [], [{:arg, [], nil}]},
    {:>, [], [{:arg,  [], nil}, 0]}
  ]
}
|> Macro.to_string
# "with_when(arg) when arg > 1"

Specs

fn_name() :: {name :: atom(), arity :: non_neg_integer()}

Tuple containing a function's name and arity.

{:my_function, 2}

Link to this section Functions

Link to this macro

def(fn_head, fn_body \\ nil)

View Source (macro)

An override of Kernel.def/2.

This macro checks if the @expropriate attribute was set to true before defining a public function, and outputs a warning if that's the case.

Regardless of the warning, it always defines the fuction using Kernel.def/2

Link to this function

define_first_public_body(fn_name, fn_head, fn_body)

View Source

Specs

define_first_public_body(fn_name(), fn_head(), fn_body()) :: Macro.t()

Defines a public function via Kernel.def/2, and sets the necessary attributes for the following bodies.

Called by expropriated functions when they define their first body. In addition to defining the function as public, this function also sets the @expropriate attribute back to false and registers the function's name to the @expropriated_names attribute.

If the function has multiple bodies, it will directly call define_public/2 instead.

Link to this function

define_private(fn_head, fn_body)

View Source

Specs

define_private(fn_head(), fn_body()) :: Macro.t()

Defines a private function via Kernel.defp/2

Called by functions that are not being expropriated.

Link to this function

define_public(fn_head, fn_body)

View Source

Specs

define_public(fn_head(), fn_body()) :: Macro.t()

Defines a public function via Kernel.def/2

Called by functions that are being expropriated.

Link to this macro

defp(fn_head, fn_body \\ nil)

View Source (macro)

An override of Kernel.defp/2.

This macro decides whether or not the expropriate the function body based on the following criteria:

  • The @expropriate attribute is set to true
  • The function's name (fn_name/0) was already expropriated. (For functions with multiple bodies)

Specs

fn_head_to_name(fn_head()) :: {:ok, fn_name()} | :error

Transforms a function's head AST into a tuple containing the name and arity of the function.

Specs

generate_unused_ast() :: Macro.t()

Generates AST to prevent warnings for unused @expropriate attributes.

These warnings happened when the module has @expropriate attributes, but ExPropriate is disabled at a config level. Eg: in prod environment.

Specs

generate_use_ast() :: Macro.t()

Generates the AST necessary to expropriate only tagged functions on compile time.