7.8

top ← prev up next →

rackjure

Source.

This is tested on Racket versions 6.0 and newer.

1 Introduction

This package provides a few Clojure-inspired ideas in Racket.

Asumu Takikawa’s #lang clojure showed me what’s possible and was the original basis. Why not just use that? Because I wanted to use some Clojure ideas in Racket, not use Clojure.

When it must choose, #lang rackjure chooses to be more Rackety. For example the threading macros are ~> and ~>> (using ~ instead of -) because Racket already uses -> for contracts. Plus as Danny Yoo pointed out to me, ~ is more "thready".

2 Using as a language vs. as a library

Most features work if you merely (require rackjure) — or a specific module such as (require rackjure/threading) — in any module language such as racket or racket/base.

However a few features only work as a module language — by using #lang rackjure at the start of your source file, or by supplying rackjure as the language in a module form. This is because they depend on redefining #%app or extending the Racket reader. These are:

Of course, because they must make #%app do more work at runtime, there is some performance overhead.

However the overhead is only for function applications within a module using rackjure as its language — not for function applications in other modules.

If you do not need those features, you can (require rackjure) or even just the specific modules you use, in a "leaner" lang such as racket/base.

For example you can use just the threading macros ~> and ~>> in racket/base:

3 Threading macros

As of version 0.9, instead of providing its own implementation, this module now re-provides all of the threading package, which has additional features not described here. Please refer to its documentation.

syntax (~> expression form )

Threads expression through the forms. Inserts expression as the second item in the first form , making a list of it if it is not a list already. If there are more forms, inserts the first form as the second item in second form, etc.

syntax (~>> expression form )

~> last item in each form. Likebut inserting as theitem in each form.





The "threading" macros let you thread values through a series of applications in data-flow order. Sometimes this is a clearer than deeply nested function calls.

Although similar to the thrush combinator function (and you may hear them described that way), these are actually macros (in both Clojure and #lang rackjure).

And although similar to compose, the order is reversed, and again, these are macros.

The ~> form "threads" values through a series of forms as the second item of each form. (If a form is a function application, remember that the second item is the first argument.)

For example, instead of:

You can write:

Or if you prefer on one line:

Notice that bytes-length and string->bytes/utf-8 aren’t enclosed in parentheses. A function that takes just one argument can be specified this way: The ~> macro automatically adds the parentheses.

syntax (some~> expression form )

Analogous to some-> in Clojure, i.e. stop threading at a #f value.

syntax (some~>> expression form )

Analogous to some->> in Clojure, i.e. stop threading at a #f value.

4 Applicable dictionaries

#lang rackjure redefines #%app to make applications work differently when a dict? is in the first position:

; When (dict? d) is #t ; Set ( d key val ) => ( dict-set d key val ) ; Get ( d key ) => ( dict-ref d key #f ) ( d key #:else default ) => ( dict-ref d key default )

And also when a dict? is in the second position:

; Get ( key d ) => ( dict-ref d key ) ( key #f ) => #f ; unless (or (procedure? ‘ key ‘ ) (dict? ‘ key ‘ ))

These last two variants, in combination with the ~> threading macro, provide concise notation for accessing nested dictionary (for example the nested hasheqs from Racket’s json module):

expands to:

( ' c ( ' b ( ' a dict ) ) )

which in turn is applied as:

Note that dictionary keys are not required to be Clojure style :keywords. They may be anything.

This application syntax doesn’t work for a dict? that stores procedure? as keys or values. The reason is that #lang rackjure must provide its own #%app. The only way (AFAIK) it can distinguish a normal function application from a dictionary application is to check for procedure? in the first position. As a result, in those cases you’ll have to use dict-ref and dict-set.

Keep in mind that a dict? is a Racket generic that covers a variety of things besides hash tables and association lists, such as vectors and lists. As a result if v is a vector then (vector-ref v 2) can be written simply as (v 2).

4.1 Not-found values

One issue is how to handle the optional last argument to dict-ref, which is the value to use if the key is not found. We handle this slightly differently than dict-ref:

1. We use an optional keyword argument, #:else. This leaves arity 3 available to mean dict-set.

2. If #:else isn’t supplied and the key isn’t found we return #f (whereas dict-ref raises an error). Rationale: Returning #f is more convenient when used with threading macros like some~>. Admittedly, one person’s "convenience" is another person’s "magic behavior" and/or "latent bug".

5 Dictionary initialization using { }

#lang rackjure provides a more-concise way to create dictionaries.

You can write

as

{k0 v0 k1 v1 ... ...}

Especially handy with nested dicts:

{ ' key "value" ' key1 { ' key "value" ' key1 "value1" } }

The current-curly-dict parameter says what this expands to.

alist hash hasheq ( f k v ... ... ) signature. Defaults to. May be set toor anything with the samesignature.

Examples:

> ( parameterize ( [ current-curly-dict alist ] ) { ' k0 0 ' k1 1 } ) ' ( ( k0 0 ) ( k1 1 ) ) > ( parameterize ( [ current-curly-dict hasheq ] ) { ' k0 0 ' k1 1 } ) ' #hasheq( ( 0 ) ( 1 ) )

Creates an association list.

Example:

> ( alist ' k0 0 ' k1 1 ' k2 2 ) '((k0 . 0) (k1 . 1) (k2 . 2))

6 Dictionary utilities

A few utility functions for dicts.

Functionally merge d1 into d0 . Values in d0 are overriden by values with the same key in d1 . Nested dict s are handled recursively.

> ( dict-merge { } { ' type ' line } ) ' ( ( type line ) ) > ( dict-merge { ' type ' triangle ' sides 3 } { ' type ' square ' sides 4 } ) ' ( ( type square ) ( sides 4 ) ) > ( dict-merge { ' people { ' john { ' age 10 } ' mary { ' age 7 } } } { ' people { ' john { ' age 11 } } } ) ' ( ( people ( john ( age 11 ) ) ( mary ( age 7 ) ) ) )

Setting a value in d1 to the current value of the dict-merge-delete-value parameter – which defaults to 'DELETE – causes the key/value in d0 with that key to be deleted from the returned dictionary.

> ( dict-merge ' ( [ a a ] [ b b ] ) ' ( [ b DELETE ] ) ) ' ( [ a a ] )

' DELETE . Used to tell dict-merge Defaults to. Used to tellthat a key/value pair with that key should be deleted.

> ( parameterize ( [ dict-merge-delete-value ' DELETE ] ) ( dict-merge ' ( [ a a ] [ b b ] ) ' ( [ b DELETE ] ) ) ) ' ( [ a a ] ) > ( parameterize ( [ dict-merge-delete-value ' FOO ] ) ( dict-merge ' ( [ a a ] [ b b ] ) ' ( [ a DELETE ] [ b FOO ] ) ) ) ' ( ( a DELETE ) )

Returns a {} style string describing the dict d , including any nested dict s.

> ( define sample-dict ' ( [ a 0 ] [ b 0 ] [ c ( [ a 0 ] [ b 0 ] [ c ( [ a 0 ] [ b 0 ] [ c 0 ] ) ] ) ] ) ) > ( displayln ( dict->curly-string sample-dict ) ) { ' a 0 ' b 0 ' c { ' a 0 ' b 0 ' c { ' a 0 ' b 0 ' c 0 } } }

7 Strings

Idiomatic Racket would probably use ~a.

str can be a succinct alternative to string-append and/or format.

Also, it returns an immutable string (created via string->immutable-string).

Examples:

> ( str ) "" > ( str "hi" ) "hi" > ( str 1 ) "1" > ( str #f ) "#f" > ( str "Yo" "Yo" ) "YoYo" > ( str "Yo" "Yo" "Ma" ) "YoYoMa" > ( apply str ' ( 0 1 2 3 ) ) "0123" > ( str 0 1 2 3 ) "0123" > ( str ' ( 0 1 2 3 ) ) "(0 1 2 3)"

Our version adds optional keyword arguments, the defaults of which behave like Clojure’s str:

#:fmt: The function to apply to each argument. Defaults to ~a. May be any (-> any/c string?) function, e.g. ~v.

#:sep: A string? to add between each. Defaults to "".

Examples:

8 Conditionals

Idiomatic Racket would probably use match.

Combines if and let:

( let ( [ identifier test-expr ] ) ( if identifier then-expr else-expr ) )

Idiomatic Racket would probably use match.

Combines when with let:

( let ( [ identifier test-expr ] ) ( when identifier body ) )

A shortcut for:

Idiomatic Racket would use unless.

A shortcut for:

( when ( not test-expr ) body )

9 Operational equivalence

egal? as described in An implementation ofas described in Equal Rights for Functional Objects

An alternative to equal? and eq? that says whether two things are "operationally equivalent", by taking into account mutability.

In general, two things that are equal? will also be egal? only if they are both immutable. Some things in Racket aren’t immutable by default. For example, although "string-constants" are immutable, strings returned by string or string-join are mutable, unless you also run them through string->immutable-string. Same with bytes. Other things come in both mutable and immutable variants, such as hashes and vectors.

For more details, see egal.rkt for the implementation and test cases. A few examples:

Examples:

> ( require rackjure/egal ) ; Although "string" literals are immutable... > ( egal? "a" "a" ) #t ; string is mutable... > ( egal? ( string #\a ) ( string #\a ) ) #f ; Immutable strings are (you guessed it) immutable... > ( egal? ( string->immutable-string ( string #\a ) ) ( string->immutable-string ( string #\a ) ) ) #t

For two structs to be egal?, all of the following must be true:

1. They must have the same field values.

2. They must be instances of the same structure type.

3. The structure type must be #:transparent. (Regular equal? does a field comparison for Racket structs only if they are #:transparent. Otherwise the structs are opaque and eq? is used.)

4. The structure type must not be #:mutable, nor must any of the individual fields be #:mutable.

10 Other

10.1 Partial application

procedure (partial proc v ) → procedure? proc : procedure? v : any/c

curry Function for partial application. Differs fromin that it doesn’t care about function arity.

( ( partial + 1 ) 2 ) <=> ( + 1 2 )

10.2 Atomic swap

procedure (box-swap! box proc v ) → any/c box : box? proc : procedure? v : any/c

swap! in Clojure, but for box? Likein Clojure, but for

Essentially it is:

( define ( box-swap! box f . args ) ( let loop ( ) ( let* ( [ old ( unbox box ) ] [ new ( apply f old args ) ] ) ( if ( box-cas! box old new ) new ( loop ) ) ) ) )

11 Reader function literals

The Clojure reader lets you succinctly define anonymous function literals. For example

#( )

is equivalent to this in Clojure:

or in Racket:

%1 through %n are positional arguments

% is a synonym for %1

%& is a rest argument

%#:keyword is a #:keyword argument

The Racket reader already uses #( ) for vector literals. Therefore Rackjure instead uses your choice of #fn( ), #λ( ), or #lambda( ).

Examples:

> (map #λ(+ % 1) '(1 2 3)) '(2 3 4) > (map #λ(+ % %2) '(1 2 3) '(1 2 3)) '(2 4 6) ;; Rest argument > (#λ(apply list* % %&) 1 '(2 3)) '(1 2 3) ;; Keyword argument > (#λ(* 1/2 %#:m (* %#:v %#:v)) #:m 2 #:v 1) 1 ;; Ignores unused arguments > (#λ(begin %2) "ignored" "used") "used" ;; Handles an arbitary number of arguments > (apply #λ(list %1 %42) (build-list 42 add1)) (list 1 42)