-
Notifications
You must be signed in to change notification settings - Fork 156
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 Finite class #2858
base: master
Are you sure you want to change the base?
Add Finite class #2858
Conversation
540afcf
to
4d3695c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of questions! Mostly wondering whether it would make sense to provide an Index
-based Enum
instead.
res = x :> prev' | ||
prev' = case natVal (asNatProxy prev') of | ||
0 -> repeat def | ||
_ -> let next = x +>> prev | ||
in registerB clk rst en (repeat def) next | ||
_ -> let next' = x +>> prev' | ||
in registerB clk rst en (repeat def) next' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that you have to do this is a pretty strong hint these names shouldn't be in Clash.Prelude
. Grepping my projects for prev
and next
also reveals a number of uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's kind of unavoidable. Every new name we choose might already be picked up in existing user code and I doubt that it's in the interest of the user, if we choose artificially unintuitive names just because of that.
So yes, the user might experience some new warnings about "clashing" names after a Clash update, which are always easy to fix via renaming or hiding the new stuff from Clash.Prelude
, in case they are not desired.
Please note that finding a good (and intuitive) naming scheme that doesn't clash with basic existing libraries (in particular base
) is a challenge already, especially if the offered primitives are quire fundamental. I also tried to improve the situation here based on some prior experience I had with the finite library, which comes with a similar interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the point is we write many state machines in Clash and there prev
and next
are very intuitive local names. Of course you can't avoid all name clashes, but there are some words that are very common in Clash code and prev
and next
are two examples of that specific category. So I agree with Martijn that trying to come up with an alternative is worthwhile in this specific case.
[edit]
Also, I believe we felt we wanted to move away from primes as a suffix in our code, instead using numeric suffixes to disambiguate. So I believe it'd be better to do that instead of introducing new uses of prime in our code base.
[/edit]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before/after?
prior/later?
back/forward?
Did not think them through much, just throwing it out there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fpred
/ fsucc
for finite versions of Enum functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Haddock of a class definition is not normative, what is? They usually use the word law in these cases, but there's no wiggle room in the doc that I see. If they wanted to document something that is common but not normative, I believe they would at the least add the word usually.
[edit]
My point is that these sentences are not misleading. Can you point me to an authoritative text that says otherwise? They are laws. They just didn't use the word law.
[/edit]
[edit 2]
satSucc
and satPred
are based on Enum
. Enum
says they should add and subtract one. So the doc for satSucc
and satPred
says they should add and subtract one.
Originally satSucc
and satPred
weren't even part of SaturatingNum
. But we found that this lead to undesired behaviour of specific instances, so we moved them into the class because they're related enough to justify that.
[/edit 2]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is that these sentences are not misleading. Can you point me to an authoritative text that says otherwise? They are laws. They just didn't use the word law.
I'm not aware of any authoritative text, but the style of class requirement descriptions in base
and ghc-prim
varies a lot, so it doesn't look to me like they have a strict enforcement on wording at this point. I gave some examples below. According to those you need to take the Applicative
laws very seriously for example, while the equality laws are more kind of a nice-to-have 🙃.
Eq
The Haskell Report defines no laws for Eq. However, == is customarily expected to implement an equivalence relationship where two values comparing equal are indistinguishable by "public" functions, with a "public" function being one not allowing to see implementation details. For example, for a type representing non-normalised natural numbers modulo 100, a "public" function doesn't make the difference between 1 and 201. It is expected to have the following properties:
Ord
The Haskell Report defines no laws for Ord. However, <= is customarily expected to implement a non-strict partial order and have the following properties:
Applicative
Further, any definition must satisfy the following:
Monad
Instances of Monad should satisfy the following:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
satSucc
andsatPred
are based onEnum
.
Sure satSucc
has a default implementation, which is satSucc s n = satAdd s n 1
, but there is no requirement that I cannot chose a different implementation for my own type instances at this point.
It's also a questionable, whether just because of that every class instance should depend on having a Num
instance as well. It would be much more user-friendly to have a default signature that requires Num
, so the constraint is only required if the user wants to use the default in the first place, e.g.,
satSucc :: SaturationMode -> a -> a
-- Default method suitable for types that can represent the number 1
default satSucc :: Num a => SaturationMode -> a -> a
satSucc s n = satAdd s n 1
{-# INLINE satSucc #-}
This would elide the requirement on Num
for every class instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding satSucc
and satPred
I have primarily not been talking about documentation or implementation, I'm talking about invisible things like design and intent. And also how they changed once we noticed deficiencies.
Sure
satSucc
has a default implementation, which issatSucc s n = satAdd s n 1
, but there is no requirement that I cannot chose a different implementation for my own type instances at this point.
We mean our documentation to be normative. So yes, I definitely interpret it as a requirement that they should add/subtract 1.¹ You yourself argued this above:
Do I miss something fundamental here? I read that like
satSucc
is equivalent to(+ 1)
, except for the boundary cases, where the behavior might differ. That's what I consider by "to compatible with each other".
I'd strongly argue that even in the boundary cases the behaviour matches adding/subtracting 1, it's just that (+ 1)
does not actually add 1 in those cases.
(continuing with quote from your latest message)
It's also a questionable, whether just because of that every class instance should depend on having a
Num
instance as well.
I think discussing creating another class for satSucc
and satPred
is out-of-scope here.
¹ Unless you're talking about boundary cases where the default implementation does not actually satisfy the laws, but then I don't understand why you bring it up.
7a1399a
to
1130232
Compare
Another thing to consider: there is at least some overlap with Counter I believe. Perhaps |
9564261
to
80dc978
Compare
Good point. Making How about adding another deriving strategy for |
I think however we slice it this PR will be an API change, unless we decide to not export it from
I think |
decaec3
to
c8821c4
Compare
c8821c4
to
042a148
Compare
cb4795a
to
8aeab59
Compare
645d9fa
to
2752731
Compare
Could you hide the three internal modules from the Haddock? Also, I think we should have instances for [edit] |
ce12c7a
to
3719e4a
Compare
👍 [edit]
I never have used |
Okay, when this is merged, could you create an issue that we should add the instance? |
3719e4a
to
de1af6f
Compare
de1af6f
to
e8cf2ec
Compare
e8cf2ec
to
bbf0415
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had another pass over the Haddock and feel there's still some needed changes.
'Bounded' instance) such that 'Enum' instances must ship partial | ||
functions for most finite types. Likewise, the number of inhabitants | ||
does not align with the number of indices offered by 'Int' for most | ||
types, which 'Finite' resolves by using 'Index n' instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types, which 'Finite' resolves by using 'Index n' instead. | |
types, which 'Finite' resolves by using @'Index' n@ instead. |
Please check if that fixes the rendering of the Haddock. I think it does, but I'm not sure.
) => Vec (ElementCount a) a | ||
elements = to <$> gElements | ||
|
||
-- | The @0@ indexed element. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-- | The @0@ indexed element. | |
-- | The element at index 0. |
Just a suggestion... it's just that to me it feels a zero-indexed something is something that has indexes starting at 0, like most arrays and stuff in computer science.
) => Maybe a | ||
lowestMaybe = to <$> gLowestMaybe | ||
|
||
-- | The @(ElementCount a - 1)@ indexed element |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-- | The @(ElementCount a - 1)@ indexed element | |
-- | The element at index @(ElementCount a - 1)@ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At 0 I said "just a suggestion" but I changed my mind. I think
The
(ElementCount a - 1)
indexed element
is an odd sentence, and I would like it changed. That also means it makes complete sense to change the 0-indexed phrasing.
-- | A newtype wrapper that reverses the index order, which normally | ||
-- would be used by the 'Finite' instance of the inner type via | ||
-- 'Generic' deriving. The newtype is only intended be used with the | ||
#if MIN_VERSION_base(4,15,0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not appear to work. CPP and Haddock do not play nice. I think it works better with the {-
construction, but still, you'll have to jump through some hoops and you're probably better off with a little duplication. Otherwise you'll get CPP-generated markers as part of your Haddock.
Please test with GCC versions both matching the #if
and the #else
.
Note how your documentation ends at "used with the" in the Haddock generated in CI:
https://clash-lang.gitlab.io/-/clash-compiler/-/jobs/8839034866/artifacts/hadocs/clash-prelude/clash-prelude/Clash-Class-Finite.html#t:GenericReverse
-- * Extensions | ||
, GenericReverse(..) | ||
, WithUndefined(..) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also list BoundedEnumEq
? Right now the only mention of that is at the
(Bounded a, Enum a, Eq a, KnownNat n, 1 <= n) => Finite (BoundedEnumEq n a)
instance, and if you then click
see BoundedEnumEq
you're taken to the Internal
module. I don't think we should ever say "see the internal module" in our doc.
The PR adds the
Finite
class as well as supplemental instances for most of the standard types.Finite
is a class for types with only finitely many inhabitants and can be considered a more hardware-friendly alternative toBounded
andEnum
, utilizingIndex
instead ofInt
and vectors instead of lists.Have a look at the haddock documentation for further insights.
Requires
Still TODO: