Skip to content

Verbs

Verbs

Registering a verb

You can use register_verb to register a verb. Here are the arguments of register_verb:

  • cls: The default type to register for _default backend if TypeHolder, it is a generic function, and not counted as a real implementation.
  • func: The function works as a verb. If None (not provided), this function will return a decorator.
  • context: The context to evaluate the arguments
  • kw_context: The context to evaluate the keyword arguments
  • name: and
  • qualname: and
  • doc: and
  • module: The meta information about the function to overwrite func's or when it's not available from func
  • dependent: Whether the verb is dependent.

    >>> @register_verb(context=Context.EVAL, dependent=True)
    >>> def length(data):
    >>>     return len(data)
    >>> # with dependent=True
    >>> # length()  -> VerbCall, waiting for data to evaluate
    >>> # with dependent=False
    >>> # length()  -> TypeError, argument data is missing
    
  • ast_fallback: What's the supposed way to call the verb when AST node detection fails.

    • piping - Suppose this verb is called like data >> verb(...)
    • normal - Suppose this verb is called like verb(data, ...)
    • piping_warning - Suppose piping call, but show a warning
    • normal_warning - Suppose normal call, but show a warning
    • raise - Raise an error

Verbs are pipeable

from pipda import register_verb

@register_verb(int)
def increment(data):
    return data + 1

1 >> increment()  # 2

# You can also call it normally
increment(1)  # 2

More about pipeable, see Piping.

Verbs are dispatchable

Like singledispatch functions, verbs are dispatched by the type of their first argument.

from pipda import register_verb


@register_verb(int)
def increment(data):
    return data + 1


@increment.register(str)
def _increment(data):
    return data + '1'


1 >> increment()  # 2
'1' >> increment()  # '11'

What if a verb is not registered for a type? It will be dispatched to default generic function, which raises a NotImplementedError.

[] >> increment()  # NotImplementedError

If you want to change the default behavior, you can register a generic function yourself using a type holder.

from pipda import register_verb
from pipda.utils import TypeHolder

@register_verb(TypeHolder)
def increment(data):
    return data + 1

1.1 >> increment()  # 2.1

Verbs evaluate other arguments using the first one as data

from pipda import register_verb, Context, Symbolic

f = Symbolic()

@register_verb(list, context=Context.EVAL)
def add(data, other):
    """Add other to each element of data"""
    return [d + other for d in data]


[1, 2, 3] >> add(1)  # [2, 3, 4]
# Using the first element of data
[1, 2, 3] >> add(f[0])  # [2, 3, 4]

More about contexts, see Contexts.

Verbs pass down the context if not specified in the arguments

from pipda import register_func, register_verb, Context, Symbolic

f = Symbolic()

@register_func()  # no context specified
def double(data):
    return data * 2


@register_verb(list, context=Context.EVAL)
def add(data, other):
    return [d + other for d in data]


[1, 2, 3] >> add(double(f[0]))  # [3, 4, 5]

Dependent verbs

Dependent verbs are functions that can be used as arguments to another verb without passing the data argument. They can also work as normal verbs.

from pipda import register_verb, Context, Symbolic

f = Symbolic()

@register_verb(list, dependent=True)
def times(data, n):
    """Times each element of data with n"""
    return [d * n for d in data]

@register_verb(list, context=Context.EVAL)
def add(data, other):
    """Add other to each element of data"""
    return [d + other for d in data]

# times 2 to each element and add the first element to all elements
# Note that we don't pass the first argument to times
[1, 2, 3] >> add(times(2)[0])  # [3, 4, 5]

# When called directly:
times(2)  # VerbCall object
times([1, 2, 3], 2)  # VerbCall object

# But when data is piped in, it is evaluated
[1, 2, 3] >> times(2)  # [2, 4, 6]