For a parameter created with a guard function, the result of the guard function is used instead of the value passed in. (The guard function is not applied to the initial value.) So when you write (parameterize ([*host* "hello"]) ...) or (*host* "hi"), the parameter is set to #t because that is the result of (string? "hello") and (string? "hi").
You could either change the guard function to something like:
(λ (v)
(if (string? v)
v
(raise-argument-error '*host* "string?" v)))
Thank you! Further confusing the issue, the "guard" isn't used to check the original value! (Just the opposite as with structs!).
I'm now using my own constructor for parameters:
(define (my-parameter value guard name [check-name #f])
(define (string x) (format "~a" x))
(define (check v)
(unless (guard v)
(raise-argument-error (string name)
(string (or check-name (object-name guard) 'check))
v ) )
v )
(make-parameter (check value) check name) )
It works just as with structs, actually: the guard for a struct should return all values (as multiple values) for the fields, and a parameter is conceptually also a struct with one field. If you don’t need to do anything with the values, just return them as is. The reason guards work this way is so that guards can do more than checking, such as normalizing the input values (as with “Boolean parameters”).