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.