The root of the problem seems be using Any
when you need to use specific a polymorphic type variable, like A
. You don't show the type you have given for formlet-process
, but your definition of Formlet
:
allows the processing function to return Any
value, when a (Formlet A)
must specifically return a value of type A
.
It's hard to explain the error message in detail without knowing what type you had given for formlet-process
, but parametric->/c
is used to create contracts with parametric polymorphism: it's more or less the dynamic equivalent of Typed Racket's ∀
.
Here is a self-contained working version of your example: the final expression returns '#("A Tale of Two Cities" "It was the best of times.")
.
#lang typed/racket
(module untyped racket
(provide new-post-formlet
demo-request)
(require web-server/formlets/syntax
web-server/formlets/input
web-server/http
net/url)
(define new-post-formlet
(formlet
(#%# (p (label "Title" (br)
,{input-string . => . title}))
(p (label "Text" (br)
,{input-string . => . body})))
(and (non-empty-string? title)
(non-empty-string? body)
(vector-immutable title body))))
(define demo-request
(request #"POST"
(url #f #f #f #f #t '() '() #f)
'()
(delay
(list (binding:form #"input_0" #"A Tale of Two Cities")
(binding:form #"input_1" #"It was the best of times.")))
#f
"192.168.1.1"
80
"192.167.1.2")))
(require typed/xml
typed/net/url
typed/web-server/http)
(define-type XExprForest
(Listof XExpr))
(define-type (Formlet A)
(-> Integer
(Values XExprForest
(-> (Listof binding:form) A) ; NOT Any
Integer)))
(require/typed 'untyped
[new-post-formlet
(Formlet (U #f (Immutable-Vector String String)))]
[demo-request
request])
(require/typed web-server/formlets/lib
[formlet-process
(∀ [A]
(-> (Formlet A) request A))])
(formlet-process new-post-formlet demo-request)
(I took the liberty of adjusting your markup: putting an input element inside a <label></label>
establishes the semantic relationship for the browser and assistive technologies without having to explicitly use IDs.)
I've used both Typed Racket and formlets, but I've never used formlets from Typed Racket. Still, I'll offer a few thoughts.
The types I wrote in my example above restrict formlets to return a single value. My experience (particularly from Fix formlet/c by LiberalArtist · Pull Request #29 · racket/web-server · GitHub) leads me to believe this will cover every case you will encounter in practice, but it is a restriction compared to web-server/formlet
. It is awkward to avoid the restriction because define-type
doesn't support defining variadic type constructors, and Typed Racket can't generate contracts for variable-arity polymorphic types.
If you are using send/suspend
or other continuation-based features of #lang web-server
, you will want to make sure you understand the reference section on Formlets and Stateless Servlets.
Overall, my main suggestion is that you think through what benefit you are hoping to get by using Typed Racket. In general, the reason to use Typed Racket for part of a program is to check some invariants in that part at compile time instead of run time. But choosing which parts of a program to write in Typed Racket depends a great deal on context. To be more concrete, let me sketch two contrasting points of view, both of which could be reasonable under the right circumstances.
- Alice is writing a web application with complicated "business logic". She chooses to write most of it in Typed Racket because she prefers to get errors at compile time. Alice knows that the parts of her application that parse network input will inevitably have to deal with malformed data at runtime: Typed Racket won't protect her from ill-formed or malicious HTTP requests. For convenience, Alice chooses untyped Racket for the code that uses
web-server/formlets
to extract request fields and put them into data structures. She relies on the contracts generated by Typed Racket to confirm that the data is valid when it crosses the boundary to the rest of her application.
- Bob's web application needs to handle a lot of structured user input. He is writing many custom formlets and even some higher-order formlet combinators. Bob wants to use Typed Racket to find logic errors in the formlets he's implementing as soon as possible, without having to run the application's extensive test suite. Bob writes a wrapper module for
web-server/formlets
in Typed Racket (similar to the implementation of typed/web-server/http
) and reimplements the formlet
macro to cooperate better with Typed Racket. Bob's application simply saves the parsed input for later analysis, so Bob decides only to use Typed Racket for the code that implements his formlets.