I'm not completely sure what you want, so the following handles both unbound identifiers in function position, as well as expressions in function position that have a value but the value is not a procedure. It delegates to a macro and a function respectively, that you can play with separately without fully understanding the main handler.
#lang racket
; We'll override ...
#;(f a ...) ; ... when f is not a macro, i.e. when this is by default a function call.
(module app-hook racket
(provide (rename-out [our-app #%app]))
(require syntax/parse/define)
; Will delegate to here at expansion time when f is an unbound identifier, for maximum control.
(define-simple-macro (call-with-unbound f a ...)
; For example, you could ...
(list 'f a ...))
; Will delegate to here at run time if f evaluates to a non-procedure.
(define (call-with-non-procedure f . as)
; For example, you could ...
(list* f as))
(define-syntax-parser our-app
[(_ f:id a ...)
#:when (not (identifier-binding #'f))
(syntax/loc this-syntax
(call-with-unbound f a ...))]
[(_ f a ...)
(syntax/loc this-syntax
(let ([f-value f]) ; standard macro side-effect safety : evaluate f expression only once
(if (procedure? f-value)
(f-value a ...)
(call-with-non-procedure f-value a ...))))]))
(require 'app-hook)
(g (length '(a b c))) #;'(g 3)
(length (g '(a b c))) #;2
((+ 1 2) (+ 30 40 50)) #;'(3 120)
((println "once"))
; To Do (for an “industrial strength” version) : are we okay with the error messages from ...
#;()
#;(a . b)
#;(#:k)
#;((values 1 2))