Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[><]: Extend >< to map input sequence. #64

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

NoahStoryM
Copy link
Collaborator

@NoahStoryM NoahStoryM commented Aug 14, 2022

Summary of Changes

With this change, we can define meru-step in this way:

(define-flow meru-step
  (~> (>< (-< _ _)) 
      (-< 0 _ 0)
      (>< (esc (procedure-reduce-arity + 2)))))

And I think it's useful in some cases:

(~> ('k1 "v1" 'k2 "v2") (>< cons) ▽ make-immutable-hash)  ; '#hash((k1 . "v1") (k2 . "v2"))

Public Domain Dedication

  • In contributing, I relinquish any copyright claims on my contribution and freely release it into the public domain in the simple hope that it will provide value.

(Why: The freely released, copyright-free work in this repository represents an investment in a better way of doing things called attribution-based economics. Attribution-based economics is based on the simple idea that we gain more by giving more, not by holding on to things that, truly, we could only create because we, in our turn, received from others. As it turns out, an economic system based on attribution -- where those who give more are more empowered -- is significantly more efficient than capitalism while also being stable and fair (unlike capitalism, on both counts), giving it transformative power to elevate the human condition and address the problems that face us today along with a host of others that have been intractable since the beginning. You can help make this a reality by releasing your work in the same way -- freely into the public domain in the simple hope of providing value. Learn more about attribution-based economics at drym.org, tell your friends, do your part.)

@countvajhula
Copy link
Collaborator

Interesting. I was concerned that something like (~> (1 2 3 4) (>< +)) would produce 10, but it produces 1,2,3,4 as intended -- I guess that's because it uses the "least" supported arity. That's great 🙂

Out of curiosity, have you checked the performance of the new version relative to the original? You can run racket profile/forms.rkt -f amp (which you can also "look up" using make profile-selected-form) to get a benchmark value in milliseconds.

The current idiomatic approach to this would be to explicitly group inputs in passing them to flows, e.g. using forms like group or bundle to control exactly which inputs go to which flows. So we typically pass either no inputs, exactly one input, or all inputs -- in other words, arity as an idea isn't really modeled in the language aside from these bounding cases.

I like that the proposed behavior is consistent with existing uses of >< (and all existing tests pass), while generalizing to new, natural, cases and that makes it compelling as a possible addition to the language. But as it introduces a new idea (i.e. arity modeling), I think it warrants spending more time to consider the implications and alternatives, and what may be the best way to introduce such functionality to the language.

The (>< cons) example is neat. Using the current idiomatic approach with the possible collate form from #62 , it could be:

(~> ('k1 "v1" 'k2 "v2") (collate [2 cons] [2 cons]) ▽ make-immutable-hash)

Or potentially a variant like:

(~> ('k1 "v1" 'k2 "v2") (group* 2 cons) ▽ make-immutable-hash)

These would not require arity modeling and could be done using the existing "none, all, or one" paradigm. But they aren't as nice as:

(~> ('k1 "v1" 'k2 "v2") (>< cons) ▽ make-immutable-hash)

The meru-step example too, is nice, which can be seen even more clearly here:

(define add (procedure-reduce-arity + 2))
(define-flow meru-step
  (~> (>< (-< _ _)) 
      (-< 0 _ 0)
      (>< add)))

Are there cases where use of an arity-aware >< could be less intuitive or possibly surprising? What if there is a function of unknown arity -- could the behavior in such a case surprise the user or present any challenges? Arguably, in the meru example, if the user didn't know that add is binary, then there would be some element of guesswork as to what this code is doing. To see this possibility more clearly:

(define-flow meru-step
  (~> (>< (-< _ _)) 
      (-< 0 _ 0)
      (>< func)))

Here, the user would not immediately know whether it is every value generated from the intermediate step that is mapped under func or selected subsets of some unknown size n. They would need to read the definition of func in order to understand this code. On the other hand, in the present state of affairs where >< always funnels single inputs, the code here can be understood without knowing the details of func.

So there are a few options to consider:

  1. Should this augment the existing >< form?
  2. Should it be a new form, e.g. ><*? (Just an example, and we'd have to agree on naming conventions to avoid overloading symbols like * with more than one meaning. But ><* seems consistent with the meaning of * in ==*)
  3. Should it be provided in a separate library?

The most conservative but also most flexible option here is (3). It is flexible because it is not in conflict with (1) or (2). As a point of comparison, many utilities present in the Racket core originally began as separate libraries (I think a lot of racket/function was originally a library -- but I don't recall where I got this impression). Another benefit of (3) is that it would be the least friction for you, and you can iterate on this and related arity-like features without needing to wait on integration into the Qi core. We can also add a new section to the Qi docs to promote libraries in the ecosystem, so that these packages would be discoverable. And over time, some of these libraries (perhaps the present proposal) could be incorporated into the core language. If you decide to go with this third option I can help you set up a package on the Racket package index when you get to that stage, if you haven't done that before.

Wdyt?

@NoahStoryM
Copy link
Collaborator Author

NoahStoryM commented Aug 23, 2022

Sorry for the late reply.

If you decide to go with this third option I can help you set up a package on the Racket package index when you get to that stage, if you haven't done that before.

3rd option looks good to me! I also plan to separate a library to study whether some constructs in category theory are useful for Qi. :)

By the way, the tutorial for covalues is basically completed (see learn-qi-cat.rkt ). I'll set up Qi+ after extending ==* and finishing documents.

Out of curiosity, have you checked the performance of the new version relative to the original? You can run racket profile/forms.rkt -f amp (which you can also "look up" using make profile-selected-form) to get a benchmark value in milliseconds.

Ah, I thought that the initial commit didn't change the complexity, so I didn't notice performance. split-at and apply are more expensive than I thought. The latest commit should fix the performance issue.

Original:

$ racket profile/forms.rkt -f amp
amp: 324 ms

Latest version:

$ racket profile/forms.rkt -f amp
amp: 327 ms

What if there is a function of unknown arity -- could the behavior in such a case surprise the user or present any challenges?

I don't think there will be a situation where the users are not sure about the arity of a function. After all, why use a function if they are not sure about the meaning of it?

Moreover, the implementation of covalues is dependent on coarity. Qi+ requires programmers to know the coarity information of procedures they use. From this perspective, knowing the arity information may not be a burden.

Should it be a new form, e.g. ><? (Just an example, and we'd have to agree on naming conventions to avoid overloading symbols like * with more than one meaning. But >< seems consistent with the meaning of * in ==*)

There is basically no performance gap between the extended >< and the original ><, so I guess it's not necessary to add a new form ><*.

In addition, I hope each form that handles Values has its duality -- the form that handles Covalues. In Qi+, ==*'s duality is ==+ -- * means product (values), + means sum (covalues). Maybe <>+ is also needed if adding ><*, but they increase the complexity of language.

@countvajhula
Copy link
Collaborator

@NoahStoryM FYI I created a wiki page with some suggested guidelines for writing libraries for Qi. Feedback welcome 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants