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

Add ∘[₁] as aliases for compose[1] #5115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

NoahStoryM
Copy link
Contributor

Checklist
  • Bugfix
  • Feature
  • tests included
  • documentation

Description of change

When implementing mathematical functions in Racket, I often define the alias manually: (define ∘ compose), to represent composed functions like g∘f as (∘ g f). This repetitive task occurs so frequently that I believe it makes sense for Racket to natively support .

@jackfirth
Copy link
Contributor

I'm not sure it's worth it to add this alias to Racket given that Racket doesn't have infix operators, and (∘ g f) seems more confusing to me than either (compose g f) or g ∘ f. Perhaps such an addition would make more sense for Rhombus? There it could be a real operator.

@NoahStoryM
Copy link
Contributor Author

When implementing mathematical functions in Racket, especially in algebra-related contexts, I find that using (∘ g f) feels quite harmonious within the surrounding expressions. Additionally, I've noticed that S-expressions can unexpectedly complement the representation of certain algebraic operations, such as those found in category theory.

As someone currently writing documentation on category theory using Racket, I've experienced firsthand how well S-expressions work for describing these concepts. In this context, having as an alias for compose not only aligns with mathematical notation but also enhances readability and consistency.

@jackfirth
Copy link
Contributor

That sounds like it ought to be part of a third-party library focused on category theory then.

@NoahStoryM
Copy link
Contributor Author

Actually, the Racket-based documentation on category theory I’m working on only uses features provided by #lang racket and doesn’t rely on any additional libraries. In a sense, this represents an alternative perspective on programming that doesn't depend on a specific third-party library.

From this perspective, procedures play a central role, and composition is fundamental to category theory. As a result, procedure composition—Racket's compose—is used extensively. I hope that programmers adopting this viewpoint should not need to depend on an external library just to use such a basic and essential construct. Instead, they should be able to directly leverage what Racket already provides.

@rfindler
Copy link
Member

Does this addition cause any code on the package server to fail to compile?

Does it perhaps make sense to export these from racket/function (which means that #lang racket programs would have them)?

@NoahStoryM
Copy link
Contributor Author

Does this addition cause any code on the package server to fail to compile?

I searched for this symbol. The results show that four packages provide , including my ctp package. Three use as an alias for compose, and the fourth is also related to compose.

Conflicts would arise in my ctp package and @countvajhula 's relation/composition package, where documentation files import racket/base and conflict with the existing definition. These conflicts can be resolved by adding (except-in racket/base ∘) to the respective imports. In fact, both ctp and relation/composition already exclude some operators provided by racket/base, so it would be a straightforward adjustment to exclude as well.

Does it perhaps make sense to export these from racket/function (which means that #lang racket programs would have them)?

I don't have an issue with moving to racket/function. I just think it might seem a bit odd to place a simple renaming of a procedure already defined in racket/base into a separate library. That said, I'm completely open to this approach if it's preferred.

@Mehbark
Copy link

Mehbark commented Nov 25, 2024

Just as a data point with regards to a third-party library, Haskell has base-unicode-symbols. It even has ∘ for composition!

@soegaard
Copy link
Member

In my opinion t is not worth the trouble having ∘ be an alias compose.
First of all we in general don't have aliases (I can only think of lambda).
Second, there is a tradition for using full names.

Also, it is not enough to check the documentation for ∘.
Check at least all packages on the package server.

A quick search on Github reveal that multiple projects use ∘.
Some use it as short for compose but there are other use cases too.

https://github.com/search?q=language%3Aracket+%E2%88%98&type=code

@mflatt
Copy link
Member

mflatt commented Nov 25, 2024

I see lots of s in registered packages, too, including ctp, elle-lib, pch-toolkit, xlist, csc104 (and formica and subtemplate, although those appear to have preexisting conditions). @NoahStoryM, does adding break (your) ctp package, or do you have to change the package? Can you try other packages to see whether the addition causes problems? @soegaard 's search may be more exhaustive.

It's possible that adding to racket/base works in practice while adding to racket/list doesn't because, requires can shadow bindings from the #lang line. In any case, our policy is not to add something if it creates a conflict with a registered package. Unfortunately, there's not an easy way to know without just trying packages.

@NoahStoryM
Copy link
Contributor Author

In my opinion t is not worth the trouble having ∘ be an alias compose. First of all we in general don't have aliases (I can only think of lambda). Second, there is a tradition for using full names.

In my experience, using full names for some operations can be very confusing, especially when they are used extensively in the same expression, making it hard to grasp the main idea. For example:

((⋅ (∘ T μ) (∘ λ S) (∘ S λ)) (∘))

If written with full names, it becomes:

((vertical-compose (horizontal-compose T μ) (horizontal-compose λ S) (horizontal-compose S λ)) (horizontal-compose))

This example uses just two types of composition operations. If programming in the style of Cartesian Closed Categories (CCC), more operators would be introduced (e.g., (co)pairing, (co)product, bang from/to, etc.). Insisting on full names for all of these operations would make the code cluttered and hard to read, as the screen would be filled with long names, obscuring the actual computation.

In general programming, function names tend to be the longest identifiers, which usually isn't a problem because those paradigms focus on nested function calls. Proper formatting and indentation can make such code readable. However, in a composition-centered style, such as CCC-style programming, there are rarely other variable names, and expressions are almost entirely composed of functions. In this context, using full names for functions drastically reduces readability, making it difficult to understand the structure and intent of the code.

On a related note, I used to avoid Unicode in programming, but I've come to appreciate its advantages. There are several reasons for this:

  • It aligns better with mathematical notation, making code concise and easier to read.

  • As a Chinese, I often find it challenging to express certain concepts in English. Using Chinese directly feels more natural and, in my experience, looks better in S-expressions than in other programming languages, perhaps because the syntax is expressed through parentheses rather than keywords.

  • Unicode allows for more expressive and consistent naming. If I recall correctly, Racket even supports using images as variable names, so encouraging the use of Unicode seems entirely reasonable.

While Racket does not traditionally rely on Unicode, and programmers can always define their own aliases for such operations, I believe there are common functions with well-established Unicode symbols (e.g., ∘ for composition). Not using these symbols where they are appropriate feels counterintuitive and inconsistent. Adding them as built-in aliases would better reflect their mathematical origins and make such code more approachable for users familiar with these conventions.

@NoahStoryM
Copy link
Contributor Author

I see lots of ∘s in registered packages, too, including ctp, elle-lib, pch-toolkit, xlist, csc104 (and formica and subtemplate, although those appear to have preexisting conditions). @NoahStoryM, does adding ∘ break (your) ctp package, or do you have to change the package? Can you try other packages to see whether the addition causes problems? @soegaard 's search may be more exhaustive.

The ∘ (and other operators) provided in my ctp package is mainly intended to highlight example code in the documentation and provide hyperlinks to corresponding term definitions. It's not designed to encourage programmers to (require ctp) in their actual code. Therefore, this addition would only affect the documentation part of the package.

I'll test the mentioned libraries individually on GitHub to check whether this addition causes any issues and report back with the results.

@sorawee
Copy link
Collaborator

sorawee commented Nov 26, 2024

Personally, I also don't think it's worth adding the unicode symbol to racket/base.

Besides reasons that many people here have mentioned, there's another one: having a symbol for compose would appear to give an "approval" that compose is the right function to use. But in fact, it depends on the context. compose is very general, because it needs to deal with multiple values, and that hampers its performance. When the computation only involves single value, compose1 is a better function to use. One solution would be to have both and ∘1, but I dislike ∘1 even more...

@NoahStoryM
Copy link
Contributor Author

NoahStoryM commented Nov 26, 2024

I searched for files containing the ∘ symbol and found 60 files on GitHub. After excluding cases where ∘ is used in strings, I identified 16 repositories where ∘ is defined as a compose alias or is related to composition:

SuzanneSoy/phc-toolkit
SuzanneSoy/phc-ts
SuzanneSoy/phc-adt
SuzanneSoy/subtemplate
SuzanneSoy/xlist
tonyg/racket-something
samsergey/formica
Misc-Lisp-Scripts
rcherrueau/APE
aiwen324/Courses
ovidiugabriel/lwfront
jirkamarsik/ling-eff
tail-reversion/elle
dvanhorn/dpc
countvajhula/relation
noahstorym/ctp

Additionally, I found 4 repositories where I'm not sure if the use of ∘ is related to composition, and none of these would conflict with the proposed addition:

plum-umd/parsing-with-derivatives
rmculpepper/racket-derp
stereobooster/derp
deeglaze/concrete-summaries

So far, I've identified two scenarios where introducing ∘ could cause issues (both involve repositories that treat ∘ as a compose alias):

  1. (require (rename-in racket/base [compose ∘])) in .rkt files
> (require (rename-in racket/base [compose compose]))
> (require (rename-in racket/base [compose ∘]))
string:1:20: rename-in: identifier `∘' already in nested require spec
  at: racket/base
  in: (rename-in racket/base (compose ∘))
 [,bt for context]
> (require (rename-in racket/base [compose c] [compose c]))
string:1:53: rename-in: duplicate identifier
  at: c
  in: (rename-in racket/base (compose c) (compose c))
 [,bt for context]

I was surprised that this raises an error. I'm not sure this is the intended behavior of Racket.

  1. (require (for-label racket/base collection)) in .scrbl files
    Here, the conflict arises when collection provides ∘, causing a naming conflict in Scribble documentation.

I'm still in the process of testing these repositories to identify other potential issues and will provide updates as I proceed.

@NoahStoryM
Copy link
Contributor Author

NoahStoryM commented Nov 26, 2024

I tested the repositories and encountered failures in the following (For some repos I wasn't sure how to test, so I simply ran the files containing ∘) :

[FAIL] samsergey/formica                           ;; due to `unstable-list-lib/unstable/contract.rkt` is missing
[FAIL] tail-reversion/elle                         ;; due to some files in this repo are missing
[FAIL] SuzanneSoy/phc-adt                          ;; due to SuzanneSoy/xlist
[FAIL] rcherrueau/APE/racket/CPN98/definitions.rkt ;; due to `(require (for-syntax racket/base "utils.rkt"))`, in "utils.rkt": `(define ∘ compose1)`
[FAIL] SuzanneSoy/xlist                            ;; due to `(require (rename-in racket/base [compose ∘]))`
[FAIL] countvajhula/relation/relation-doc          ;; due to `(require (for-label racket/base collection))`
[FAIL] noahstorym/ctp                              ;; due to `(require (for-label racket/base collection))`

After excluding repositories that failed due to unrelated issues, the conflicts caused by introducing ∘ are easily resolvable.

@countvajhula
Copy link
Contributor

Another point of reference: Haskell provides . for this purpose, out of the box.

My 2c: function composition is basic enough to warrant a symbolic operator, though unlike +, there may be diverging preferences on what this operator should be (e.g. Qi's ~> for left-to-right vs right-to-left composition, or even Haskell's . to avoid unicode). Yet, ∘ is standard in math so it makes the strongest case for inclusion. If this is added, I'd favor ∘ and ∘₁ (rather than ∘1).

@NoahStoryM
Copy link
Contributor Author

If this is added, I'd favor ∘ and ∘₁ (rather than ∘1).

I like ∘₁. Thank you for pointing this out.

@NoahStoryM NoahStoryM changed the title Add ∘[1] as aliases for compose[1] Add ∘[₁] as aliases for compose[1] Nov 27, 2024
@mflatt
Copy link
Member

mflatt commented Dec 3, 2024

After excluding repositories that failed due to unrelated issues, the conflicts caused by introducing ∘ are easily resolvable

Unfortunately, the question is not whether it would be easy to adjust a package, but whether we force package authors to make a change and/or just have broken packages. Our policy is to avoid adding exports that break packages.

On occasion, an addition has seemed important enough that we worked with package authors to change (or commit to changing) packages in advance, and then we could proceed with an addition. I think those cases involved a smaller number of packages, though.

If it seems worthwhile chasing down package authors and getting things changed in a way that will allow the addition of , then give it a try. Otherwise, though, I think we can't add .

@NoahStoryM
Copy link
Contributor Author

Thank you for the clarification. I've analyzed the causes of failure in the affected packages and have devised solutions for each case.

For example:

  • (require (rename-in racket/base [compose ∘])) can be replaced with (define ∘ compose) to avoid conflicts.

    By the way, is the conflict the intended behavior of Racket? It feels somewhat counterintuitive, as renaming [compose compose] doesn't cause a conflict, while [compose ∘] does. I'm curious if this is by design:

    > (require (rename-in racket/base [compose compose]))
    > (require (rename-in racket/base [compose ∘]))
    string:1:20: rename-in: identifier `∘' already in nested require spec
      at: racket/base
      in: (rename-in racket/base (compose ∘))
     [,bt for context]
    > (require (rename-in racket/base [compose c] [compose c]))
    string:1:53: rename-in: duplicate identifier
      at: c
      in: (rename-in racket/base (compose c) (compose c))
     [,bt for context]
  • For (require (for-label racket/base collection)), I plan to resolve the conflict by encapsulating the for-label module with a custom module definition, such as:

    (module for-label racket/base
      (require collection)
      (provide (all-from-out racket/base collection)))
    (require (for-label 'for-label))

    I've already adopted this approach in ctp, and I haven't observed any issues so far.

I'll proceed to open PRs for the affected repositories with these changes.

@sorawee
Copy link
Collaborator

sorawee commented Dec 4, 2024

I think there are also a lot of other stuff to be done in addition to fixing conflicting packages. For example, DrRacket currently doesn't support ∘₁ well, showing the following dialog whenever the symbol is pasted to the editor.

Screenshot 2024-12-03 at 5 40 10 PM

If we really want to go ahead with this change, I believe improving the user experience for DrRacket should be a prerequisite.

And is there an easy way to input ∘₁ in DrRacket?

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.

8 participants