cperivol
2021-11-23 12:54:09

Is there a way to define (define-generics container (get-a-value container)) and then implement it for list, vector,hash, etc without wrapping them in structures?


cperivol
2021-11-23 12:55:16

(And also without enumerating all of them in #:defaults)


ryanc
2021-11-23 13:30:27

#:defaults (and #:fast-defaults) is the way to implement generics for existing data types. As to enumerating them all in the #:defaults clauses: well, you can just add an indirection instead, like this: (define other-container-alist (list (list pair? car) (list vector? (lambda (v) (vector-ref v 0))))) (define (other-container? v) (for/or ([entry other-container-alist]) ((car entry) v))) (define (other-container-get-a-value v) (define impl (for/or ([entry other-container-alist]) (if ((car entry) v) (cadr entry) #f))) (impl v)) (define-generics container [get-a-value container] #:defaults ([other-container? (define (get-a-value container) (other-container-get-a-value container))])) (get-a-value (list 1 2 3)) (get-a-value (vector 'a 'b 'c)) You can even add new implementations by mutating the association list variable.


cperivol
2021-11-23 14:49:10

thanks a lot!


cperivol
2021-11-23 15:01:30

Is there a way racket-y to define a generic that supports an empty element? I am trying to define a generic monoid that supports the unit element. At the moment I am depending on a paramemeter to give me an arbitrary value of the type I need in use but I don’t love this solution.


notjack
2021-11-23 18:33:33

No, there’s no good way to do that. Generic interfaces attach the method implementations to values, so if there’s no value, you can’t figure out what implementation to use. For monoids specifically I suggest reifying the monoid into a first-class object like so:

(struct monoid (operator identity)) (define (monoid-append m . xs) (for/fold ([result (monoid-identity m)]) ([x (in-list xs)]) ((monoid-operator m) result x))) (define sum-monoid (monoid + 0)) (define product-monoid (monoid * 1))


rokitna
2021-11-23 18:53:00

I don’t think there’s an idiomatic Racket approach to this problem.

One way or another, you can’t get a value of the monoid you want without somehow specifying the value or the monoid at the call site. (A monoid itself, at run time, would be an object with methods for getting the identity element and appending values.)

In a statically typed world, the way the return value is used can allow the language to infer its type at compile time, and that can be used to determine what monoid to use at compile time (through things like implicit arguments or type classes), which means the choice of monoid (or an identity determined from it) is already known by run time, when we can use it to compute the identity value.

Haskell type classes are also capable of solving for more complicated ways to make the appropriate type of monoid object from the ones available. For instance, they can pass in the Cartesian product of two known monoids automatically, if that makes a monoid of the right type.

Without static types, there are potentially a few options, but I think the general case of computing composite monoids still has to be done by hand. So part of the solution is to have explicit monoid objects that can be passed into and returned from functions to construct Cartesian products and such.

One option, potentially the most seamless with Racket’s abstraction techniques, is to just pass those monoid objects explicitly to anything that needs them. (This is known in Haskell as dictionary-passing style, and programs are elaborated into it in their Core representation.) This is the approach I’ve taken in a couple of my Racket libraries (Lathe Morphisms and Punctaffy).

That extra argument can be verbose. Personally, I’ve found that since I’m often computing it using explicit operations (like that Cartesian product example) for each thing I pass it to, there isn’t enough in common between one call site and another for me to use implicit arguments of any kind. Instead, I just put my type class dictionary objects into variables that have terse names, and most of my functions begin with a section that computes all the dictionaries I’ll be passing around in the main body.

If you do want to try to make things more implicit, parameters are a very Rackety approach to doing so. However, they’re a form of dynamic scope; they effectively let information propagate implicitly through every single function call, even ones that wouldn’t intend to propagate it like that.

I feel like lexical scope is probably more reliable. There is a Rackety approach to lexically scoped implicit arguments, but it seems to be rare for libraries to use it. Namely, operations like #%app and #%datum are known as interposition points, and they can be customized for a piece of code in a lexically scoped way, by using (let-syntax ([#%app ...]) ...) and such. A library’s macros could potentially be customizable according to additional interposition points. To do this, the macros would need to break hygiene for those identifiers, using something like (syntax-local-introduce #'#%monoid). I’ve considered adding something like this to Lathe Morphisms’s approach at some point, especially if it’s in support of something that’s a more pervasive DSL-like syntax, like applicative functor idiom brackets or monadic do notation.

Another Rackety option for implicit arguments is the use of syntax parameters. These are sort of a less potent kind of dynamic scope than regular parameters: Functions don’t automatically propagate them from their call sites to their bodies, but macros do.

Racket libraries use parameters or syntax parameters in many of the places I would probably prefer they use interposition points. :-p A downside of interposition points is that if someone doesn’t like the name of an interposition point like #%app, they can’t control that name by putting rename-in in a require form. I’ve considered addressing that by using a single interposition point that contains a collection of bindings for others; that way there’s at most one regrettable name that can’t be renamed, not a new one for each use of the technique.


rokitna
2021-11-23 18:53:52

replied in thread :)


cperivol
2021-11-23 19:30:43

Nice! thanks!


cperivol
2021-11-23 19:30:51

Much appreciated


rokitna
2021-11-23 20:28:42

Oh, another option for propagating arguments in a terser way is to use units. https://docs.racket-lang.org/reference/mzlib_unit.html\|https://docs.racket-lang.org/reference/mzlib_unit.html

That way you’re basically importing a set of functions that are all specialized to some choice of monoid.


phdumaresq
2021-11-24 02:57:21

Using Raco, is there a way to get a list of all the installed packages returned to you just as a list with each package on one line or something? raco pkg show returns it in a way that’s a little tougher to parse which isn’t great for automation.


phdumaresq
2021-11-24 02:58:37

Same with raco pkg show <name> - which doesn’t even signal an error to the shell if it failed, which makes detecting if a package is installed or not tougher than it should be.


shu--hung
2021-11-24 05:47:16

Can you call the function installed-pkg-names?