On 12/28/2012 08:23 AM, Matthias Felleisen wrote:
On Dec 27, 2012, at 9:22 PM, Neil Toronto wrote:
Sorry it took so long to reply.
I applied the patch and verified that my example runs a *lot* faster (with
domain and range contracts identical, of course). Unfortunately, a similar test
using arrays isn't any faster. This is how the identity function for arrays is
defined in the test:
(: array-id (All (A) ((Array A) -> (Array A))))
(define (array-id arr) arr)
If we had macros for types, we might be able to write
(All (A) (let ((t (Array A))) (t -> t)))
The change doesn't help because the contracts for each instance of (Array A) in
the type aren't identical. Merging such contracts would be a great space
optimization in general, but I don't know whether it would make using
`math/array' from untyped code any faster.
Actually, right now, I'm sure it wouldn't help much. I can't think of any array
functions that return an array with an identical procedure
It is not an identical procedure that makes for contract optimizations here but
identical contracts/types. Would that help?
Hmm... it might, if the merging were thorough enough (i.e. merging
contracts from different modules) or `contract-stronger?' were used. By
the time an array is operated on in Typed Racket code, I think it's had
its procedure wrapped with two equivalent higher-order contracts. An
example:
#lang racket
(require math/array)
(define arr (make-array #(100 100) 0))
(array->mutable-array arr)
So `arr' has an (Array any/c) contract from being returned from
`make-array' across the barrier. When `array->mutable-array' operates on
it, it crosses the barrier again, so an (Array any/c) contract is applied.
What would be really nice is if passing the array back in to typed code
*removed* the contract put on it coming out, or removed parts of it. For
the array's procedure, the typed code needs a ((Vectorof Index) -> A).
It gets a value with the contract ((vectorof index?) . -> . any/c). The
typed code has been statically verified to not violate that contract.
Would it be sound for the barrier to detect that and unwrap the value?
Neil ⊥
On 12/18/2012 12:04 PM, Robby Findler wrote:
I can't help with the others, but for 1.) I've considered using a
contract-stronger? test or even just an eq? test when applying a
contract, checking what's already on the value. I haven't added that as
it hasn't come up "in anger" (and it isn't free, but the check is
cheap). If I put a broken test (diff below) and adjust your example a
little bit, it runs quickly for me. Does it also help in your larger
context?
Adjustment:
(module defs racket
(define c (any/c . -> . any/c))
(provide (contract-out [fun-id (-> c c)]))
(define (fun-id f) f))
Diff:
[robby@wireless-165-124-98-86] ~/git/plt/collects/racket$ git diff . | cat
diff --git a/collects/racket/contract/private/arrow.rkt
b/collects/racket/contract/private/arrow.rkt
index 55ed632..63c9a6e 100644
--- a/collects/racket/contract/private/arrow.rkt
+++ b/collects/racket/contract/private/arrow.rkt
@@ -506,6 +506,10 @@ v4 todo:
partial-mandatory-kwds
partial-optional-kwds
partial-ranges))
(λ (val)
+ (cond
+ [(eq? (value-contract val) ctc)
+ val]
+ [else
(if has-rest?
(check-procedure/more val mtd? dom-length
mandatory-keywords optional-keywords orig-blame)
(check-procedure val mtd? dom-length optionals-length
mandatory-keywords optional-keywords orig-blame))
@@ -521,7 +525,7 @@ v4 todo:
impersonator-prop:contracted ctc
impersonator-prop:application-mark (cons contract-key
;; is this right?
- partial-ranges)))))))
+
partial-ranges)))])))))
(define (->-name ctc)
(single-arrow-name-maker
The diff is broken because it doesn't account for blame. The test should
be making sure that the two contracts blame the same things
(alternatively, we could not check that and just drop the inner
contract. That may be better, but not as easy to try ...)
Robby
On Tue, Dec 18, 2012 at 12:06 PM, Neil Toronto <neil.toro...@gmail.com
<mailto:neil.toro...@gmail.com>> wrote:
So there are potentially huge inefficiencies when mixing typed and
untyped code, usually when functions are passed across the contract
barrier. Here are a few things that I think cause them. Can the
People Who Know This Stuff Really Well please comment on these issues?
====
1. Functions that cross the contract barrier can have exactly the
same contract applied multiple times. Here's some benchmark code
that demonstrates the issue:
#lang racket
(module defs racket
(provide (contract-out [fun-id (-> (any/c . -> . any/c)
(any/c . -> . any/c))]))
(define (fun-id f) f))
(require 'defs)
(define (f x) x)
(define h
(for/fold ([h f]) ([_ (in-range 1000)])
(fun-id h)))
(for ([_ (in-range 5)])
(time (for ([_ (in-range 1000)])
(h 5))))
I get over 800ms for each 1000 applications of `h', because it's
basically a 1000-deep wrapper. (Or is it 2000?)
(The contract system is smart enough to check the contract quickly
when the types are (any/c . -> . any), but I don't think TR ever
generates contracts using `any'.)
This is a problem for `math/array' because array procedures are
wrapped going in and going out with exactly the same contract:
((vectorof index?) . -> . any/c).
====
2. It seems TR checks more things than it really has to. In this
example, the predicate `foo?' prints something so we can observe
when it's applied, and is used as a predicate for an opaque type
whose values cross the contract barrier:
#lang racket
;; Provides a predicate and constructor for the opaque type `Foo'
(module foo-defs racket
(provide foo? make-foo)
(define (make-foo x) x)
(define (foo? x)
(printf "foo?~n")
(exact-integer? x))
)
(module typed-defs typed/racket
(provide get-foo foo-ap)
(require/typed
(submod ".." foo-defs)
[#:opaque Foo foo?]
[make-foo (Integer -> Foo)])
;; prints `foo?' 10 times; definitely necessary
(define foos (build-vector 10 make-foo))
(: get-foo (Integer -> Foo))
(define (get-foo i)
(vector-ref foos i))
(: foo-ap (All (A) ((Foo -> A) Foo -> A)))
(define (foo-ap f foo)
(f foo))
)
(require 'typed-defs)
I don't understand why the contract for `get-foo' has to check the
return value, because TR already ensures that `get-foo' always
returns a `Foo':
(printf "going to get a foo~n")
(get-foo 5) ; prints `foo?' once; why?
Could TR generate (exact-integer? . -> . any) for `get-foo'?
Relatedly, TR already ensures that the function passed to `foo-ap'
is only applied to `Foo' values, but this is also checked by a contract:
(printf "going to apply a function to a foo~n")
(foo-ap identity 1) ; prints `foo?' twice; why not once, just for 1?
====
3. It's a shame we can't know statically whether an untyped function
will return more than one value.
Suppose we could, and that TR generated contracts with `any/c'
argument types for higher-order functions (i.e. fix issue #2). Then
array procedures passed from untyped to typed code could have the
contract (any/c . -> . any), which is checked immediately.
There's probably more, but this will do for now.
Neil ⊥
_________________________
Racket Developers list:
http://lists.racket-lang.org/__dev <http://lists.racket-lang.org/dev>
_________________________
Racket Developers list:
http://lists.racket-lang.org/dev
_________________________
Racket Developers list:
http://lists.racket-lang.org/dev