Yes, there are two standard ways.
Contracts
Contracts check conditions and raise errors at runtime:
#lang racket
(define/contract (sum-coins pennies nickels dimes quarters)
(-> integer? integer? integer? integer? integer?)
(+ (* 1 pennies)
(* 5 nickels)
(* 10 dimes)
(* 25 quarters)))
(sum-coins 1 2 3 4)
;; => 141
(sum-coins "1" 2 3 4)
; sum-coins: contract violation
; expected: integer?
; given: "1"
; in: the 1st argument of
; (->
; integer?
; integer?
; integer?
; integer?
; integer?)
; contract from: (function sum-coins)
; blaming: /tmp/so.rkt
; (assuming the contract is correct)
; at: /tmp/so.rkt:3.18
; Context (errortrace):
; /tmp/so.rkt:11:0: (sum-coins "1" 2 3 4)
; /tmp/so.rkt:11:0: (sum-coins "1" 2 3 4)
Typed Racket
Or you can use Typed Racket, which checks types at compile time:
#lang typed/racket
(define (sum-coins [pennies : Integer]
[nickels : Integer]
[dimes : Integer]
[quarters : Integer])
(+ (* 1 pennies)
(* 5 nickels)
(* 10 dimes)
(* 25 quarters)))
(sum-coins 1 2 3 4)
;; => 141
(sum-coins "1" 2 3 4)
; /tmp/so2.rkt:14:11: Type Checker: type mismatch
; expected: Integer
; given: String
; in: "1"