Hello,
I would like to define typed classes with abstract methods / interfaces. This is the approach I am going for at the moment:
(define my-interface%
(class object%
(super-new)
(: abstract-increment (-> Integer Integer))
(define/public (abstract-increment x) (error 'abstract-increment "undefined"))))
(define my-impl%
(class my-interface%
(super-new)
(define/override (abstract-increment x) (+ x 1))))
(define obj1 (new my-interface%))
;(send obj1 abstract-increment 1) ; => error "undefined"
(define obj2 (new my-impl%))
(send obj2 abstract-increment 1) ; => 2
Are there any better ways to achieve this?
I quickly looked at the implementation of interface, and it seems quite unsurprisingly to be doing something more complex than what my code does.
The object system in Racket is structural not nominal, so, if I understand you correctly, row polymorphism for typed code would be an ideal way to handle situations similar to yours. However, I recently found out that TR's local type inference currently does not support row polymorphism for objects.
1 Like
It appears that typed/racket/class does not support the abstract
form supported by racket/class. Perhaps this is a bug? Or perhaps abstract
is hard for the semantics?
1 Like
Typed Racket's support for class/objects are experimental currently. I think it is not too hard to statically check if a class to be instantiated contains abstract
methods (and/or ones inherited from its base classes)
[I can't speak for Asumu, but I think it was just because `abstract` added few interesting values from a gradual typing's perspective. ]
I think I wasn't clear: for @scolobb's case, if interfaces (in an oo sense) are the only thing needed, then Typed Racket already has types to express them, but the corresponding support in the local type inference are not there yet.
1 Like
Thank you for your answers!
I think I get the gist of what you are saying @capfredf , which means that I will go with my crude approach for now. I have been poring on row polymorphism for some time now, and while the basic definition seems rather clear, I am still not quite sure about the implications. More poring needed, and your answers are quite helpul
I have in fact found an answer to the interface part of my question (see caveat below) in the docs right here: 4 Typed Classes , in the documentation for #:implements
. Here is how it applies to my original example:
(define-type my-interface<%> (Class (abstract-increment (-> Integer Integer))))
(: my-impl% (Class #:implements my-interface<%>
(extra-method (-> String))))
(define my-impl%
(class object%
(super-new)
(define/public (abstract-increment x) (+ x 1))
(define/public (extra-method) "I am an extra method")))
;(define obj1 (new my-interface<%>)) ; Type name used out of context.
(define obj2 (new my-impl%))
(send obj2 abstract-increment 1)
(send obj2 extra-method)
That's quite cool, because you get actual interfaces. You just need to keep in mind that you get them via a different mechanism than (untyped) Racket's interface
construct.
Caveat: This almost works for me, because ideally I would like to pass type parameters to the parent class/interface (cf. this other question), and #:implements
does not accept type constructors.
Since #:implements
doesn't accept constructors, could you write the equivalent of
(define instantiated-type (my-interface-constructor<%> Integer))
(: my-impl% (Class #:implements instantiated-type …))
?
Not elegant, but might get the job done?
1 Like
Aah, that's clever, thank you! I am leaving on vacation right now, but I'll test it once I'm back in front of a computer.
I am not sure it solves my main problem, as I would essentially like to parameterize instantiated-type
by a type variable coming from the signature of my-impl%
, but I have to try!