In computer science, arrows or bolts are a type class used in programming to describe computations in a pure and declarative fashion. First proposed by computer scientist John Hughes as a generalization of monads, arrows provide a referentially transparent way of expressing relationships between logical steps in a computation. Unlike monads, arrows don't limit steps to having one and only one input. As a result, they have found use in functional reactive programming, point-free programming, and parsers among other applications.
Motivation and history
While arrows were in use before being recognized as a distinct class, it wasn't until 2000 that John Hughes first published research focusing on them. Until then, monads had proven sufficient for most problems requiring the combination of program logic in pure code. However, some useful libraries, such as the Fudgets library for graphical user interfaces and certain efficient parsers, defied rewriting in a monadic form.
The formal concept of arrows was developed to explain these exceptions to monadic code, and in the process, monads themselves turned out to be a subset of arrows. Since then, arrows have been an active area of research. Their underlying laws and operations have been refined several times, with recent formulations such as arrow calculus requiring only five laws.
Relation to category theory
In category theory, the Kleisli categories of all monads form a proper subset of Hughes arrows. While Freyd categories were believed to be equivalent to arrows for a time, it has since been proven that arrows are even more general. In fact, arrows are not merely equivalent, but directly equal to enriched Freyd categories.
Definition
Like all type classes, arrows can be thought of as a set of qualities that can be applied to any data type. In the Haskell programming language, arrows allow functions (represented in Haskell by -> symbol) to combine in a reified form. However, the actual term "arrow" may also come from the fact that some (but not all) arrows correspond to the morphisms (also known as "arrows" in category theory) of different Kleisli categories. As a relatively new concept, there is not a single, standard definition, but all formulations are logically equivalent, feature some required methods, and strictly obey certain mathematical laws.
Functions
The description currently used by the Haskell standard libraries requires only three basic operations: A type constructor that takes functions from any type to another , and lifts those functions into an arrow between the two types. arr (s -> t) -> A s t A piping method that takes an arrow between two types and converts it into an arrow between tuples. The first elements in the tuples represent the portion of the input and output that is altered, while the second elements are a third type describing an unaltered portion that bypasses the computation. first (A s t) -> A (s,u) (t,u)
As all arrows must be categories, they inherit a third operation from the class of categories: A composition operator that can attach a second arrow to a first as long as the first function's output and the second's input have matching types. A s t >>> A t u -> A s u
Although only these three procedures are strictly necessary to define an arrow, other methods can be derived to make arrows easier to work with in practice and theory.
One more helpful method can be derived from and (and from which can be derived): A merging operator that takes two arrows, possibly with different input and output types, and fuses them into one arrow between two compound types. The merge operator is not necessarily commutative. A s t *** A u v -> A (s,u) (t,v)
Arrow laws
In addition to having some well-defined procedures, arrows must obey certain rules for any types they may be applied to: Arrows must always preserve all types' identities (essentially the definitions of all values for all types within a category). arr id == id When connecting two functions & , the required arrow operations must distribute over compositions from the left. arr (f >>> g) == arr f >>> arr g first (f >>> g) == first f >>> first g In the previous laws, piping can be applied directly to functions because order must be irrelevant when piping & lifting occur together. arr (first f) == first (arr f)