On this page:
4.1 Using Qi from the Host Language
4.1.1 Core
flow
on
4.1.2 Threading
~>
~>>
4.1.3 Conditionals
switch
4.1.4 Lambdas
flow-lambda
flow-λ
π
switch-lambda
switch-λ
λ01
4.1.5 Definitions
define-flow
define-switch
4.2 Using the Host Language from Qi
4.2.1 Using Racket Values in Qi Flows
4.2.2 Using Racket to Define Flows
4.2.3 Using Racket Macros as Flows
define-qi-foreign-syntaxes
4.3 Using Qi with Another DSL
4.3.1 Using Qi Directly
4.3.2 Using a Macro Bridge
4.3.3 Writing a Qi Dialect

4 Language Interface

The most common way to use Qi is via interface macros in a host language such as Racket. Qi may also be used in tandem with other embedded or hosted DSLs.

    4.1 Using Qi from the Host Language

      4.1.1 Core

      4.1.2 Threading

      4.1.3 Conditionals

      4.1.4 Lambdas

      4.1.5 Definitions

    4.2 Using the Host Language from Qi

      4.2.1 Using Racket Values in Qi Flows

      4.2.2 Using Racket to Define Flows

      4.2.3 Using Racket Macros as Flows

    4.3 Using Qi with Another DSL

      4.3.1 Using Qi Directly

      4.3.2 Using a Macro Bridge

      4.3.3 Writing a Qi Dialect

4.1 Using Qi from the Host Language

The core entry-point to Qi from the host language is the form . In addition, other forms such as on, switch, and ~> build on top of to provide convenient syntax in specialized cases. Together, these forms represent the interface between the host language (e.g. Racket) and Qi.

4.1.1 Core

syntax

( flow-expr)

 
flow-expr = 
  | _
  | (gen expr ...)
  | 
  | sep
  | 
  | collect
  | (esc expr)
  | (clos flow-expr)
  | (as identifier ...)
  | (one-of? expr ...)
  | (all flow-expr)
  | (any flow-expr)
  | (none flow-expr)
  | (and flow-expr ...)
  | (or flow-expr ...)
  | (not flow-expr)
  | (and% flow-expr ...)
  | (or% flow-expr ...)
  | NOT
  | !
  | AND
  | &
  | OR
  | 
  | NOR
  | NAND
  | XOR
  | XNOR
  | any?
  | all?
  | none?
  | inverter
  | 
  | ground
  | (~> flow-expr ...)
  | (thread flow-expr ...)
  | (~>> flow-expr ...)
  | (thread-right flow-expr ...)
  | X
  | crossover
  | ==
  | (== flow-expr ...)
  | relay
  | (relay flow-expr ...)
  | (==* flow-expr ...)
  | (relay* flow-expr ...)
  | -<
  | (-< flow-expr ...)
  | tee
  | (tee flow-expr ...)
  | fanout
  | (fanout nat)
  | feedback
  | (feedback nat flow-expr)
  | (feedback nat (then flow-expr) flow-expr)
  | (feedback (while flow-expr) flow-expr)
  | (feedback (while flow-expr) (then flow-expr) flow-expr)
  | count
  | 1>
  | 2>
  | 3>
  | 4>
  | 5>
  | 6>
  | 7>
  | 8>
  | 9>
  | (select index ...)
  | (block index ...)
  | (bundle (index ...) flow-expr flow-expr)
  | (group nat flow-expr flow-expr)
  | sieve
  | (sieve flow-expr flow-expr flow-expr)
  | (partition [flow-expr flow-expr] ...)
  | (if flow-expr flow-expr)
  | (if flow-expr flow-expr flow-expr)
  | (when flow-expr flow-expr)
  | (unless flow-expr flow-expr)
  | switch
  | (switch switch-expr ...)
  | (switch (% flow-expr) switch-expr ...)
  | (switch (divert flow-expr) switch-expr ...)
  | (gate flow-expr)
  | ><
  | (>< flow-expr)
  | amp
  | (amp flow-expr)
  | pass
  | (pass flow-expr)
  | <<
  | (<< flow-expr)
  | (<< flow-expr flow-expr)
  | >>
  | (>> flow-expr)
  | (>> flow-expr flow-expr)
  | (loop flow-expr)
  | (loop flow-expr flow-expr)
  | (loop flow-expr flow-expr flow-expr)
  | (loop flow-expr flow-expr flow-expr flow-expr)
  | (loop2 flow-expr flow-expr flow-expr)
  | (ε flow-expr flow-expr)
  | (effect flow-expr flow-expr)
  | apply
  | (qi:* expr ...)
  | (expr expr ... __ expr ...)
  | (expr expr ... _ expr ...)
  | (expr expr ...)
  | literal
  | identifier
     
literal = boolean
  | char
  | string
  | bytes
  | number
  | regexp
  | byte-regexp
  | vector-literal
  | box-literal
  | prefab-literal
  | (quote value)
  | (quasiquote value)
  | (quote-syntax value)
  | (syntax value)
     
expr = a-racket-expression
     
index = exact-positive-integer?
     
nat = exact-nonnegative-integer?
     
switch-expr = [flow-expr flow-expr]
  | [flow-expr (=> flow-expr)]
  | [else flow-expr]
     
identifier = a-racket-identifier
     
value = a-racket-value

syntax

(flow flow-expr)

Define a flow by using the various forms of the Qi language.

This produces a value that is an ordinary function. When invoked with arguments, this function passes those arguments as inputs to the defined flow, producing its outputs as return values. A flow defined in this manner does not name its inputs, and like any function, it only produces output when it is invoked with inputs.

See also on and ~>, which are shorthands to invoke the flow with arguments immediately.

Examples:
> (( (* 5)) 3)

15

> (( (and positive? odd?)) 5)

#t

> (( (~> + (* 2))) 3 5)

16

syntax

(on (arg ...) flow-expr)

Define and execute a flow with the inputs named in advance.

This is a way to pass inputs to a flow that is an alternative to the usual function invocation syntax (i.e. an alternative to simply invoking the flow with arguments). It may be preferable in certain cases, since the inputs are named at the beginning rather than at the end.

In the respect that it both defines as well as invokes the flow, it has the same relationship to as let has to lambda, and can be used in analogous ways.

Equivalent to (( flow-expr) arg ...).

Examples:
> (on (5) (and positive? odd?))

#t

> (on ((* 2 3) (+ 5 2))
    (~> (>< ->string)
        string-append))

"67"

4.1.2 Threading

syntax

(~> (args ...) flow-expr ...)

syntax

(~>> (args ...) flow-expr ...)

These Racket forms leverage the identically-named Qi forms to thread inputs through a sequence of flows. ~> threads arguments in the first position by default, while ~>> uses the last position, but in either case the positions can instead be explicitly indicated by using _ or __.

In these docs, we’ll sometimes refer to the host language as "Racket" for convenience, but it should be understood that Qi may be used with any host language.

Note that, as there may be any number of input arguments, they must be wrapped in parentheses in order to distinguish them from the flow specification – unlike the usual threading macro where the input is simply the first argument.

As flows themselves can be nonlinear, these threading forms too support arbitrary arity changes along the way to generating the result.

In the respect that these both define as well as invoke the flow, they have the same relationship to as let has to lambda, and can be used in analogous ways.

Equivalent to (( (~> flow-expr ...)) args ...).

See also: Relationship to the Threading Macro.

Examples:
> (~> (3) sqr add1)

10

> (~> (3) (-< sqr add1) +)

13

> (~> ("a" "b") (string-append "c"))

"abc"

> (~>> ("b" "c") (string-append "a"))

"abc"

> (~> ("a" "b") (string-append _ "-" _))

"a-b"

4.1.3 Conditionals

syntax

(switch (arg ...)
  maybe-divert-clause
  [predicate consequent]
  ...
  [else consequent])
A predicate-based dispatch form, usable as an alternative to cond, if, and match.

Each of the predicate and consequent expressions is a flow, and they are each typically invoked with arg ..., so that the arguments need not be mentioned anywhere in the body of the form.

This Racket form leverages the identically-named Qi form. See Conditionals for its full syntax and behavior.

Examples:
> (switch (5)
    [(and positive? odd?) (~> sqr add1)]
    [else _])

26

> (switch (2 3)
    [< +]
    [else min])

5

4.1.4 Lambdas

These anonymous function forms may be used in cases where you need to explicitly name the arguments for some reason. Otherwise, in most cases, just use directly instead as it produces a function while avoiding the extraneous layer of bindings.

syntax

(flow-lambda args flow-expr)

syntax

(flow-λ args flow-expr)

syntax

(π args flow-expr)

Similiar to lambda but constrained to the flow language. This is exactly equivalent to (lambda args (on (args) flow-expr)) except that the keywords only introduce bindings, and aren’t part of the values that are fed into flow-expr. flow-λ and π are aliases for flow-lambda. The present form mainly finds its use internally in define-flow, and in most cases you should use directly.

Examples:
> ((flow-lambda a* _) 1 2 3 4)

'(1 2 3 4)

> ((flow-lambda (a b c d) list) 1 2 3 4)

'(1 2 3 4)

> ((flow-lambda (a . a*) list) 1 2 3 4)

'(1 (2 3 4))

> ((flow-lambda (a #:b b . a*) list) 1 2 3 4 #:b 'any)

'(1 (2 3 4))

> ((flow-lambda (a #:b b c . a*) list) 1 2 3 4 #:b 'any)

'(1 2 (3 4))

> ((flow-lambda (a b #:c c) (~> + (* c))) 2 3 #:c 10)

50

syntax

(switch-lambda args
  maybe-divert-clause
  [predicate consequent ...]
  ...
  [else consequent ...])

syntax

(switch-λ args
  maybe-divert-clause
  [predicate consequent ...]
  ...
  [else consequent ...])

syntax

(λ01 args
  maybe-divert-clause
  [predicate consequent ...]
  ...
  [else consequent ...])
Similar to lambda but constrained to be a flow-based dispatcher. This is exactly equivalent to (lambda args (switch (args) maybe-divert-clause [predicate consequent ...] ... [else consequent ...])) except that the keywords only introduce bindings, and aren’t part of the values that are fed into flow-expr. switch-λ and λ01 are aliases for switch-lambda.

Examples:
> ((switch-lambda (a #:b b . a*)
     [memq 'yes]
     [else 'no]) 2 2 3 4 #:b 'any)

'yes

> ((switch-lambda (a #:fx fx . a*)
     [memq (~> 1> fx)]
     [else 'no]) 2 2 3 4 #:fx number->string)

"2"

> ((switch-lambda (x)
     [(and positive? odd?) (~> sqr add1)]
     [else _]) 5)

26

4.1.5 Definitions

The following definition forms may be used in place of the usual general-purpose define form when defining flows.

syntax

(define-flow name flow-expr)

syntax

(define-flow (head args) flow-expr)

Similiar to the function form of define but constrained to the flow language. This is exactly equivalent to (define head (flow-lambda args flow-expr)).

syntax

(define-switch name
  maybe-divert-clause
  [predicate consequent ...]
  ...
  [else consequent ...])

syntax

(define-switch (head args)
  maybe-divert-clause
  [predicate consequent ...]
  ...
  [else consequent ...])
Similiar to the function form of define but constrained to be a (predicate-based) dispatcher. This is exactly equivalent to (define head (switch-lambda args maybe-divert-clause [predicate consequent ...] ... [else consequent ...])).

Examples:
> (define-switch abs
    [negative? -]
    [else _])
> (map abs (list -1 2 -3 4 -5))

'(1 2 3 4 5)

The advantage of using these over the general-purpose define form is that, as they express the definition at the appropriate level of abstraction and with the attendant constraints for the type of flow, they can be more clear and more robust, minimizing boilerplate while providing guardrails against programmer error.

4.2 Using the Host Language from Qi

Arbitrary native (e.g. Racket) expressions can be used in flows in one of two core ways. This section describes these two ways and also discusses other considerations regarding use of the host language alongside Qi.

4.2.1 Using Racket Values in Qi Flows

The first and most common way is to simply wrap the expression with a gen form while within a flow context. This flow generates the value of the expression.

Examples:
> (define v 2)
> (( (~> (gen (* 5 v) (* 3 v)) list)))

'(10 6)

4.2.2 Using Racket to Define Flows

The second way is if you want to describe a flow using the host language instead of Qi. In this case, use the esc form. The wrapped expression in this case must evaluate to a function, since functions are the only values describable in the host language that can be treated as flows. Note that use of esc is unnecessary for function identifiers since these are usable as flows directly, and these can even be partially applied using standard application syntax, optionally with _ and __ to indicate argument placement. But you may still need esc in the specific case where the identifier collides with a Qi form.

Examples:
> (define-flow add-two
    (esc (λ (a b) (+ a b))))
> (~> (3 5) add-two)

8

4.2.3 Using Racket Macros as Flows

Flows are expected to be functions, and so you cannot naively use a macro as a flow. But there are many ways in which you can. If you’d just like to use such a macro in a one-off manner, see Converting a Macro to a Flow for an ad hoc way to do this. But a simpler and more complete way in many cases is to first register the macro (or any number of such macros) using define-qi-foreign-syntaxes prior to use.

syntax

(define-qi-foreign-syntaxes form ...)

This form allows you to register "foreign macros" for use with Qi. These could be Racket macros, or the forms of another DSL. By simply registering such forms by name using this form, you can for the most part use them just as if they were functions, except that the catch-all template __ isn’t supported for such macros.

Examples:
> (define-syntax-rule (double-me x) (* 2 x))
> (define-syntax-rule (subtract-two x y) (- x y))
> (define-qi-foreign-syntaxes double-me subtract-two)
> (~> (5) (subtract-two 4) double-me)

2

> (~>> (5) (subtract-two 4) double-me)

-2

> (~> (5 4) (subtract-two _ _) double-me)

2

By doing this, you can thread multiple values through such syntaxes in the same manner as functions. The catch-all template __ isn’t supported though, since macros (unlike functions) require all the "arguments" to be supplied syntactically at compile time. So while any number of arguments may be supplied to such macros, it must be a specific rather than an arbitrary number of them, which may be indicated syntactically via _ to indicate individual expected arguments and their positions.

Note that for a foreign macro used in identifier form (such as double-me in the example above), it assumes a single argument. This is different from function identifiers where they receive as many values as may happen to be flowing at runtime. With macros, as we saw, we cannot provide them an arbitrary number of arguments. If more than one argument is anticipated, explicitly indicate them using a _ template.

Finally, as macros "registered" in this way result in the implicit creation of Qi macros corresponding to each foreign macro, if you’d like to use these forms from another module, you’ll need to provide them just like any other Qi macro, i.e. via (provide (for-space qi ...)).

4.3 Using Qi with Another DSL

Qi may also be used in tandem with other DSLs in a few different ways – either directly, if the DSL is implemented simply as functions without custom syntax, or via a one-to-one macro "bridge" between the two languages (if the interface between the languages is small), or potentially by implementing the DSL itself as a Qi dialect (if the languages interact extensively).

4.3.1 Using Qi Directly

If the forms of the DSL are callable, i.e. if they are functions, then you can just use Qi with them the same way as with any other function.

4.3.2 Using a Macro Bridge

See Converting a Macro to a Flow.

Using the macro bridge approach, you would need to write a corresponding Qi macro for every form of your DSL that interacts with Qi (or use define-qi-foreign-syntaxes to do this for you). If this interface between the two languages is large enough, and their use together frequent enough, this approach too may prove cumbersome. In such cases, it may be best to implement the DSL itself as a dialect of Qi.

4.3.3 Writing a Qi Dialect

The problem with the macro bridge approach is that all paths between the two languages must go through a level of indirection in the host language. That is, the only way for Qi and the other DSL to interact is via Racket as an everpresent intermediary.

To get around this, a final possibility to consider is to translate the DSL itself so that it’s implemented in Qi rather than Racket. That is, instead of being specified using Racket macros via e.g. define-syntax-parse-rule and define-syntax-parser, it would rather be defined using define-qi-syntax-rule and define-qi-syntax-parser so that the language expands to Qi rather than Racket (directly). This would allow your language to be used with Qi seamlessly since it would now be a dialect of Qi.

There are many kinds of languages that you could write in Qi. See Writing Languages in Qi for a view into the possibilities here, and what may be right for your language.