rackettyped-racket

Function typing in TypedRacket


Is it possible to write something similar to C++'s

template <typename... T> std::string dup(int n, std::string s, T... tail) {
  std::string result;
  for (int i = 0; i < n; ++i) {
      result += s;
  }
  if constexpr (sizeof...(tail) == 0) {
    return result;
  } else {
    return result + dup(tail...);
  }
}


std::cout << dup(1, std::string("abc"), 2, std::string("xyz"), 3, std::string("a")) << std::endl; // abcxyzxyzaaa

I tried to write the following function in TypedRacket

#lang typed/racket

(: repeat-string (-> Integer String String))
(define (repeat-string n str)
  (apply string-append (build-list n (λ (_) str))))

(: dup (All (T ...) (-> Integer String T ... T String)))
(define (dup n s . tail)
  (define result (repeat-string n s))
  (cond [(empty? tail) result]
        [else (string-append result (apply dup tail))])
  )

but i have typecheck error: Type Checker: Bad arguments to function in 'apply': Domain: Integer String T ... T Arguments: (List T ... T) in: (apply dup tail)

Honestly, i dont understand why arguments are (List T ... T) but not (T ... T). Moreover, the function is correct, untyped Racket produces the correct results


Solution

  • The key to doing this with proper type safety is to specify the type of your function using ->* with a #:rest-star keyword:

    #lang typed/racket
    
    ;; Version that avoids building an intermediate list
    (: repeat-string : Integer String -> String)
    (define (repeat-string n s)
      (define len (string-length s))
      (build-string (* n len) (lambda (i) (string-ref s (remainder i len)))))
    
    
    (: dup (->* (Integer String) #:rest-star (Integer String) String))
    (define (dup n s . rest)
      (if (null? rest)
          (repeat-string n s)
          (string-append (repeat-string n s) (apply dup rest))))
    
    (dup 1 "abc" 2 "xyz" 3 "a") ; "abcxyzxyzaaa"
    

    If your function takes a list directly, you can use Rec and List* to create the type signature. You can also then define dup in terms of it:

    (: dup* : (Rec IntStrRepeated (U Null (List* Integer String IntStrRepeated))) -> String)
    (define (dup* pairs)
      (if (null? pairs)
          ""
          (string-append (repeat-string (car pairs) (cadr pairs)) (dup* (cddr pairs)))))
    
    (: dup (->* (Integer String) #:rest-star (Integer String) String))
    (define (dup n s . rest)
      (dup* (list* n s rest)))
    
    
    (dup* '(1 "abc" 2 "xyz" 3 "a")) ; "abcxyzxyzaaa"