PropertyDamage.Sequence (PropertyDamage v0.1.0)

View Source

Represents a command sequence that may contain parallel branches.

A sequence consists of three parts:

  • prefix: Commands executed sequentially before any branching
  • branches: Optional list of parallel branches (each branch is a list of commands)
  • suffix: Commands executed sequentially after branches merge

Linear Sequences

A linear (non-parallel) sequence has branches: nil and all commands in prefix:

%Sequence{prefix: [cmd1, cmd2, cmd3], branches: nil, suffix: []}

Use Sequence.linear/1 as a convenience constructor:

Sequence.linear([cmd1, cmd2, cmd3])

Branching Sequences

A branching sequence has commands before the branch point in prefix, parallel branches in branches, and commands after merge in suffix:

%Sequence{
  prefix: [cmd1, cmd2],
  branches: [[cmd3a, cmd4a], [cmd3b, cmd4b]],
  suffix: [cmd5, cmd6]
}

Use Sequence.branching/3 as a convenience constructor:

Sequence.branching(
  [cmd1, cmd2],                       # prefix
  [[cmd3a, cmd4a], [cmd3b, cmd4b]],   # branches
  [cmd5, cmd6]                        # suffix
)

Execution Semantics

  1. Execute prefix commands sequentially
  2. If branches is not nil: a. Fork projection state b. Execute each branch in parallel (or interleaved) c. Merge results and check for linearizability
  3. Execute suffix commands sequentially

Ref Constraints

  • Refs created in prefix can be used in any branch
  • Refs created in one branch CANNOT be used in another branch
  • Refs created in branches CAN be used in suffix (after merge)

Summary

Functions

Add a new branch to the sequence.

Append a command to the end of the sequence.

Get the number of branches (0 for linear sequences).

Create a branching sequence with parallel execution.

Check if a sequence has parallel branches.

Get the total number of commands in the sequence.

Filter commands in the sequence, preserving structure.

Create a linear (non-branching) sequence.

Check if a sequence is linear (no parallel branches).

Generate all possible linearizations of a branching sequence.

Map a function over all commands in the sequence, preserving structure.

Prepend a command to the beginning of the sequence.

Convert a sequence to a flat list of commands.

Types

command()

@type command() :: struct()

t()

@type t() :: %PropertyDamage.Sequence{
  branches: [[command()]] | nil,
  prefix: [command()],
  suffix: [command()]
}

Functions

add_branch(seq, branch_commands)

@spec add_branch(t(), [command()]) :: t()

Add a new branch to the sequence.

If the sequence is linear, converts it to branching with the existing commands as prefix and the new branch.

append(seq, command)

@spec append(t(), command()) :: t()

Append a command to the end of the sequence.

For linear sequences, appends to prefix. For branching sequences, appends to suffix.

branch_count(sequence)

@spec branch_count(t()) :: non_neg_integer()

Get the number of branches (0 for linear sequences).

branching(prefix, branches, suffix \\ [])

@spec branching([command()], [[command()]], [command()]) :: t()

Create a branching sequence with parallel execution.

Parameters

  • prefix - Commands to execute before branching
  • branches - List of branches, each branch is a list of commands
  • suffix - Commands to execute after branches merge (optional)

Examples

iex> seq = PropertyDamage.Sequence.branching(
...>   [setup_cmd],
...>   [[branch_a_cmd1, branch_a_cmd2], [branch_b_cmd1]],
...>   [cleanup_cmd]
...> )
iex> length(seq.branches)
2

branching?(seq)

@spec branching?(t()) :: boolean()

Check if a sequence has parallel branches.

command_count(sequence)

@spec command_count(t()) :: non_neg_integer()

Get the total number of commands in the sequence.

Counts commands in prefix + all branches + suffix.

Examples

iex> seq = PropertyDamage.Sequence.branching([cmd1], [[cmd2, cmd3], [cmd4]], [cmd5])
iex> PropertyDamage.Sequence.command_count(seq)
5

filter(sequence, pred)

@spec filter(t(), (command() -> boolean())) :: t()

Filter commands in the sequence, preserving structure.

Empty branches are removed. If all branches become empty, converts to linear.

linear(commands)

@spec linear([command()]) :: t()

Create a linear (non-branching) sequence.

All commands are placed in the prefix with no branches.

Examples

iex> seq = PropertyDamage.Sequence.linear([cmd1, cmd2, cmd3])
iex> seq.prefix
[cmd1, cmd2, cmd3]
iex> seq.branches
nil

linear?(sequence)

@spec linear?(t()) :: boolean()

Check if a sequence is linear (no parallel branches).

Examples

iex> PropertyDamage.Sequence.linear?(%Sequence{branches: nil})
true

iex> PropertyDamage.Sequence.linear?(%Sequence{branches: [[cmd1], [cmd2]]})
false

linearizations(seq)

@spec linearizations(t()) :: [t()]

Generate all possible linearizations of a branching sequence.

For a sequence with N branches, generates all valid interleaving orderings of the branch commands, preserving the order within each branch.

Returns a list of linear sequences, each representing one possible sequential execution order.

For linear sequences, returns a list containing just that sequence.

Complexity

For K branches with N total commands, worst case is O(N! / (n1! n2! ... * nk!)) where ni is the length of branch i.

map(sequence, fun)

@spec map(t(), (command() -> command())) :: t()

Map a function over all commands in the sequence, preserving structure.

Examples

iex> seq = PropertyDamage.Sequence.linear([1, 2, 3])
iex> PropertyDamage.Sequence.map(seq, &(&1 * 2))
%PropertyDamage.Sequence{prefix: [2, 4, 6], branches: nil, suffix: []}

prepend(seq, command)

@spec prepend(t(), command()) :: t()

Prepend a command to the beginning of the sequence.

to_list(sequence)

@spec to_list(t()) :: [command()]

Convert a sequence to a flat list of commands.

For linear sequences, returns the prefix. For branching sequences, returns prefix ++ (flattened branches) ++ suffix.

Note: This loses the parallel structure - use only for display/debugging.

Examples

iex> seq = PropertyDamage.Sequence.branching([cmd1], [[cmd2], [cmd3]], [cmd4])
iex> PropertyDamage.Sequence.to_list(seq)
[cmd1, cmd2, cmd3, cmd4]