greg
2019-5-22 12:47:57

Someone else can probably explain this better (because I’ve hardly used Racket generics or classes). But: - structs are the most basic “thing” in Racket. They aggregate data. They come in three flavors: The default opaque, #:transparent, and #:prefab. The only notion of “inheritance” here is adding more fields to a superstruct, and getting a new predicate for the substruct. There are no “methods”. - racket/class is a full-on object-oriented system with a ton of options. - Generics gen:interface were added later — https://blog.racket-lang.org/2012/11/generics.html


greg
2019-5-22 12:49:15

Maybe this is a little confusing. Maybe the User’s Guide could have some overview near the start. Maybe we need more Racket books.


greg
2019-5-22 12:50:25

Anyway @sydney.lambda it sounds like maybe you’re (understandably) confusing classes and generics?


greg
2019-5-22 12:51:43

Maybe reading about each will help.


greg
2019-5-22 12:53:26

(I spent decades doing OOP in C++. For me, Racket was a chance to not do OOP. Instead to learn more about so-called functional programming. As a result I’m pretty ignorant about racket/class and racket/generics. At most I’ve played around with e.g. defining a gen:custom-write to make some struct write/print/display in a special way.)


greg
2019-5-22 12:54:00

(So, not even defining my own generic interface. Just providing an implementation for one.)


greg
2019-5-22 12:54:20

TL;DR I am a bear of little help.


jerome.martin.dev
2019-5-22 13:43:42

I use generics from time to time. They do have a notion of “inheritance” but it’s really simple. When you create a generic, you create a function that will have an optional default behavior when applied on any object (using #:defaults and fallbacks), then a custom behavior for every struct that overrides the given generic. It’s the basic framework from which one can write an OOP system, but it doesn’t force you to program in OOP.


jerome.martin.dev
2019-5-22 13:44:31

It’s handy when you use simple structs most of the time, but need a simple “dispatch” function that works on different types


jerome.martin.dev
2019-5-22 13:46:00

If you make a new struct that is based on a struct with generics, the new struct will have the behaviors of its parent, like a normal inheritance system


jerome.martin.dev
2019-5-22 13:47:22

But compared to OOP, where the functions are stored and called from the objects, here you just plug in a new dispatch route to an existing function


jerome.martin.dev
2019-5-22 13:48:29

If you don’t specify defaults nor fallbacks, you’ll get an error, of course, if you try calling a generic on a struct that did not defined a behavior


diallo.ms
2019-5-22 20:15:55

Hello,

I am studying Binary Search Trees in ISL+. The context is a bit long but easy to understand… Context: The 2 exercises are related:

Design the function create-bst. It consumes a BST B, a number N, and a symbol S. It produces a BST that is just like B and that in place of one NONE subtree contains the node structure (make-node N S NONE NONE). ; BST Number Symbol -> BST ; Produces a BST that is just like B and that in place of one NONE subtree contains the node structure (make-node N S NONE NONE). (check-expect (create-bst (make-node 89 'h NONE (make-node 95 'g NONE NONE)) 77 'c) (make-node 89 'h (make-node 77 'c NONE NONE) (make-node 95 'g NONE NONE))) (define (create-bst B N S) (match B [(no-info) (make-node N S NONE NONE)] [(node ssn name left right) (cond [(< N ssn) (make-node ssn name (create-bst left N S) right)] [(> N ssn) (make-node ssn name left (create-bst right N S))] [else B])])) The other exercise is: Design the function create-bst-from-list. It consumes a list of numbers and names and produces a BST by repeatedly applying create-bst.

My solution: (define tD (make-node 63 'a (make-node 29 'b (make-node 15 'd (make-node 10 'h NONE NONE) (make-node 24 'i NONE NONE)) NONE) (make-node 89 'c (make-node 77 'l NONE NONE) (make-node 95 'g NONE (make-node 99 'o NONE NONE))))) (define tC '((99 o) (77 l) (24 i) (10 h) (95 g) (15 d) (89 c) (29 b) (63 a))) ; [List-of [List Number Symbol]] -> BST ; Produces a BST by repeatedly applying create-bst. ;(check-expect (create-bst-from-list tC) tD) (define (create-bst-from-list ll) (match ll ['() NONE] [(cons head tail) (create-bst (create-bst-from-list tail) (first head) (second head))]))

My question: I feel this last function can be written as: (define (create-bst-from-list.v2 ll) (foldr (lambda (bst l) (create-bst bst (first l) (second l))) NONE ll)) I am having difficulties figuring out the step between create-bst-from-list to create-bst-from-list.v2. Could I have some hints as to proceed ? Thank you !


diallo.ms
2019-5-22 20:19:50

The data definition (that I had forgotten to add): (define-struct no-info []) (define NONE (make-no-info)) (define-struct node [ssn name left right]) ; A BT (short for BinaryTree) is one of: ; – NONE ; – (make-node Number Symbol BT BT)


soegaard2
2019-5-22 20:27:30

@diallo.ms In create-bst-from-list.v2 switch from (lambda (bst l) ...) to (lambda (l bst) ...).


sydney.lambda
2019-5-22 20:41:49

(apologies for the intrusion)


sydney.lambda
2019-5-22 20:42:08

Thanks for the help @greg and @jerome.martin.dev (apologies, I was at college). The issue was that I had an interface, a struct, and a substruct defined in this sort of fashion: (define-generics some-interface (fun-one some-interface) (fun-two some-interface))

(struct A () #:methods gen:some-interface [(define (fun-one a) “called fun one”)])

(struct B A () #:methods gen:some-interface [(define (fun-two a) “called fun two”)])


sydney.lambda
2019-5-22 20:42:49

and I expected (erroneously) that a B would use A’s fun-one, and the B fun-two. Same goes for the case in which the method is defined on both, and would use the most specific/recent implementation. That sort of thing. If the substruct doesn’t have its own #:methods, it does indeed use the parent’s definitions when called without producing an error.


sydney.lambda
2019-5-22 20:43:32

I’m actually not at all experienced with OOP languages, so I’m unsure what to “expect” either way I suppose you could say.


sydney.lambda
2019-5-22 20:45:08

as jerome stated, I just fancied a way of having functions specialised based on the type/s of the argument/s. A lot of my functions started as cond-ladders checking the input types, which I then moved into dispatcher functions based on alists of (type . procedure) pairs (ala SICP). When I saw generics I thought “oh, so I’m reinventing the wheel as usual!” Any advice on how others tend to use this functionality (there isn’t much Racket-specific material online) would be fantastic and very much appreciated.


soegaard2
2019-5-22 20:46:03

@asumu ^


sydney.lambda
2019-5-22 21:08:34

I also tried using #:defaults (#:fallbacks worked, iirc) but couldn’t figure out how to resolve the fact that the generic needed the predicate (B?) to be defined (and thus the struct) and the struct of course needed the generic to be already defined. Probably obvious, but I kind of gave up assuming it was a sign I was doing something unnatural in the first place. Apologies for the wall of text! I’m not well-versed in traditional OOP so that’s my (lackluster) excuse :wink:

What I had envisioned was just a way of being able to say: (defmethod (fun-name arg1 …) #:predicate (B?)). For any predicate, and when called with any argument would just check the predicate (or a list of predicated to decide resolution/defaults order?) to decide which implementation of that function to call. As a replacement for explicit dispatch functions and procedure tables. I was happy just using a-lists/dicts as opposed to struct for my entities in honesty. Hopefully when I become proficient with macros I can try something like that.


greg
2019-5-22 21:33:50

@sydney.lambda Ah I see. I’m not sure how to “untie” that mutual definition. (Maybe using delay and force? idk. Maybe @asumu knows.) > I was happy just using a-lists/dicts as opposed to struct for my entities in honesty. Doing an explicit data-driven dispatch with a dictionary like that seems fine to me, for many purposes.


diallo.ms
2019-5-22 21:36:18

@soegaard2 Thank you ! I had not realized the order of the arguments in (lambda (l bst)…) was this important. It’s only right now that the pieces are coming together: the composition between the way foldr works and the application of the lambda.


soegaard2
2019-5-22 21:37:46

Think of the (lambda (l bst)...) as equivalent to (cons element data-structure)


soegaard2
2019-5-22 21:37:56

Same order.


diallo.ms
2019-5-22 21:38:16

ok !


sydney.lambda
2019-5-22 21:44:44

Thanks @greg :slightly_smiling_face: It’s too easy to doubt yourself sometimes when you’re a new programmer. Think I’ll stick with the dict/dispatch approach after all!


rokitna
2019-5-22 22:22:47

It sounds like you might get something like what you were looking for if you put fun-one and fun-two in two separate generic interfaces. That A could implement both interfaces or maybe just the fun-one interface, and B could implement just the fun-two interface (and inherit the fun-one implementation).


sydney.lambda
2019-5-22 23:32:47

@rokitna that does indeed work! Thank you :slightly_smiling_face:


rokitna
2019-5-22 23:34:25

ooh, good to hear :)


rokitna
2019-5-22 23:34:57

whoops, I wrote “That” in a place where I meant “Then”


sydney.lambda
2019-5-22 23:42:11

For posterity, the original use case I was trying to get this to work for was akin to (for a dragon-warrior clone)

(define-generic fightable (get-input fightable) (input->action fightable))

(struct combatant (name health strength status)) ;just so the inheriting structs have those fields

(struct monster combatant () #:methods gen:fightable [(define (get-input monster) (random))])

(struct slime monster () #:methods gen:fightable [(define (input->action slime random-val) (use-val-to-choose-slimey-action))])

(struct player combatant () #:methods gen:fightable [(define (get-input player) (read))])


sydney.lambda
2019-5-22 23:43:47

Hopefully that sort of makes sense. I’m getting the feeling maybe generics are more intended to be really specific, almost single-method things? Perhaps gen:monster, gen:slime (and co.) and gen:player. This is something I’ll learn over time I suppose, I’ve focused almost entirely on “functional” concepts thus far so classes/inheritance/methods/interfaces are still very new to me. For some reason doing this with dicts and dispatch tables doesn’t come out feeling (or sounding) as strange, for some reason. Thanks to everyone for the help, it is very much appreciated :slightly_smiling_face:


notjack
2019-5-22 23:52:07

@sydney.lambda This gives me Entity-Component-System vibes, especially since you’re using it for game engine logic https://en.m.wikipedia.org/wiki/Entity_component_system


notjack
2019-5-22 23:52:52

May or may not apply to your case but seems related


sydney.lambda
2019-5-22 23:56:16

Thanks for the link. Just from an initial look-over, some parts seem to be describing what I was attempting to do with the structs/dispatch method before trying generics.


sydney.lambda
2019-5-23 00:00:13

I’m admittedly quite embarrassed that, after reading about the perils of inheritance on several occasions, and being drawn towards a non OO paradigm in the first place, that I’m complaining about struggling to get something to work because of inheritance !


notjack
2019-5-23 00:06:05

Not at all a reason for embarrassment :) it’s tricky