13 For Racket Programmers
This section motivates some of the choices in Raqit.
13.1 def and fun
Racket has a single define form for defining both values and functions. Raqit has separate def and fun forms. This is so that the former can also handle binding multiple values unambiguously, which in Racket requires the separate define-values form. It also allows fun to unambiguously handle specifying both single as well as multiple arities for the function, which in Racket requires reliance on a separate case-lambda.
The names are chosen to be familiar from other contexts: Clojure and Python use the name def, while Rhombus uses the name fun for the function-defining form.
In general, Raqit borrows the idea from many popular languages that defining forms need not begin with the word "define." Thus, fun defines a function, flow defines a Qi flow, and class defines a class. In Racket, all of these require a prefacing form of define, although this policy of Raqit’s is consistent with the use of struct in Racket to define a structure type. Likewise, protocol defines a generic interface (the specific name is less important, but this one was chosen to match a similar facility in Clojure and in Python). An advantage of this approach, in addition to brevity, is that it makes it easier for the programmer to identify class definitions, function definitions, protocol definitions, etc., at a glance.
13.2 Unquoted literals
Racket literals are quoted, meaning that, e.g., '(a b c) does not evaluate its arguments, a point of surprise for many newcomers, and requiring the use of (list a b c). Raqit allows writing unquoted literals like [a b c], supporting a practice followed by many programming languages, including by Rhombus. It is more convenient and more visually distinct.
13.3 Interchangeable Delimiters
At the same time, Racket programmers expect delimiter types to be interchangeable, and being able to preserve this in general syntax (aside from expression positions) is valuable. To accomplish this, Raqit handles [] and () parsing at the expansion stage, when the usage context (e.g., general syntax vs expression) is already known, allowing these to be distinguished only in expression contexts and not otherwise.
13.4 Functional Programming
Racket has adequate functional programming facilities but they are syntactically awkward (e.g., requiring the use of explicit conjoin, disjoin and compose) or conceptually gratuitous, requiring explicit use of currying, or entailing the frequent need to spell out explicit lambdas.
Raqit’s design makes it easy to embed Qi flows anywhere in your programs and conveniently extend the syntax of such flows in the same way as any other language form. This enables easy specification of higher-order functions, including intuitive currying syntax (e.g., ☯ (f arg)), effortless composition (e.g., ~>, and and or), point-free dispatching (switch) to express many common conditionals, functional optimizations not possible in Racket (deforestation), and more. Raqit strongly encourages the functional style, and with simple and elegant syntax.
13.5 Host Language and Hosted Languages
As Racket is a general ecosystem for languages, the use of any particular DSL requires a dedicated interface macro, and dedicated forms for macro-extension. Raqit aims to be a proof-of-concept of seamlessly weaving specialized hosted DSLs into the general-purpose host language by leveraging technologies such as Syntax Spec, to gain the generality of the host as well as benefit from tailored, elegant syntax and performance from the specializations of each DSL. The ability to use and extend Qi flows using familiar host language patterns (e.g., the macro form) is one example.
13.6 Relations and Operators
Racket has special relations and operators for working with every type. Raqit provides generic operators where it makes sense, including order relations like < that can sort any orderable type (e.g., numbers, strings, sets), and the ~ append operator that can append any appendable type (e.g., lists, strings, vectors). Racket’s type-specific relations are the most efficient, but the huge gain in economy of expression is worth the modest performance cost from generic dispatch in most cases.
13.7 Unified macro Form
Racket provides a diverse range of macro-defining forms and at least two separate macro systems! In addition, DSLs provide their own macro-defining forms.
Raqit unifies both single and multi-clause pattern-based macros using the single macro form. Additionally, it uses reader-level syntax to delegate macro definitions to DSL-specific macro-defining forms, once again, expecting only the same syntax common to all macro-defining forms, i.e., single-clause vs multi-clause patterns.
This unification greatly simplifies defining macros, and also supports DSLs being seamlessly part of the host language while still retaining clear formal separation.
13.8 : Instead of cons and list*
cons intuitively generalizes to list*, so Raqit handles both using the common and standard syntax, : (borrowed from functional languages like Haskell), usable in both expression and pattern-matching contexts.
13.9 Pervasive pattern-matching
In many cases, ordinary binding forms are naturally generalized by pattern-matching ones. Raqit (like Rhombus) scrupulously uses the pattern-matching generalizations offered by Racket, exclusively, avoiding the dichotomy without any loss in expressiveness.
13.10 protocol and implements
Declaring that a particular Racket struct type implements a generic interface has a complicated and non-standard syntax, where the keyword argument #:methods groups the following two otherwise ungrouped arguments, with the second one requiring an extra pair of delimiters to wrap the defined methods. Explicit grouping with implements simplifies this significantly, and also allows us to expect simply the name of the interface being implemented (e.g., (implements stack ...)), instead of its exact binding (i.e., avoiding gen:stack).
13.11 loop as Named let
Racket’s let does double duty, both as a binding form as well a looping form. This makes it very useful, but it also prevents programmers from gaining valuable clues from visually scanning code that could allow them to identify that the form refers to a recursion rather than a simple scoped expression. Therefore, Raqit’s let explicitly forbids a name, and loop, while otherwise equivalent, explicitly requires a name. Additionally, both of these are pattern matching, making them strictly more powerful than Racket’s let (and equivalent to match-let).
13.12 Research and Planned Features
It would be valuable to explore:
More dedicated DSLs for major language facilities, such as exception-handling
List comprehensions
Pervasive persistent, immutable data structures
Generic collection and sequence operations
There are also many compelling research directions being explored for Qi, intended to enable new possibilities for functional #langs including Raqit.