Hello Racketeers (and Robby especially)!

On 22. 12. 20 1:30, Robby Findler wrote:
> Is Typed Racket able to prove that your use of unsafe accessors is
> actually safe? 

Short answer: YES.

One question for a start: And what now?

Disclaimer: The following text is by no means intended as critique but
rather it should be viewed as a summary of my experience with typing
middle-sized project in two weeks.

Long rant follows.

When Ben sent the Typed Racket Survey[1] here, my experience with TR was
only minimal. I worked with the math/matrix and basically focused on
performance gains it might yield. So I honestly filled-in whatever I
knew. Now I know much more. Hope it will help.

Let's first sum up where TR helped enormously and actually surprised me
VERY pleasantly:

* Finding cases of (when ...) and similar #<void> returning constructs
where values are to be returned and probably error should be raised
instead of returning nothing. Although the actual erroneous return never
happened in the code in question, those constructs were meant to ensure
correct value is returned... Probably some leftovers from the early days
of this project. TR spotted them immediately.

* Finding improper usage of #f for missing information. When the program
expects some value, #f is not the right choice of fallback value. It is
the right one if you want to use it as a condition. Again, these were
leftovers from various experiments - mostly when loading data. TR to the
rescue again.

* Unintended use of default procedure arguments - nice and unexpected!

And now for the worse part. TR rough edges:

* Higher-order procedures and polymorphic functions in all imaginable
combinations. That was a total disaster. Yes, the documentation clearly
states that - but typing any code using these is like trying to break a
badly designed cipher. Irregularities here and there. Sometimes simple
`inst' was enough. Sometimes casting both the function and the arguments
was necessary. The biggest trouble is that the error messages are very
cryptic and sometimes even do not point to the offending construct but
only the module file. I will provide MWE to show this if someone wants
to look into it.

* Struct: Missing struct generics - when porting code that relies on
them, there's not much that can be done.

* Math: there really is just a natural logarithm and no logarithm with
arbitrary base? Yes, one-line to implement, but why?

* Math/Fixnums/Flonums: All fx+/-/*/... accept two arguments only. No
unary fl-, no variadic-argument fl+ or fxior (this one hurt the most).

* unsafe/ops: unsafe-vector-* resisted a lot - until I just gave up and
require/typed it for my particular types. Maybe casting would help, but
the error messages were different to the classical problems of the
polymorphic functions in TR.

* Classes: My notes say "AAAAAAAAAAAAAAAAAAAA". Which roughly says it
all. Although I managed to back down to only using bitmap% class,
properly typing all procedures using it was a nightmare. By properly I
mean "it compiles and runs".

* with-input-from-file does not accept Path or String, only Path-String
and the conversion rules are either missing or strange at best.
Basically I ended up with just converting to String and casting to
Path-String to make everything work.

* with-input-from-file also revealed that procedure signatures as types
can be very tricky - just passing `read' was not possible, because it
accepts some optional arguments. Wrapping it in thunk helped though.

* order of definitions matters. Not that this is unexpected, it is just
strange when working with larger code-base where it didn't matter.
Actually the error messages were helpful here.

* Type annotations of procedures with variadic arguments. The only place
where I had to put annotations outside of the procedure definition. It
is nothing super-problematic, but it feels inconsistent with the rest.

* More modules need to be required everywhere. If module A provides a
procedure that accepts a type from module B, all modules using that
procedure must also require the module B to know the type. In normal
Racket it does not matter as long as you just pass the opaque data along.

* Syntax macros are extra hard. As I use some syntax trickery to convert
semi-regular code to "futurized" one, I basically gave up and just ran
everything single-threaded. The main issue is passing type information
from the module that uses the macro to the macro and matching it on both
sides. Also the macro must split the type annotation from argument names
in the syntax pattern when it defines new procedures - I did some ugly
hacks to get through it but in the end I just refrained from using them
when possible (except for the unsafe-struct macro, of course).

If anyone actively working on TR reads this, I'd be more than happy to
discuss my experience and share the code (although it is really ugly as
I really only intended to prove the unsafe structs are used safely).
Next Saturday would be a good time to discuss it at the Racket Users
Video Meetup[2]!


Hmmm... some clever closing words...

Robby, thank you! And I mean it both sarcastically (two weeks, 52
commits, 2292-line diff!) and genuinely (it really helped).

Doctor Tobin-Hochstadt: Tear down this wall! (Thanks Jay ;)


Cheers,
Dominik


[1] https://groups.google.com/g/racket-users/c/qfepyERIsYA/m/Tnz3rjcHAwAJ
[2] https://groups.google.com/g/racket-users/c/-_I17qdo3SY/m/QcBV-Aa0CwAJ

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/785e18e7-54b4-53f1-e53e-a621221bdf00%40trustica.cz.

Reply via email to