Zulip Chat Archive
Stream: mathlib4
Topic: Renaming monotonicity of powers lemmas
Yaël Dillies (Dec 18 2023 at 08:27):
This is an announcement that #9095 landed. This is a rather disruptive PR since it renamed 58 lemmas that are among the most used in mathlib. If you have a project depending on mathlib, you should check how the lemmas were renamed to adapt.
Yaël Dillies (Dec 18 2023 at 08:28):
The lemmas in question are all about monotonicity of (a ^ ·)
and (· ^ n)
. The full list of renames by file is
Algebra.GroupPower.Order
pow_mono
→pow_right_mono
pow_le_pow
→pow_le_pow_right
pow_le_pow_of_le_left
→pow_le_pow_left
pow_lt_pow_of_lt_left
→pow_lt_pow_left
strictMonoOn_pow
→pow_left_strictMonoOn
pow_strictMono_right
→pow_right_strictMono
pow_lt_pow
→pow_lt_pow_right
pow_lt_pow_iff
→pow_lt_pow_iff_right
pow_le_pow_iff
→pow_le_pow_iff_right
self_lt_pow
→lt_self_pow
strictAnti_pow
→pow_right_strictAnti
pow_lt_pow_iff_of_lt_one
→pow_lt_pow_iff_right_of_lt_one
pow_lt_pow_of_lt_one
→pow_lt_pow_right_of_lt_one
lt_of_pow_lt_pow
→lt_of_pow_lt_pow_left
le_of_pow_le_pow
→le_of_pow_le_pow_left
pow_lt_pow₀
→pow_lt_pow_right₀
Algebra.GroupPower.CovariantClass
pow_le_pow_of_le_left'
→pow_le_pow_left'
nsmul_le_nsmul_of_le_right
→nsmul_le_nsmul_right
pow_lt_pow'
→pow_lt_pow_right'
nsmul_lt_nsmul
→nsmul_lt_nsmul_left
pow_strictMono_left
→pow_right_strictMono'
nsmul_strictMono_right
→nsmul_left_strictMono
StrictMono.pow_right'
→StrictMono.pow_const
StrictMono.nsmul_left
→StrictMono.const_nsmul
pow_strictMono_right'
→pow_left_strictMono
nsmul_strictMono_left
→nsmul_right_strictMono
Monotone.pow_right
→Monotone.pow_const
Monotone.nsmul_left
→Monotone.const_nsmul
lt_of_pow_lt_pow'
→lt_of_pow_lt_pow_left'
lt_of_nsmul_lt_nsmul
→lt_of_nsmul_lt_nsmul_right
pow_le_pow'
→pow_le_pow_right'
nsmul_le_nsmul
→nsmul_le_nsmul_left
pow_le_pow_of_le_one'
→pow_le_pow_right_of_le_one'
nsmul_le_nsmul_of_nonpos
→nsmul_le_nsmul_left_of_nonpos
le_of_pow_le_pow'
→le_of_pow_le_pow_left'
le_of_nsmul_le_nsmul'
→le_of_nsmul_le_nsmul_right'
pow_le_pow_iff'
→pow_le_pow_iff_right'
nsmul_le_nsmul_iff
→nsmul_le_nsmul_iff_left
pow_lt_pow_iff'
→pow_lt_pow_iff_right'
nsmul_lt_nsmul_iff
→nsmul_lt_nsmul_iff_left
Data.Nat.Pow
Nat.pow_lt_pow_of_lt_left
→Nat.pow_lt_pow_left
Nat.pow_le_iff_le_left
→Nat.pow_le_pow_iff_left
Nat.pow_lt_iff_lt_left
→Nat.pow_lt_pow_iff_left
Yaël Dillies (Dec 18 2023 at 08:28):
On top of that, the following lemmas were removed:
self_le_pow
was a duplicate ofle_self_pow
.Nat.pow_lt_pow_of_lt_right
is defeq topow_lt_pow_right
.Nat.pow_right_strictMono
is defeq topow_right_strictMono
.Nat.pow_le_iff_le_right
is defeq topow_le_pow_iff_right
.Nat.pow_lt_iff_lt_right
is defeq topow_lt_pow_iff_right
.
Yaël Dillies (Dec 18 2023 at 08:28):
And finally:
- Some lemma assumptions have been turned from
0 < n
or1 ≤ n
ton ≠ 0
. - A few
Nat
lemmas have beenprotected
.
Yaël Dillies (Dec 18 2023 at 08:29):
There are other changes that are non-breaking that you can look up in #9095 if you're interested.
Yaël Dillies (Dec 18 2023 at 08:31):
I should remind you that the information of what lemma has been changed in what PR is available in the #changelog. The changelog does not however contain lists of renames.
Johan Commelin (Dec 18 2023 at 08:35):
Thanks for clearly documenting the changes!
Tudor achim (Dec 23 2023 at 01:59):
Just curious, what is the benefit of these changes? The cost of the change is substantial since existing non-mathlib codebases have to be refactored, and muscle memory needs to be re-trained. In this case the lemmas seem to be among the most-used in mathlib which is especially rough.
And if renames are necessary, it seems that an alternative that should be considered is leaving the old lemmas in place as aliases for the new lemmas, possibly with a deprecation attribute or something of the sort. It would be great to introduce those for this change if possible. That way there's a smooth off-ramp from the old lemmas to the new ones.
Jireh Loreaux (Dec 23 2023 at 04:24):
@Alex J. Best I meant to send this earlier, but got caught during the Zulip outage. This PR would be an ideal test candidate for evaluating Leaff's renaming detection. It would be awesome if you could generate the list that Yaël created manually above automatically from Leaff.
Johan Commelin (Dec 23 2023 at 06:44):
@Tudor achim Thanks for posting. You are right, and I think mathlib might need to start thinking about these issues. In general, I think we are quite unaware of all the projects that depend on mathlib. So it's good that you tell us about such examples.
I'm optimistic that in the near future we will have much better tooling around such renames.
Already the latest Lean release seems to have brought us functionality for automatically renaming declarations. If we can run that in batch mode, and those batches can be scripted, then these transitions should become almost painfree.
Yaël Dillies (Dec 23 2023 at 06:47):
In that specific case, my motivation was that the lemma names were all over the place and as a result it was impossible to infer what the name of a lemma is going off the name of a similar lemma, and in fact some of those similar lemmas were missing!
Ruben Van de Velde (Dec 23 2023 at 06:52):
And in particular, mathlib strongly leans on "guessability" of names, so there's also a cost to having inconsistent names. That doesn't mean we can't have a deprecation period, though
Johan Commelin (Dec 23 2023 at 06:54):
Certainly! We shouldn't stop making these changes. But we can try to be a bit less disruptive, in support of other projects depending on mathlib.
Yaël Dillies (Dec 23 2023 at 06:55):
I know there's a new deprecation feature, but it's a bit unergonomic to use within mathlib since it throws warnings on uses of the deprecated lemmas, and mathlib CI errors on warnings.
Mario Carneiro (Dec 23 2023 at 07:04):
well yeah, mathlib should not have warnings in itself, doesn't mean it shouldn't flag deprecations for downstream use
Mario Carneiro (Dec 23 2023 at 07:05):
besides you can always suppress the deprecation warning if you can't be bothered / it's a larger project to update mathlib itself after a deprecation
Mario Carneiro (Dec 23 2023 at 07:06):
certainly if this is pushing you to just remove instead of deprecate then it's the wrong reaction
Yaël Dillies (Dec 23 2023 at 07:07):
How can I suppress the deprecation warning? With a nolint?
Mario Carneiro (Dec 23 2023 at 07:07):
the linter warning should tell you the name of the linter to disable
Mario Carneiro (Dec 23 2023 at 07:08):
if it's the kind of warning that pops up immediately rather than at the end of the build with #lint
, you suppress it using set_option linter.foo false in
Mario Carneiro (Dec 23 2023 at 07:09):
all linter warnings (including the unused variable linter) can be suppressed in this way
Yaël Dillies (Dec 23 2023 at 07:09):
Yes but that involves disabling the linter of declarations where is the number of downstream uses. When I first heard of the deprecation tool, I was hoping this meant I could touch (zero) declarations downstream while still saying "you shouldn't use this lemma, but that one instead".
Johan Commelin (Dec 23 2023 at 07:09):
I think the workflow should be something like:
- Rename a lemma, and tag it with
@[old_name foo_bar]
. - The attribute creates an alias, and deprecates the old name in favour of the new name.
- mathlib has a boatload of warnings
- automatic tooling fixes all those warnings to switch over to the new names
- PR
- a week later: remove all the
@[old_name]
attributes.
Mario Carneiro (Dec 23 2023 at 07:09):
what the heck kind of deprecation is that @Yaël Dillies
Mario Carneiro (Dec 23 2023 at 07:10):
the whole point is to get the downstream uses to use something else
Yaël Dillies (Dec 23 2023 at 07:10):
Listen, the way I do refactors is
- Shuffle many lemmas around, possibly leaving duplicates for me to clean up afterwards
- Fix the downstream uses
Mario Carneiro (Dec 23 2023 at 07:10):
okay?
Mario Carneiro (Dec 23 2023 at 07:10):
warnings don't break the build
Yaël Dillies (Dec 23 2023 at 07:10):
Each step is a sufficiently hard to review PR on its own. I don't want to mix the steps together.
Mario Carneiro (Dec 23 2023 at 07:11):
at least they shouldn't
Yaël Dillies (Dec 23 2023 at 07:11):
Mario Carneiro said:
warnings don't break the build
Sorry, what??
Mario Carneiro (Dec 23 2023 at 07:11):
they make CI fail but it should still produce oleans and such
Yaël Dillies (Dec 23 2023 at 07:11):
All I'm complaining here is that @[deprecated]
doesn't naturally let me split steps 1 and 2 into two PRs.
Mario Carneiro (Dec 23 2023 at 07:12):
Sure it does, step 1 can not deprecate the old declaration and just have both
Yaël Dillies (Dec 23 2023 at 07:13):
I'm not a super big fan of this, but if the PR deprecating them comes around quickly I guess it's alright.
Mario Carneiro (Dec 23 2023 at 07:13):
Johan Commelin said:
- a week later: remove all the
@[old_name]
attributes.
I would really like us to start thinking on a time scale more like 3 months for this kind of thing
Johan Commelin (Dec 23 2023 at 07:14):
also fine with me
Mario Carneiro (Dec 23 2023 at 07:14):
this is the kind of thing that gives mathlib a bad name
Johan Commelin (Dec 23 2023 at 07:14):
week
was a meta-syntactic variable in that step (-;
Yaël Dillies (Dec 23 2023 at 07:15):
FWIW, I'm happy with having a long list of @[deprecated] alias
for a long time.
Mario Carneiro (Dec 23 2023 at 07:15):
The thing is, deprecation and migration are aspects of library design too, and they actually take work to achieve
Yaël Dillies (Dec 23 2023 at 07:16):
The amount of work I spent in my PR description is probably no less than what it would have taken me to deprecate. Migration I don't know.
Mario Carneiro (Dec 23 2023 at 07:17):
Right now our best tools for migration are tied up in deprecations
Mario Carneiro (Dec 23 2023 at 07:18):
Maybe we want a @[soft_deprecated]
which is a info message instead of a warning, this should fix the issue with breaking CI since noisy files also cause CI to fail but lake will run to completion
Newell Jensen (Dec 23 2023 at 07:19):
Mario Carneiro said:
this is the kind of thing that gives mathlib a bad name
blasphemy! ... :grinning_face_with_smiling_eyes:
Yaël Dillies (Dec 23 2023 at 07:19):
Yes, that would be an improvement as I could then fix all of mathlib in one go instead of many
Mario Carneiro (Dec 23 2023 at 07:19):
seems like a hack around our current tools though, I wouldn't want to actually have users dealing with that
Mario Carneiro (Dec 23 2023 at 07:19):
deprecations are morally warnings
Yaël Dillies (Dec 23 2023 at 07:21):
No but I mean I could @[soft_deprecated]
all the lemmas I want, build mathlib, remove all downstream uses, replace all the @[soft_deprecated]
with @[deprecated]
.
Mario Carneiro (Dec 23 2023 at 07:21):
right, I was imagining exactly that workflow
Mario Carneiro (Dec 23 2023 at 07:22):
unfortunately it doesn't help consumers that are also big projects
Newell Jensen (Dec 23 2023 at 07:22):
Does mathlib have some sort of stable release schedule? From what it seems most of the tagged releases are from toolchain updates.
Mario Carneiro (Dec 23 2023 at 07:23):
It has a version compatible with each lean stable release, but it doesn't have stable releases in the "stable" sense
Yaël Dillies (Dec 23 2023 at 07:23):
Mario Carneiro said:
unfortunately it doesn't help consumers that are also big projects
Then maybe the solution is to have a lake flag for warning/info on deprecation?
Mario Carneiro (Dec 23 2023 at 07:23):
I think lake should just not stop on warnings-as-errors
Yaël Dillies (Dec 23 2023 at 07:23):
That's a fair position. I always found that a bit surprising.
Mario Carneiro (Dec 23 2023 at 07:24):
it's a very low effort implementation
Yaël Dillies (Dec 23 2023 at 07:24):
It would also be interesting to have an extra date argument to @[deprecated]
so that a bot can watch for them and send a reminder months later "Those lemmas have been deprecated for a long time. Should we delete them?" (and also for general information of the source code readers).
Mario Carneiro (Dec 23 2023 at 07:24):
it literally just turns all warning messages into error messages, with all the downstream effects of an error message on the server and lake
Mario Carneiro (Dec 23 2023 at 07:25):
yes the whole thing could be redesigned, someone needs to look into how other languages do it
Yaël Dillies (Dec 23 2023 at 07:25):
Without that bot checking for long-deprecated lemmas, I would be a bit wary of using @[deprecated]
.
Mario Carneiro (Dec 23 2023 at 07:26):
since := NNN
is part of most deprecation mechanisms I know
Mario Carneiro (Dec 23 2023 at 07:26):
or will_be_removed_in := NNN
Yaël Dillies (Dec 23 2023 at 07:26):
Also, do deprecated
lemmas show up in search tools like rw?
, apply?
, exact?
and the docs?
Mario Carneiro (Dec 23 2023 at 07:26):
I think so
Mario Carneiro (Dec 23 2023 at 07:27):
every such tool has to implement this kind of logic independently so it's very haphazard what is checked
Yaël Dillies (Dec 23 2023 at 07:27):
Okay, docs#and_assoc' does show up the docs with a @[deprecated and_assoc]
tag, but it's not as flashing yellow as I would have wanted it to.
Mario Carneiro (Dec 23 2023 at 07:27):
there are half a dozen isInternal
or isBlacklisted
functions in lean+mathlib
Mario Carneiro (Dec 23 2023 at 07:28):
that also relates to the fact that there is no generic way to show attributes on a definition like lean 3 #print
Mario Carneiro (Dec 23 2023 at 07:29):
so every such metaprogram has to go and query every attribute it cares about, and since this is extensible in both programs and attributes you can see why this is difficult to arrange
Yaël Dillies (Dec 23 2023 at 07:29):
I should also say I have many other breaking refactors underway and I am willing for them to be the testing ground of any deprecation/migration method we come up with.
Mario Carneiro (Dec 23 2023 at 07:30):
I think we need a holistic proposal for how deprecation is supposed to work, what the attribute looks like, the code action, auto fix etc
Yaël Dillies (Dec 23 2023 at 07:40):
Mario/Johan, would you welcome a PR that turns the PR description of #9095 into a long list of @[deprecated] alias
in the relevant files?
Yaël Dillies (Dec 23 2023 at 07:40):
(for the lemmas whose old name hasn't been taken by a new lemma)
Johan Commelin (Dec 23 2023 at 07:44):
I don't want to push you to do that work now. But if you do it, I'm happy to merge that PR
Johan Commelin (Dec 23 2023 at 07:45):
Yaël Dillies said:
(for the lemmas whose old name hasn't been taken by a new lemma)
This is also something that the holistic deprecation proposal should address.
Yaël Dillies (Dec 23 2023 at 07:45):
It should be pretty easy. Let's try.
Yaël Dillies (Dec 23 2023 at 07:45):
I've just deprecated my first ever lemmas in #9219.
Thomas Murrills (Dec 23 2023 at 07:48):
For documentation purposes, it would be nice for the deprecated
attribute to be able to store a blurb on why something was deprecated, even if it’s just a link to a PR or issue. (Ideally it would be a little bit more.) :)
Johan Commelin (Dec 23 2023 at 07:49):
The value of deprecated since
should ideally be updated just before the PR is merged by bors...
Johan Commelin (Dec 23 2023 at 07:50):
@Thomas Murrills you can already give a string argument, if you want to
Thomas Murrills (Dec 23 2023 at 07:51):
Ah, I’ve just never seen that string argument used, so I assumed it didn’t exist :sweat_smile:
Yaël Dillies (Dec 23 2023 at 07:54):
Johan Commelin said:
The value of
deprecated since
should ideally be updated just before the PR is merged by bors...
Yes, although I think it's fine if the date is off by a few days. It's not like huge refactor PRs sit around for a long time anyway.
Thomas Murrills (Dec 23 2023 at 08:00):
What would the value of since
be, in general? A date, a PR number, a commit…? (All three?)
Yaël Dillies (Dec 23 2023 at 08:01):
Mario Carneiro (Dec 23 2023 at 08:02):
PR number sounds like the most reliable
Mario Carneiro (Dec 23 2023 at 08:03):
I think we could still make a script to find old deprecations with only PR numbers to go by
Yaël Dillies (Dec 23 2023 at 08:03):
And then we say something like "The deprecation period is 3000 PRs long"?
Thomas Murrills (Dec 23 2023 at 08:22):
If we stored time as well, and if each mathlib commit somehow knew the time at which the commit itself was committed, we wouldn’t need soft_deprecated: we could calculate the amount of time from the deprecation to the current commit you were on, and decide to either log info or warn based on the result.
E.g. log “this declaration will be deprecated in the version of <project> released <x> days after the current version” where x = deprecationTime + gracePeriodDuration – projectCommitTime (where here mathlib is the project). If x ≤ 0, you get a warning instead.
(Maybe there are reasons not to want this behavior, but just putting it out there as an idea.)
Johan Commelin (Dec 23 2023 at 08:41):
@Mario Carneiro Can you tell me whether there is now a way to do batch renames? Can we give Lean or VScode or the LSP a dictionary of names mapping to new names , and it will just update all of mathlib to use those new names?
Eric Wieser (Dec 23 2023 at 09:09):
Mario Carneiro said:
Maybe we want a
@[soft_deprecated]
which is a info message instead of a warning
Python has PendingDeprecationWarning
for this
Eric Rodriguez (Dec 23 2023 at 09:15):
I guess the way to deprecate lemmas that have a name that is then used elsewhere is to have a special deprecation attribute that only comes up if there's an error applying the lemma (e.g. if there's a type error applying the lemma/a rw doesn't work/etc, it can say 'you may be wanting lemma X, which was recently renamed from this')
Tudor achim (Dec 23 2023 at 17:11):
To me, mathlib feels like the standard library of other languages (and less like functionality-specific libraries that developers build around the core language). Generally speaking, standard libraries rarely if ever undergo breaking API changes outside of major version bumps. This includes both renaming things as well as changing API type signatures in a breaking way. The counterargument to this, of course, is that the mathlib library is significantly more complex than the stdlib of any major language, and it's also early days, so it's natural that there should be more breaking changes otherwise the long-term potential of the library is diminished. I don't think anybody would argue this point.
I would suggest the following principles:
- If a function or definition exists in a given
mathlib
master commit without the deprecated tag, it is guaranteed to be preserved (with the exact module/name/type signature) for at least 6 months. However, it may later be tagged as deprecated. - If a function is deprecated, that means that it cannot be used elsewhere in mathlib, which ensures that it doesn't propagate to the core codebase in ways that other people would pattern match on. So it is the responsibility of someone deprecating functions to replace them in the rest of mathlib.
- Over time, as the library matures and design patterns are solidified, I think it's reasonable to increase the 6 month window to something on the order of several years (but likely not beyond).
Following these principles would provide a much more stable platform to build on, and I think they, in spirit, capture the key points of the discussion above.
There was a suggestion in the thread about deprecating more quickly and providing migration tools to keep up -- just note that these can be serious papercuts to impose on a user community (e.g. even python's 2to3
was pretty annoying to use, speaking from personal experience). I think for folks working on mathlib who are very familiar with these things they don't seem like a big deal, but as the popularity of mathlib keeps growing, there will be lots of users for whom it will be a turnoff to have to regularly run housekeeping scripts on their repos.
Tudor achim (Dec 23 2023 at 17:14):
As a test case, in this specific instance, would it be acceptable to open a PR to reintroduce those functions with whatever deprecated tag is most appropriate? I'd be happy to handle that.
Johan Commelin (Dec 23 2023 at 18:04):
@Tudor achim thanks for your thoughts. @Yaël Dillies also offered to add back the functions, together with deprecated tags. I'll be happy to merge that PR.
Yaël Dillies (Dec 23 2023 at 18:33):
I don't think deprecation even makes sense for most of mathlib. Many parts of the library are leaves that are under active development, and each lemma can change many times in a few months. In contrast, the lemmas that I renamed here are part of the algebraic order hierarchy, which is very central.
Yury G. Kudryashov (Dec 23 2023 at 19:29):
I opened a new topic for the discussion about backward compatibility of mathlib: https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Backward.20compatibility
Eric Rodriguez (Dec 23 2023 at 19:33):
(also, @Joachim Breitner, when will loogle be updated? It doesn't have the new lemma names yet)
Yuyang Zhao (Dec 23 2023 at 20:04):
I recall that I have an ancient mathlib3 PR that uses ₚ
suffixes for some lemmas with positivity assumptions. I felt this looks better than suffixes like '
and _of_pos_of_nonneg
.
Yaël Dillies (Dec 23 2023 at 20:05):
Sadly, many search tools don't support unicode, so I think it would in practice be a downgrade.
Eric Rodriguez (Dec 23 2023 at 20:14):
I disagree and think it would be a strong upgrade. ' is unsearchable anyways, and searching for _of_blah sucks
Yaël Dillies (Dec 23 2023 at 20:17):
The other thing is that _of_pos
and _of_nonneg
are sometimes good ways to disambiguate similar lemmas.
Yury G. Kudryashov (Dec 23 2023 at 20:17):
E.g., I think that we should have many versions of docs#mul_lt_mul
Yury G. Kudryashov (Dec 23 2023 at 20:18):
And of_le_of_lt
/of_lt_of_le
/..., possibly with extra of_pos
/of_nonneg
seem like a good way to disambiguate.
Yuyang Zhao (Dec 23 2023 at 20:21):
I came up with this idea inspired by the suffix ₀
(also a unicode).
Yuyang Zhao (Dec 23 2023 at 20:35):
Another motivation is that lemmas like docs#mul_lt_mul_of_nonneg_of_pos and docs#mul_lt_mul_of_le_of_lt' have inconsistent names. Using the suffix p
allows them to have consistent names with no more than one '
, and can clearly distinguish docs#mul_lt_mul_of_le_of_lt which does not require positivity assumptions.
Yuyang Zhao (Dec 23 2023 at 20:40):
#782 is the old synchronizing PR, I'll make a new one.
Yaël Dillies (Dec 23 2023 at 20:57):
Please don't! I am halfway through a PR touching those lemmas.
Eric Rodriguez (Dec 23 2023 at 20:59):
Yael, if you are doing this already, can we at least add aliases with names of that form? I think it's very valuable, and aliases are _cheap_
Yuyang Zhao (Dec 23 2023 at 21:06):
I think I kept the original name as an alias in my previous PR. No, but most of them.
Joachim Breitner (Dec 23 2023 at 21:08):
Eric Rodriguez said:
(also, Joachim Breitner, when will loogle be updated? It doesn't have the new lemma names yet)
Manually, once in a while. Thanks for the ping :-)
I should automate it if course eventually.
Yuyang Zhao (Dec 23 2023 at 21:18):
It looks like some inconsistent names and wrong names like docs#mul_lt_mul_of_le_of_le' are not kept. In any case I can wait for the other changes to be done first. I'm in no hurry to change it. I already have several PRs that have been there for more than 6 months.
Yuyang Zhao (Dec 23 2023 at 23:21):
#9249 In fact, nothing in mathlib has been broken.
upd: Wait, I may have misunderstood. But I can wait for other PRs anyway.
Tudor achim (Dec 24 2023 at 00:22):
@Yaël Dillies , just to confirm, are you currently making a PR to mathlib to reintroduce those lemmas as aliases that are marked deprecated? If you guys want to defer this decision until later that's also totally cool, just trying to plan out some things internally.
Yaël Dillies (Dec 24 2023 at 06:43):
Yaël Dillies said:
Yes, see :wait_one_second:
Last updated: May 02 2025 at 03:31 UTC