No matching definitions.

tur/arrow

stdlib/arrow.tur

arrow combinators over the (->) function arrow, in two surfaces.

Bare functions (the simple path): arr, >>>, arrow-first, arrow-second, par-comp, arrow-split, arrow-const, and arrow-dup as plain functions -- use these when you do not need polymorphism over the arrow constructor.

Typeclass dispatch (parametric over any Arrow): the Haskell-style Arrow / ArrowZero / ArrowPlus / ArrowChoice / ArrowLoop / ArrowApply hierarchy with its (->) instances, declared lower in this file. Use these when you want code resolved through the instance dictionary. The two surfaces share the method names arr / >>>: a bare call dispatches to the matching instance when the receiver's type selects one, and falls back to the bare combinator otherwise (see docs/archive/history/typeclass-methods-share-value-namespace-with-defns.md).

Feedback (ArrowLoop): arrow-loop-lazy, arrow-loop-fix, and arrow-loop-delay close the d output of an arrow back onto its d input, each at a different time -- see the "ArrowLoop feedback cells" section below, and docs/guides/arrows-guide.md ("ArrowLoop -- feedback").

The typeclass layer was scaled back in docs/archive/history/stdlib-arrow-scaleback-plan.md and reintroduced -- then merged back into this single module -- per docs/archive/history/stdlib-arrow-typeclass-reintroduction-plan.md.

Since: Phase CA0

defn

arr

(arr [^fat f])

lift a function to an arrow (identity for function arrows).

fthe function to lift

The function itself.

(arr (fn [x] (+ x 1)))  ; => identity for functions
defn

>>>

(>>> [A B C])

compose two function arrows sequentially (register-class-correct).

Polymorphic over the element type: the type parameters A, B, C are bound at each call site from the declared types of f and g, so a float pipeline emits a double-returning inner body dispatched through tur_thunk_double_double_t (register-class-correct), and an int pipeline uses the standard int64 thunk ABI.

ffirst arrow (A -> B)
gsecond arrow (B -> C)

A closure (fn [A] C) that applies f then g. Typed as a function, not ptr<void>, so the result can be called directly at its element types.

((>>> (fn [x : float] : float (+ x 1.0))
      (fn [x : float] : float (* x 2.0))) 3.0)  ; => 8.0

Since: Phase CA1

defn

arrow-first

(arrow-first [^fat f])

apply a function to the first component of a Tuple2.

ffunction to apply

A function on Tuple2 that applies f to the first field.

((arrow-first (fn [x] (+ x 1))) <heap Tuple2 (5, 10)>)  ; => Tuple2(6, 10)
defn

arrow-second

(arrow-second [^fat f])

apply a function to the second component of a Tuple2.

ffunction to apply

A function on Tuple2 that applies f to the second field.

((arrow-second (fn [x] (+ x 1))) <heap Tuple2 (5, 10)>)  ; => Tuple2(5, 11)
defn

par-comp

(par-comp [a b c d ^fat f ^fat g])

parallel composition of two functions on Tuple2 types.

ffunction a -> b
gfunction c -> d

A function on Tuple2 a c -> Tuple2 b d: (par-comp f g)(tuple2 x y) = tuple2(f x, g y).

((par-comp inc double) (tuple2 1 2))  ; => Tuple2(2, 4)
defn

arrow-split

(arrow-split [a b c ^fat f ^fat g])

duplicate input and apply two functions, returning a Tuple2.

ffunction a -> b
gfunction a -> c

A function a -> Tuple2 b c: (arrow-split f g)(x) = tuple2(f x, g x).

((arrow-split inc double) 3)  ; => Tuple2(4, 6)
defn

arrow-const

(arrow-const [v])

arrow that ignores its input and always returns v.

vthe constant value to return

An arrow (any -> v).

((arrow-const 42) 999)  ; => 42

Since: Phase B2

defn

arrow-dup

(arrow-dup [x] :int)

arrow that duplicates its input into a Tuple2.

xthe value to duplicate

A Tuple2 of x with itself: Tuple2(x, x).

(tuple2-1st (arrow-dup 7))  ; => 7

Since: Phase B2

defopaque

LoopCell

(defopaque LoopCell :int)

the fed-back d slot of an ArrowLoop, as a handle.

Carrier is :int (the erased two-slot pair carrier the arrow layer runs on), but the newtype keeps an arbitrary integer out of loop-cell-force: only a cell minted by one of the arrow-loop combinators is accepted.

Since: arrowloop-lazy-feedback

defn

loop-cell-of

(loop-cell-of [slot :int] :LoopCell)

read slot 1 of an ArrowLoop input pair as a LoopCell.

The arrow layer erases every slot to the int64 carrier, so a looped arrow written in Turmeric receives the feedback cell as a plain int. This is the one sanctioned way back to the handle type.

slotthe erased slot-1 value handed to the looped arrow

The same handle, typed as a LoopCell.

(loop-cell-force (loop-cell-of d))  ; => the fed-back value

Since: arrowloop-lazy-feedback

defn

loop-cell-ready?

(loop-cell-ready? [c :LoopCell] :bool)

has the fed-back value been written yet?

False exactly inside an arrow-loop run, before the looped arrow has returned its d output. arrow-loop-fix and arrow-loop-delay seed the cell up front, so it reads true there from the first call.

cthe feedback cell

true when loop-cell-force would succeed, false when it would black-hole.

(if (loop-cell-ready? c) (loop-cell-force c) 0)

Since: arrowloop-lazy-feedback

defn

loop-cell-force

(loop-cell-force [c :LoopCell] :int)

read the fed-back value out of a feedback cell.

Panics with <<loop>> when the value is not there yet -- that is the black hole, and it is a genuine error in the looped arrow (it demanded its own output). Guard with loop-cell-ready? when a miss is expected.

cthe feedback cell

The fed-back d value, in the erased arrow carrier.

(let [lp (arrow-loop step)]
  (loop-cell-force (loop-cell-of (lp 7))))

Since: arrowloop-lazy-feedback

defclass

Category

(defclass Category [arr])

the base of the arrow hierarchy: an identity arrow and sequential composition.

(ident [] :arr)

the identity arrow (nullary; the result is the arrow itself). Resolved by return-type / unique-instance dispatch (there is no argument to dispatch on); see docs/archive/history/return-type-dispatch-nullary-arrow-methods-plan.md.

(comp [f g] :arr)

left-to-right composition: run f, then g (comp f g == g . f over (->)). Distinct method name from Arrow's >>> so the two surfaces coexist without an operator-mangling collision.

A second instance (e.g. Kleisli, stdlib/kleisli.tur) makes a bare (ident) with no expected type ambiguous -- the Mechanism-B uniqueness gate then fires the existing ambiguity error, resolvable by ascription ((:: (ident) :Kleisli)).

(->), Kleisli

Since: Phase CA2

defclass

Arrow

(defclass Arrow [a] [(Category a)])

lift functions into arrows and wire them together.

(arr [f] :a)

lift a plain function (b -> c) into an arrow a b c.

(>>> [f :(a b c) g :(a c d)] :(a b d))

left-to-right composition: run the left arrow, then the right.

(<<< [f :(a c d) g :(a b c)] :(a b d))

right-to-left composition (flipped >>>).

(first [f] :a)

act on the first component of a pair, passing the second through.

(second [f] :a)

act on the second component of a pair, passing the first through.

>>> and <<< spell their arrows -- (a b c) is the arrow from b to c, (fn [b] c) at the (->) head -- so a call with concrete functions specializes the instance at their element types, and the composed closure is called at exactly those types.

Superclass: Category, so an [^Arrow A] body may also use ident/comp.

(->)

Since: Phase CA1

defclass

ArrowZero

(defclass ArrowZero [a] [(Category a)])

an arrow with a distinguished empty/zero arrow.

(zero-arrow [] :a)

the arrow with no behaviour (nullary; result is the arrow).

The function arrow (->) is deliberately NOT made an instance: a total function b -> c with no input requires choosing an inhabitant of c, which (->) cannot do generically. The class is declared so other arrow constructors (e.g. a future Kleisli) can instantiate it.

Superclass: Category -- not Arrow, as Haskell has it. stdlib/kleisli.tur's Kleisli is a Category with an honest zero arrow and deliberately no Arrow instance, and the zero-arrow laws (zero >>> f = zero) need only composition.

Kleisli

Since: Phase CA1

defclass

ArrowPlus

(defclass ArrowPlus [a] [(ArrowZero a)])

an ArrowZero whose arrows form a monoid under choice.

(plus-arrow [f g] :a)

combine two arrows (the monoid append).

Like ArrowZero, (->) is not instantiated here; the class exists for arrow constructors that carry a sensible alternative/merge.

Superclass: ArrowZero.

Since: Phase CA1

defclass

ArrowChoice

(defclass ArrowChoice [a] [(Arrow a)])

route an arrow over one arm of a sum (Either).

(left [f] :a)

apply the arrow to a Left payload, pass Right through unchanged.

(right [f] :a)

apply the arrow to a Right payload, pass Left through unchanged.

(+++ [f g] :a)

run the left arrow on Left, the right arrow on Right (fan over sum).

(||| [f g] :a)

run either arrow and collapse both arms to a common result.

Requires the Either sum type (stdlib/either.tur).

Superclass: Arrow.

(->)

Since: Phase CA1

defclass

ArrowLoop

(defclass ArrowLoop [a] [(Arrow a)])

feed part of an arrow's output back as input.

(arrow-loop [f] :a)

given an arrow a (b, d) (c, d), produce an arrow a b c by feeding the d output back as the d input.

Turmeric is strict, so the fed-back d is handed to the looped arrow as a LoopCell rather than as a bare value: the (->) instance runs the arrow on (b, empty-cell) and fills the cell with the d output afterwards. An arrow that ignores d behaves exactly as before; an arrow that parks the cell in its c output and forces it later gets true lazy feedback; an arrow that forces the cell mid-run hits the black hole (a <<loop>> panic) instead of silently reading a sentinel.

For a looped arrow that must read d *immediately*, use the bare arrow-loop-fix (iterate to a fixpoint) or arrow-loop-delay (a unit delay in the feedback path) below.

Superclass: Arrow.

(->)

Since: Phase CA1 (lazy feedback: arrowloop-lazy-feedback)

defclass

ArrowApply

(defclass ArrowApply [a] [(Arrow a)])

apply an arrow that is itself a value flowing through.

(app [] :a)

the arrow a (a b c, b) c that applies its first component (an arrow) to its second (nullary; result is the arrow).

Superclass: Arrow.

(->)

Since: Phase CA1

definstance

Arrow[(->)]

(definstance Arrow [(->)])

the function arrow: arr is the identity (a plain function is already an arrow), >>> / <<< are composition, first / second act on a heap pair.

definstance

ArrowChoice[(->)]

(definstance ArrowChoice [(->)])

map an arrow over one arm of an Either, passing the other arm through; +++ fans two arrows over the two arms, ||| collapses them.

definstance

ArrowLoop[(->)]

(definstance ArrowLoop [(->)])

lazy (knot-tying) feedback: run the arrow on (b, empty-cell), fill the cell with the d output, project the c output.

defn

arrow-loop-lazy

(arrow-loop-lazy [^fat f] :ptr<void>)

the bare-surface twin of the ArrowLoop [(->)] instance.

Feeds the d output back through a LoopCell that is filled only once the looped arrow returns. Reads of the cell that the arrow defers -- by parking the handle in its own c output -- observe the d that this same run produced; a read made during the run is the <<loop>> black hole.

fthe looped arrow, a (b, LoopCell) -> (c, d) over the heap-pair layout

A ptr<void> closure b -> c.

;; f parks the cell in c, so forcing c after the loop yields d = b * 10.
(let [lp (arrow-loop-lazy park-cell-step)]
  (loop-cell-force (loop-cell-of (lp 7))))  ; => 70

Since: arrowloop-lazy-feedback

defn

arrow-loop-fix

(arrow-loop-fix [^fat f d0 :int fuel :int] :ptr<void>)

feedback by iteration to a fixpoint.

For a looped arrow that reads d *strictly*: the cell is seeded with d0 and the arrow is re-run, each time seeing the previous run's d, until d stops changing or fuel iterations have elapsed. The result is the c output of the last iteration. Non-convergence is not an error here -- it simply spends the fuel and returns the last c -- so a diverging arrow costs a bounded amount of work rather than hanging.

fthe looped arrow, a (b, LoopCell) -> (c, d)
d0the seed for the fed-back component
fuelmaximum iterations; anything below 1 runs exactly one pass

A ptr<void> closure b -> c.

;; newton-step: d' = (d + b/d)/2, c = d  -- integer sqrt by fixpoint.
((arrow-loop-fix newton-step 1 32) 144)  ; => 12

Since: arrowloop-lazy-feedback

defn

arrow-loop-delay

(arrow-loop-delay [^fat f d0 :int] :ptr<void>)

feedback through a unit delay (step-indexed).

The fed-back component persists across calls: call n sees the d that call n-1 produced, and the first call sees d0. This is the always-total member of the family -- there is no black hole and no iteration -- and it is the feedback shape signal-processing and state-machine arrows want, where the loop is closed over time rather than within a single step.

The returned arrow is stateful: it owns one cell, so each call advances the same feedback line. Call arrow-loop-delay again for an independent line.

fthe looped arrow, a (b, LoopCell) -> (c, d)
d0the value the first call sees fed back

A ptr<void> closure b -> c, carrying its own feedback state.

;; sum-step: d' = d + b, c = d + b  -- a running total.
(let [acc (arrow-loop-delay sum-step 0)]
  (acc 1) (acc 2) (acc 3))  ; => 1, 3, 6

Since: arrowloop-lazy-feedback

definstance

ArrowApply[(->)]

(definstance ArrowApply [(->)])

the apply arrow: run the arrow in slot 0 on slot 1.

definstance

Category[(->)]

(definstance Category [(->)])

the function arrow: ident is the plain identity function, comp is forward composition. The eta-expanded (fn [x] x) (rather than a bare identity reference) is required so the dispatched nullary call keeps a concrete result type -- see docs/archive/history/instance-method-returning-untyped-param-loses-result-type.md.

Internal definitions
__arrow_pair_first-- inline-C: apply fv to e1 of a heap pair *p,
__arrow_pair_second-- inline-C: apply fv to e2 of a heap pair *p,
__arrow_pair_par-- inline-C: apply fv to e1 and gv to e2 of *p.
__arrow_pair_split-- inline-C: produce a heap pair from (fv x, gv x).
__arrow_pair_dup-- inline-C: produce a heap pair with both fields = x.
__ac_pair_first-- inline-C: apply fv to slot 0 of a heap pair, copy slot 1.
__ac_pair_second-- inline-C: copy slot 0 of a heap pair, apply fv to slot 1.
__ac_cell_new-- inline-C: allocate a feedback cell, filled or empty.
__ac_loop_step-- inline-C: the lazy (knot-tying) ArrowLoop step. Allocate
__ac_loop_fix_step-- inline-C: the strict fixpoint ArrowLoop step. Seed a
__ac_loop_delay_step-- inline-C: one step of the unit-delay ArrowLoop. The
__ac_app-- inline-C: ArrowApply for (->). The input pair carries an arrow