racket / typed-racket

Typed Racket

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

require/typed confusing contract optimization

bennn opened this issue · comments

The domains of functions imported via require/typed are never checked at runtime, even if the functions are provided to untyped modules.

#lang racket/base

(module a racket/base
  (provide f)
  (define (f x) (add1 x)))

;; ----

(module b typed/racket/base
  (require/typed/provide (submod ".." a)
   [f (-> Natural Natural)]))

;; ----

(require 'b)
(f f)

This seems confusing at first, but I don't think it's actually unsound. Because this only lets you call untyped functions with the "wrong" type, and only in untyped contexts. If module a is typed then it will actually get a contract from a and the call will be checked.

Okay, I can change the title 😄

But I still think this is "weirdness" worth fixing for the case of an untyped script using a typed adaptor module.

(module typed-and-safe typed/racket/base
  (require/typed racket/string ;; Some library I don't want to re-implement
    [string-trim (-> String String)])
  (provide (rename-out [string-trim safe-trim]))
  ...)

(module my-script racket/base
  (require (submod ".." typed-and-safe))
  (safe-trim 4) ;; But I trusted you to catch my mistakes!
)

Also odd is that this actually does the check in Racket v6.0. In v6.0.1 and v6.1 it doesn't.

@bennn and I discussed this again and we decided that we're not going to change anything here.

@bennn and I discussed this again and we decided that we're not going to change anything here.
👍

I was expecting module b to take ownership of the function that passes through. TR doesn't do that, partly because the baseline Racket behavior for this require/provide pattern lets the outer module reference the function from a without going through b. (TR not taking ownership also means fewer contracts ~ faster performance.)

It's possible to get the behavior that I wanted with an extra define:

#lang racket/base

(module a racket/base
  (provide f)
  (define (f x) (add1 x)))

;; ----

(module b typed/racket/base
  (require/typed (submod ".." a)
   [(f -f) (-> Natural Natural)])
  (define f -f)
  (provide f))

;; ----

(require 'b)
(f f)
;;f: contract violation
;;  expected: natural?
;;  given: #<procedure:f>
;;  in: the 1st argument of
;;      (-> natural? any)
;;  contract from: 
;;      (189.rkt b)
;;  blaming: 189.rkt
;;   (assuming the contract is correct)