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

Generic functions, not aliases, for unicode operators in LinearAlgebra #37109

Open
epatters opened this issue Aug 18, 2020 · 10 comments
Open

Generic functions, not aliases, for unicode operators in LinearAlgebra #37109

epatters opened this issue Aug 18, 2020 · 10 comments
Labels

Comments

@epatters
Copy link

epatters commented Aug 18, 2020

The LinearAlgebra module exports two unicode operators, like this:

const  = dot
const × = cross

Would it be possible to change these to generic functions?

(u,v) = dot(u,v)
×(u,v) = cross(u,v)

The reason is that \cdot does not always have the interpretation of a dot product. For example, in Catlab we use it as a generic composition operator. Likewise, \times is not always a cross product; in many contexts it conventionally represents the cartesian product of sets or types. By using generic functions instead of aliases, it would at least be possible to override these operators for specific types when a different interpretation makes sense.

It would be better still to have typing, e.g.

×(u::AbstractVector, v::AbstractVector) = cross(u,v)
@jpfairbanks
Copy link
Contributor

This would be great, I imagine that some future database package might want to use \times for the cartesian product of sets which is called CROSS JOIN in SQL. Or a topology package using \times for the cartesian product of spaces.

@StefanKarpinski
Copy link
Sponsor Member

If you're using the operator with a different meaning you should use a different generic function. The and × that are exported from Base mean dot product and cross product; of you want to use the same names with a different meaning, that should be a different generic function. This article has a more in depth explanation of this: https://ahsmart.com/pub/the-meaning-of-functions/.

@jpfairbanks
Copy link
Contributor

I guess our real problem is that we need a unicode symbol for cartesian product. Is there another unicode symbol that would work for cartesian product? On the other hand, we could just call the cartesian product of sets cross(A::Set,B::Set). We need an ASCII friendly name for it and what else could cross(A::Set,B::Set) mean?

@StefanKarpinski
Copy link
Sponsor Member

You can use the same symbol and just give it a new definition.

@goretkin
Copy link
Contributor

goretkin commented Aug 19, 2020

If instead of a unicode collision with ×, you had an ASCII collision with cross, you'd want to make sure you did not define LinearAlgebra.cross but instead e.g. MyModule.cross.

It's the same with unicode identifiers.

A problem might arise when you import packages:

julia> cross(a, b) = "a × b" 
cross (generic function with 1 method)

julia> using LinearAlgebra
WARNING: using LinearAlgebra.cross in module Main conflicts with an existing identifier.

julia> cross([1,0,0],[0,1,0])
"a × b"

julia> LinearAlgebra.cross([1,0,0],[0,1,0])
3-element Array{Int64,1}:
 0
 0
 1

You might be up against wanting to export the symbol, and have it cooperate with some of the names in LinearAlgebra, but not cross. In that case, you can explicitly import specific names from LinearAlgebra)

To me it seems that the cartesian product of two sets has nothing to do with the vector cross product (though if ever I were to feel nervous making that claim, I think it's in front of those who know a lot of category theory...) if not for the name / symbols. And so they should get different (fully-qualified) names in Julia. To use the identifier "cross" both uses, you can separate them by namespace.

@cscherrer
Copy link

If you're using the operator with a different meaning you should use a different generic function. The and × that are exported from Base mean dot product and cross product; of you want to use the same names with a different meaning, that should be a different generic function.

I think it's clear that this is the current setup. But × in particular can mean many different things, and relative to the broader community "cross product in three dimensions" is a relatively niche usage. For example, it would seem reasonable for Base to have

×(a::Set, b::Set) = Set(zip(a, b))

For package developers, the current × gives some limitations. We can add methods to cross, but that seems strange. Or we can define a new ×, but then every end user will have to jump through name qualification hoops to avoid the LinearAlgebra name collision.

@goretkin
Copy link
Contributor

goretkin commented Nov 8, 2021

For package developers, the current × gives some limitations.

I'm not sure what this means. What alternative do you see? The only one I see is to not export ×.

Everything that could be said about × or cross could also be said about length (the example in the article above), though I agree that length of an iterable collection is less niche than cross product of vectors.

@epatters
Copy link
Author

epatters commented Nov 8, 2021

I'm not sure what this means. What alternative do you see? The only one I see is to not export ×.

I assume the alternative Chad that has in mind is the one suggested in the OP: make the Unicode operators into generic functions that can be overloaded.

Everything that could be said about × or cross could also be said about length (the example in the article above), though I agree that length of an iterable collection is less niche than cross product of vectors.

This statement gets to the heart of the disagreement here. Stefan has clearly stated his philosophy of generic functions: that functions with different meanings should have different names. That may well be a good rule of thumb, but I do not think it should apply to Unicode operators for mathematical notation. It is a basic and ubiquitous feature of mathematical notation that it is overloaded in ways that the same symbol does not have the same meaning in all contexts. Two examples have already been given in this thread; we could come up with countless others. The wonderful thing about Julia is that multiple dispatch makes it easy to accommodate this context-dependence! So let's embrace that and make \times and other operators be generic functions that people can use for their own purposes.

@adkabo
Copy link
Contributor

adkabo commented Nov 9, 2021

Mixing the semantics of operators that happen to have the same name seems extremely fraught.

It is a basic and ubiquitous feature of mathematical notation that it is overloaded in ways that the same symbol does not have the same meaning in all contexts.

This is true -- but it's the notation that's overloaded, not the function. Cross product and Cartesian product may use the same symbol but they're not the same object.

Merging operations into one function un-solves the expression problem. a⨯b, where a and b are each an ordered collection of integers, could mean Cartesian product or cross product, but the user or library author can't easily choose which one they mean if they're merged into the same function.

Defining a separate function -- a new × (\times) function, or using one of the other unicode multiplication symbols, such as ⊗⊠✖⨰⨱⨴⨵⨶⨷⨻ , or defining a digraph like ×× [1] -- seems like a much better solution to me.


[1] Incidentally, ×× and ⋅⋅ are currently not allowed

julia> (××)(a::Set,b::Set) = Set(Iterators.product(a,b))
ERROR: syntax: "×" is not a unary operator

but I think they should be, barring some countervailing big problem. ++ is allowed.

@goretkin
Copy link
Contributor

goretkin commented Nov 9, 2021

The wonderful thing about Julia is that multiple dispatch makes it easy to accommodate this context-dependence!

I don't think that multiple dispatch handles this context dependence. If I write a function in terms of ×, then I already have decided the context. I cannot imagine a function in terms of × where it is correct for it to mean either "vector cross product" or "cartesian product".

I am reminded of a long thread that discusses C++'s "Argument Dependent Lookup". There might be room for a Julia feature that chooses the correct × function if there is no overlap between the types of arguments that each function operates on. But coalescing two different meanings into one function undermines generic programming. It is the same reason there isn't (at least not in Base. I think there is a package) a definition like /(a::String,b::String) = joinpath(a, b).

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

No branches or pull requests

7 participants