Using formlets from/with typed/racket, formlet-process and typing

I am trying to use formlets from typed/racket.

I have the following racket module:

#lang racket

(require web-server/formlets/syntax)
(require web-server/formlets/input)
(require "blog-core-model.rkt")

(provide
 new-post-formlet
 )

(define (if-all-non-empty f . items)
  (if
   (andmap non-empty-string? items)
   (apply f items)
   #f
   ))

(define new-post-formlet
  (formlet
   (#%# (label "Title")(br)
        ,{input-string . => . title}(br)(br)
        (label "Text")(br)
        ,{input-string . => . body}
        (br)(br))
   (if-all-non-empty post-title+body title body)))

In typed/racket, I use this formlet the following way (sketch only):

(require/typed "blog-formlets.rkt"
               [new-post-formlet (Formlet String)])

;; I added transparent after getting an error like:
;; "Unable to protect opaque value passed as ‘Any‘"
(struct post-title+body
  (
   [title : String]
   [body : String]
   )
  #:transparent)

(define-type XExprForest
  (Listof XExpr))
(define-type (Formlet A)
  (-> Integer
      (Values
       XExprForest
       (-> (Listof binding:form) Any)
       Integer)))

;; This piece of code gives an error at runtime
((inst formlet-process post-title+body) new-post-formlet request)

The last line of code (which is in reality part of a function), when called, gives a runtime error:

formlet-process: broke its own contract
  promised: A71
  produced: (post-title+body "A new post" "This is a completely new post.")
  in: a part of the or/c of
      the range of
      (parametric->/c
       (A71)
       (->*
        (g76 any/c)
        ()
        (values (or/c '#f A71))))
  contract from: 
      (interface for formlet-process)
  blaming: (interface for formlet-process)

I think that "A71" should have been post-title+body, I do not understand why I am getting the error.
Note: with typed/racket/shallow, I have no error and everything works as I expected but it would be better to have the deep types despite the (in this case unnecessary) performance overhead.

Please tell me

  • why I am getting the error message and what exactly it means
  • how one should ideally use formlets from typed/racket
1 Like

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.

  1. 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.
  2. 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.
2 Likes

Adding the type:

(require/typed
 web-server/formlets
 [formlet-process (All (A) (-> (Formlet A) Request (Option A)))])

(I was mostly just trying to add a not-that-bad guess.)