Zulip Chat Archive
Stream: new members
Topic: mul_right_comm_monoid
Damiano Testa (Apr 09 2021 at 13:08):
I could not find the instances below: am I missing some import? Does it make sense to PR it? Or should the solution be to have only a single cancel_comm_monoid
class, instead of cancel_left_comm_monoid
,cancel_right_comm_monoid
and cancel_comm_monoid
, with crossed instances relating them?
instance right_of_left {R : Type*} [left_cancel_comm_monoid R] : right_cancel_comm_monoid R :=
{ mul_right_cancel := λ a b c ac, begin
repeat { rw mul_comm _ b at ac},
exact mul_left_cancel ac,
end,
..(infer_instance : left_cancel_comm_monoid R) }
instance left_of_right {R : Type*} [right_cancel_comm_monoid R] : left_cancel_comm_monoid R :=
by apply_instance -- fails
Damiano Testa (Apr 09 2021 at 13:18):
Here are a bunch of (missing?/unneeded) instances:
variable {R : Type*}
instance right_of_left [left_cancel_comm_monoid R] : right_cancel_comm_monoid R :=
{ mul_right_cancel := λ a b c ac, by { repeat { rw mul_comm _ b at ac}, exact mul_left_cancel ac },
..(infer_instance : left_cancel_comm_monoid R) }
instance left_of_right [right_cancel_comm_monoid R] : left_cancel_comm_monoid R :=
{ mul_left_cancel := λ a b c bc, by { repeat { rw mul_comm a at bc}, exact mul_right_cancel bc },
..(infer_instance : right_cancel_comm_monoid R) }
instance cancel_of_left [left_cancel_comm_monoid R] : cancel_comm_monoid R :=
{ ..(infer_instance : left_cancel_comm_monoid R),
..(infer_instance : right_cancel_comm_monoid R) }
instance cancel_of_right [right_cancel_comm_monoid R] : cancel_comm_monoid R :=
{ ..(infer_instance : left_cancel_comm_monoid R),
..(infer_instance : right_cancel_comm_monoid R) }
Anne Baanen (Apr 09 2021 at 13:21):
docs#left_cancel_comm_monoid, for the lazy like me
Damiano Testa (Apr 09 2021 at 13:22):
docs#left_cancel_comm_monoid ← Sorry, simply my attempt at seeing how to generate the link!
Damiano Testa (Apr 09 2021 at 13:24):
Unless there is a reason for preferring these typeclasses, I would opt for having a single cancel_comm_monoid
typeclass, with "asymmetric" fields, choosing, say, mul_left_cancel
.
Damiano Testa (Apr 09 2021 at 13:25):
And, if such a thing exist, also cancel_semigroup
...
Anne Baanen (Apr 09 2021 at 13:25):
Looks like they were added in #3688 and we didn't realize left
+ comm
is exactly the same as right
+ comm
.
Damiano Testa (Apr 09 2021 at 13:25):
docs#left_cancel_comm_semigroup may not exist...
Anne Baanen (Apr 09 2021 at 13:26):
So yes, defining cancel_comm_monoid extends left_cancel_monoid, comm_monoid
and then proving right_cancel
as well, looks like the best option.
Anne Baanen (Apr 09 2021 at 13:26):
(i.e. provide instances cancel_comm_monoid.to_right_cancel_monoid
and cancel_comm_monoid.to_cancel_monoid
.)
Anne Baanen (Apr 09 2021 at 13:27):
Actually, judging from the number of instances, let's promote right_cancel_comm_monoid
to be the one true typeclass.
Damiano Testa (Apr 09 2021 at 13:27):
...and there are the add
versions as well.
Anne Baanen (Apr 09 2021 at 13:29):
Unfortunately @[to_additive]
can't autogenerate structures (like typeclasses), so we have to write out the class twice :(
Damiano Testa (Apr 09 2021 at 13:29):
for the left version, there is this comment in the code:
-- TODO: I found 1 (one) lemma assuming [add_left_cancel_monoid]
.
-- Should we port more lemmas to this typeclass?
I guess that I am going to be the one taking, at least in part, this to completion!
Damiano Testa (Apr 09 2021 at 13:29):
Anne, or rather, in this case, remove them twice! :upside_down:
Damiano Testa (Apr 09 2021 at 13:31):
Ok, so I am going to keep the right_comm
code, except that I remove the word right
from it. Then, I change the left_comm
classes to instance, correct?
Damiano Testa (Apr 09 2021 at 13:32):
Finally, I remove what is now comm
with no indication of left/right
.
From a procedural point of view, I would begin with removing the comm
[no left/right], then promote right_comm
to comm
only and finally instance the left
.
Anne Baanen (Apr 09 2021 at 13:32):
Sounds good. Don't we have docs#cancel_comm_monoid already?
Damiano Testa (Apr 09 2021 at 13:33):
Yes, but it extends both. Ah, I could make it extend only right, erase right and instance left. This is simpler, you are right!
Anne Baanen (Apr 09 2021 at 13:33):
So make sure that the "new" cancel_comm_monoid
has the same lemmas and instances as the "old" cancel_comm_monoid
.
Damiano Testa (Apr 09 2021 at 13:34):
I will get down to it! I am sure that lean will let me know if I mess something up...
Damiano Testa (Apr 09 2021 at 13:35):
What does ancestor
mean below?
@[protect_proj, ancestor add_left_cancel_comm_monoid add_right_cancel_comm_monoid]
class add_cancel_comm_monoid (M : Type u)
extends add_left_cancel_comm_monoid M, add_right_cancel_comm_monoid M
Should this become
@[protect_proj, ancestor add_right_cancel_comm_monoid]
class add_cancel_comm_monoid (M : Type u)
extends add_right_cancel_comm_monoid M
Anne Baanen (Apr 09 2021 at 13:38):
I am not exactly sure to be honest! I think protect_proj
will give the protected
attribute to the fields of the structure (so that cancel_comm_monoid.mul
doesn't become mul
when the cancel_comm_monoid
namespace is open).
Anne Baanen (Apr 09 2021 at 13:39):
And ancestor
is used by the subtype_instance
tactic to copy over fields, it looks like. So just copy over what comes after the extends
.
Anne Baanen (Apr 09 2021 at 13:40):
So add_cancel_comm_monoid
would look something like:
@[protect_proj, ancestor add_comm_monoid add_right_cancel_monoid]
class add_cancel_comm_monoid (M : Type u)
extends add_comm_monoid M, add_right_cancel_monoid M
Damiano Testa (Apr 09 2021 at 13:47):
Hence, the ancestor
thing will be "forgotten" in the instance
from cancel_comm
to cancel_left
, right?
Anne Baanen (Apr 09 2021 at 13:48):
Yes, it is just to keep track of which fields to copy and which ones to add when declaring a new instance via tactics.
Eric Wieser (Apr 09 2021 at 13:52):
Why not extend both but fill out the redundant axiom with opt_param?
Damiano Testa (Apr 09 2021 at 13:56):
What does opt_param mean?
Eric Wieser (Apr 09 2021 at 13:58):
I think src#preorder has an example
Eric Wieser (Apr 09 2021 at 13:59):
The (lt := ...)
parameter fills that field automatically
Damiano Testa (Apr 09 2021 at 13:59):
I had seen this and I remember having been happy that it "just worked". I guess that now is my time to make it "just work", right? :wink:
Damiano Testa (Apr 09 2021 at 14:01):
also, when I do this
/-- Commutative version of cancel_monoid. -/
@[protect_proj, ancestor right_cancel_monoid left_cancel_monoid comm_monoid, to_additive add_cancel_comm_monoid]
class cancel_comm_monoid (M : Type u) extends
left_cancel_monoid M, right_cancel_monoid M, comm_monoid M
class
complains:
Failed to map fields of cancel_comm_monoid
Damiano Testa (Apr 09 2021 at 14:02):
This disappears, though, if I remove the line with @
Damiano Testa (Apr 09 2021 at 14:12):
In fact, simply erasing to_additive add_cancel_comm_monoid
is enough to make the error go away.
Damiano Testa (Apr 09 2021 at 14:13):
Since the class just above cancel_comm_monoid
is add_cancel_comm_monoid
, maybe the to_additive
is not needed? I would have guessed that to_additive
would have created exactly the class that is defined above, right?
Damiano Testa (Apr 09 2021 at 14:13):
Ah, Anne said that to_additive
does not work with classes...
Damiano Testa (Apr 09 2021 at 14:14):
So, I probably need it, but I am not able to make it work.
Damiano Testa (Apr 09 2021 at 14:18):
Sorry about the noise: I think that I am making progress! Ignore the previous comments, for the moment!
Mario Carneiro (Apr 09 2021 at 14:29):
You need to add the to_additive
after the class
Damiano Testa (Apr 09 2021 at 14:30):
If the proof of an instance
is by apply_instance
, then that instance
is not needed, correct?
This means that I will want to prove instances [left_cancel] + [comm] => [cancel] + [comm]
and similarly for right
and then same again for add
.
Mario Carneiro (Apr 09 2021 at 14:30):
Those don't sound like they should be instances
Mario Carneiro (Apr 09 2021 at 14:30):
they will loop
Damiano Testa (Apr 09 2021 at 14:31):
Mario, right now, this works:
/-- Commutative version of add_cancel_monoid. -/
@[protect_proj, ancestor add_right_cancel_monoid add_left_cancel_monoid add_comm_monoid]
class add_cancel_comm_monoid (M : Type u) extends
add_right_cancel_monoid M, add_left_cancel_monoid M, add_comm_monoid M
/-- Commutative version of cancel_monoid. -/
@[protect_proj, ancestor right_cancel_monoid left_cancel_monoid comm_monoid,
to_additive add_cancel_comm_monoid]
class cancel_comm_monoid (M : Type u) extends
right_cancel_monoid M, left_cancel_monoid M, comm_monoid M
/-- An add_cancel_comm_monoid. -/
instance add_left_cancel_comm_monoid (M : Type u) [add_cancel_comm_monoid M] :
add_left_cancel_monoid M := by apply_instance
I imagine that the last instance can be removed. Is the rest looking good?
Mario Carneiro (Apr 09 2021 at 14:32):
Don't put to_additive
on the class definition
Eric Wieser (Apr 09 2021 at 14:32):
Why not on the class definition?
Damiano Testa (Apr 09 2021 at 14:32):
Ok, no instances [left_cancel] + [comm] => [cancel] + [comm]
. Should there be defs instead? Or simply not there at all?
Eric Wieser (Apr 09 2021 at 14:32):
Don't we do that everywhere?
Mario Carneiro (Apr 09 2021 at 14:32):
It has to be done after the definition of the class
Damiano Testa (Apr 09 2021 at 14:32):
I copied the to_additive
adapting what was already there. Still, I am happy to keep it or remove it, as needed-
Mario Carneiro (Apr 09 2021 at 14:33):
although maybe my info is out of date, it used to be a lot more annoying
Eric Wieser (Apr 09 2021 at 14:33):
Are you sure? I thought that's just because you can't put to_additive
on the mul
version until the add version is defined
Eric Wieser (Apr 09 2021 at 14:33):
And for some reason lots of files define the add version after the mul version
Mario Carneiro (Apr 09 2021 at 14:33):
/-- A `group` is a `monoid` with an operation `⁻¹` satisfying `a⁻¹ * a = 1`.
There is also a division operation `/` such that `a / b = a * b⁻¹`,
with a default so that `a / b = a * b⁻¹` holds by definition.
-/
@[protect_proj, ancestor div_inv_monoid]
class group (G : Type u) extends div_inv_monoid G :=
(mul_left_inv : ∀ a : G, a⁻¹ * a = 1)
/-- An `add_group` is an `add_monoid` with a unary `-` satisfying `-a + a = 0`.
There is also a binary operation `-` such that `a - b = a + -b`,
with a default so that `a - b = a + -b` holds by definition.
-/
@[protect_proj, ancestor sub_neg_monoid]
class add_group (A : Type u) extends sub_neg_monoid A :=
(add_left_neg : ∀ a : A, -a + a = 0)
attribute [to_additive] group
Mario Carneiro (Apr 09 2021 at 14:33):
like this
Eric Wieser (Apr 09 2021 at 14:34):
Yes, exactly - the attribute is only separate because the definitions are in the wrong order
Eric Wieser (Apr 09 2021 at 14:34):
My guess would be that
@[foo, bar]
class X
and
@[foo]
class X
attribute [bar] X
are indistinguishable to lean
Mario Carneiro (Apr 09 2021 at 14:36):
No, they have different effects, because in the first one bar
can act while X
is still being constructed, so it can influence the constructors and such
Mario Carneiro (Apr 09 2021 at 14:36):
for example @[class] structure foo := (a : nat)
vs structure foo := (a : nat) attribute [class] foo
have different types for foo.a
Damiano Testa (Apr 09 2021 at 14:40):
Ok, I feel like the dog playing cards of the memes... the conclusion is that I should put the to additive after the definition as an attribute, right?
Damiano Testa (Apr 09 2021 at 14:40):
Anne Baanen (Apr 09 2021 at 14:42):
I think so: define class add_cancel_comm_monoid
, then class cancel_comm_monoid
, then attribute [to_additive] cancel_comm_monoid
.
Damiano Testa (Apr 09 2021 at 14:43):
While I am at it, should the second be the preferred form, then?
@[protect_proj, ancestor left_cancel_semigroup monoid, to_additive add_left_cancel_monoid]
class left_cancel_monoid (M : Type u) extends left_cancel_semigroup M, monoid M
--better as follows than above?
@[protect_proj, ancestor left_cancel_semigroup monoid]
class left_cancel_monoid (M : Type u) extends left_cancel_semigroup M, monoid M
attribute [to_additive] add_left_cancel_monoid
Damiano Testa (Apr 09 2021 at 15:00):
PR #7134
Damiano Testa (Apr 09 2021 at 15:01):
It already has an error. I will try to fix it, but may need further assistance. Sorry for opening this can of worms and not being able to clean up my own mess...
Damiano Testa (Apr 09 2021 at 15:11):
Given my very superficial understanding of the discussion above, what is below seem to be in the wrong order: should the two definitions be swapped around?
/-- A commutative monoid is a monoid with commutative `(*)`. -/
@[protect_proj, ancestor monoid comm_semigroup]
class comm_monoid (M : Type u) extends monoid M, comm_semigroup M
/-- An additive commutative monoid is an additive monoid with commutative `(+)`. -/
@[protect_proj, ancestor add_monoid add_comm_semigroup]
class add_comm_monoid (M : Type u) extends add_monoid M, add_comm_semigroup M
attribute [to_additive] comm_monoid
Damiano Testa (Apr 09 2021 at 15:12):
(This is the order in which they appear in the file algebra/group/defs
.)
Eric Wieser (Apr 09 2021 at 15:13):
attribute [to_additive] add_left_cancel_monoid
means "add_left_cancel_monoid
is the multiplicative version of some additive lemma"
Eric Wieser (Apr 09 2021 at 15:13):
Which is obviously not what you meant!
Eric Wieser (Apr 09 2021 at 15:13):
That attribute is only supposed to go on multiplicative things
Eric Wieser (Apr 09 2021 at 15:14):
I think you probably meant attribute [to_additive add_left_cancel_monoid] left_cancel_monoid
Damiano Testa (Apr 09 2021 at 15:14):
Ok, but I think that I did not change this: this is what already was in the file before I touched it!
(Let me check that this is really true!)
Eric Wieser (Apr 09 2021 at 15:14):
(your claim is not really true!)
Eric Wieser (Apr 09 2021 at 15:15):
@[to_additive x] def y
means "x is the additive version of y"
Eric Wieser (Apr 09 2021 at 15:15):
Usually you can omit x
and lean works it out from the name
Damiano Testa (Apr 09 2021 at 15:17):
Ok, I will change this, but looking at the file in git, I do not think that I wrote this. Still, I am happy to change it! :smile:
Damiano Testa (Apr 09 2021 at 15:20):
Is this the correct form, then?
/-- An additive commutative monoid is an additive monoid with commutative `(+)`. -/
@[protect_proj, ancestor add_monoid add_comm_semigroup]
class add_comm_monoid (M : Type u) extends add_monoid M, add_comm_semigroup M
/-- A commutative monoid is a monoid with commutative `(*)`. -/
@[protect_proj, ancestor monoid comm_semigroup]
class comm_monoid (M : Type u) extends monoid M, comm_semigroup M
attribute [to_additive] add_comm_monoid
Eric Wieser (Apr 09 2021 at 15:24):
No!
Eric Wieser (Apr 09 2021 at 15:25):
You've told lean that add_comm_monoid
is the multiplicative version
Damiano Testa (Apr 09 2021 at 15:28):
I'm sorry, I am trying to understand and keep everything straight, but I am clearly failing. I will try to produce a better version!
Eric Wieser (Apr 09 2021 at 15:29):
That example above isn't copied from group/basic, is assume?
Damiano Testa (Apr 09 2021 at 15:29):
This works in this case, right? Or am I still confused?
/-- An additive commutative monoid is an additive monoid with commutative `(+)`. -/
@[protect_proj, ancestor add_monoid add_comm_semigroup]
class add_comm_monoid (M : Type u) extends add_monoid M, add_comm_semigroup M
/-- A commutative monoid is a monoid with commutative `(*)`. -/
@[protect_proj, ancestor monoid comm_semigroup]
class comm_monoid (M : Type u) extends monoid M, comm_semigroup M
attribute [to_additive] comm_monoid
Eric Wieser (Apr 09 2021 at 15:30):
Your last line is different between that snippet and the previous one
Eric Wieser (Apr 09 2021 at 15:30):
The most recent one is correct
Damiano Testa (Apr 09 2021 at 15:30):
The very first one was, the one with the comm_monoid before the `add_comm_monoid*.
Damiano Testa (Apr 09 2021 at 15:30):
Yes, I changed the last line, since I imagine that that would fix the issue...
Eric Wieser (Apr 09 2021 at 15:31):
That most recent one looks fine, and it doesn't care what order the two classes go in as long as the attribute is after both
Damiano Testa (Apr 09 2021 at 15:32):
Finally I did something right!
Damiano Testa (Apr 09 2021 at 15:33):
So, as a "generic rule", the name not inside the square brackets should not had add
, correct?
Damiano Testa (Apr 09 2021 at 15:35):
To be honest, I had completely missed the distinction between inside and outside the square brackets. I hope that I have it straight now.
Kevin Buzzard (Apr 09 2021 at 15:44):
@[to_additive x] def y
attaches the attribute to y
. attribute [to_additive] x
attaches the attribute to x
.
Kevin Buzzard (Apr 09 2021 at 15:45):
You either attach the tag when you're making the definition (e.g. @[simp] theorem blah...
) or you attach the tag afterwards (e.g. theorem blah...
and then later attribute [simp] blah
).
Damiano Testa (Apr 09 2021 at 15:46):
Ah, Kevin, one more "subtlety" that I had missed! Anyway, the PR has been building for over 5s now, so it must all be correct... :upside_down:
Kevin Buzzard (Apr 09 2021 at 15:47):
You can use #print blah
to see all the tags attached to the definition/theorem called blah
-- they appear at the very top of the output.
Damiano Testa (Apr 09 2021 at 15:48):
Also, from the discussion above, I gathered that @[to_additive]
and attribute [to_additive]
are not equivalent and the latter seems to be preferred.
Kevin Buzzard (Apr 09 2021 at 15:49):
I think they are equivalent. I think you're confused because the to_additive
attribute can take other inputs.
Kevin Buzzard (Apr 09 2021 at 15:50):
here is an example in mathlib where the to_additive attribute is actually taking an input -- a string, which is the docstring for the additive version. to_additive
is a very flexible attribute and I completely agree that there is right now no good place to read about how it works.
Damiano Testa (Apr 09 2021 at 15:50):
Kevin, this is what made me say what I did: honestly, I do not understand what the difference is/might be/isn't!
https://leanprover.zulipchat.com/#narrow/stream/113489-new-members/topic/mul_right_comm_monoid/near/233831681
Kevin Buzzard (Apr 09 2021 at 15:51):
The things you wrote above are equivalent, because you didn't give to_additive
any inputs. The fun starts when you add other attributes at the same time, or add attributes which do funky stuff.
Damiano Testa (Apr 09 2021 at 15:53):
I am confused on so many levels that I do not even make sense to myself...
Kevin Buzzard (Apr 09 2021 at 15:54):
For example @[simp, to_additive] def x...
is different to @[to_additive, simp] def x...
because in the first one the additivised version of x will also get the simp attribute.
Kevin Buzzard (Apr 09 2021 at 15:57):
some attributes are quite powerful and subtle things, and to_additive
is one of them. The way I learnt how it worked was just by looking at files like submonoid.basic and making sure I understood what was going on and why things were done the way they were done. But I guess what really taught me was writing some code like this myself, and being forced to learn it (like you are now).
Kevin Buzzard (Apr 09 2021 at 15:59):
I think that when I was at your stage (a competent Lean programmer and a mathematician) I started to ask myself whether I could actually completely understand random Lean files in mathlib, as opposed to just "basically understanding what was going on". I still ignore pretty much all meta
and do
code, but everything else I decided that I should probably be able to get to the bottom of it (and of course there are mathematicians who decide to understand meta
code too...)
Damiano Testa (Apr 09 2021 at 16:00):
Ok, thanks for the encouragement! I think that the PR is building now, so I must have learned something! :upside_down:
Bryan Gin-ge Chen (Apr 09 2021 at 16:01):
Custom attributes like to_additive
and simps
(but not simp
, I think) are basically metaprograms like tactics that run as Lean processes a declaration. We should expand the documentation if the way to use one isn't completely clear.
Kevin Buzzard (Apr 09 2021 at 16:03):
You should write some to_additive
doc before you forget everything. The basic things are: (1) basic usage (it makes an additive version of a multiplicative lemma) (2) add the attribute after simp
if you want the simp
to apply to the additive version (3) for structures (rather than theorems) you have to make the structure yourself and then tag it later (your confusion about adding the attribute at the same time or afterwards) (4) link to the "dictionary" which auto-generates things like theorem names (5) how to override docstrings e.g. with add_decl_doc
(see for example line 279 of submonoid.basic, I linked to the file above) (6) the gotcha with g^n vs n smul g and how it breaks everything.
Damiano Testa (Apr 09 2021 at 16:03):
Well, at the moment, I am having difficulty even keeping track of whether something is inside or outside some brackets, so my issues are probably at a more fundamental level than what the documentation might say.
That and having to keep track of whether there was an add
or not, a right + comm
or not really crossed my eyes!
Damiano Testa (Apr 09 2021 at 16:04):
Ok, I might give this a shot! Actually, your summary is already clearer than what is in my mind...
Kevin Buzzard (Apr 09 2021 at 16:07):
oh also the docstring trick
/-- multiplicative docstring -/
@[to_additive "additive docstring"] def gi : galois_insertion...
and the "choose another name rather than the auto-generated one" trick e.g.
@[to_additive ordered_add_comm_group.add_lt_add_left]
lemma ordered_comm_group.mul_lt_mul_left' (a b : α) (h : a < b) (c : α) : c * a < c * b :=
(remove the prime, because there will be some ring version which doesn't have an additive counterpart)
Yakov Pechersky (Apr 09 2021 at 16:09):
The final aspect is tagging something with to_additive
"out of band", that is, via an attribute ...
line like at
@[simp] lemma order_of_one : order_of (1 : α) = 1 :=
begin
apply le_antisymm,
{ exact order_of_le_of_pow_eq_one (nat.one_pos) (pow_one 1) },
{ exact nat.succ_le_of_lt ( order_of_pos' ⟨1, ⟨nat.one_pos, pow_one 1⟩⟩) }
end
@[simp] lemma add_order_of_zero : add_order_of (0 : H) = 1 :=
by simp [← order_of_of_add_eq_add_order_of]
attribute [to_additive add_order_of_zero] order_of_one
Damiano Testa (Apr 09 2021 at 16:41):
Is there a way to get Lean to list the attributes of a given lemma? Say that I want to discover whether an autogenerated lemma is or isn't simp
: chan I #check
or #print
or something like this to find out?
Kevin Buzzard (Apr 09 2021 at 16:49):
#print
and look at the very top of the output.
Damiano Testa (Apr 09 2021 at 16:57):
Thanks, Kevin!
Damiano Testa (Apr 09 2021 at 19:47):
To my surprise, once I fixed the silly errors that I introduced, PR #7134 "just worked"! The 4 typeclasses (add_)left_cancel_comm_monoid, (add_)right_cancel_comm_monoid
can leave mathlib and no one would notice...
Damiano Testa (Apr 09 2021 at 20:22):
I also just noticed that this might be the first time that a PR of mine actually has a net negative effect on the number of lines of code in mathlib.
Anne Baanen (Apr 09 2021 at 21:19):
Damiano Testa said:
I also just noticed that this might be the first time that a PR of mine actually has a net negative effect on the number of lines of code in mathlib.
"Fools ignore complexity. Pragmatists suffer it. Some can avoid it. Geniuses remove it." Congratulations, you are now a genius :)
Notification Bot (Apr 10 2021 at 00:05):
This topic was moved by Scott Morrison to #Is there code for X? > mul_right_comm_monoid
Last updated: Dec 20 2023 at 11:08 UTC