-
Notifications
You must be signed in to change notification settings - Fork 48
Add Interval #52
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 Interval #52
Conversation
src/Data/Interval/Interval.purs
Outdated
|
||
data RecurringInterval a = RecurringInterval (Maybe Int) (Interval a) | ||
data Duration | ||
= DurationWeek Week |
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.
Duration could be in two formats [PnnW] or [PnnYnnMnnDTnnHnnMnnS]
.
As I understand one could rewrite for example [P3W]
into [P21D]
and vise versa (1 week is exactly 7 days isn't it?)
So we could ignore week format as it could be expressed with full date format, but in that case it would be impossible to take some string representing ISO Duration, parse it then serialize it and get same string.
but isoInterval == serialize (parse isoInterval)
will not be true for all ISO Duration as currently we don't know if a component of Duration the interval was specified (a component could be omitted if it equals 0) if we want for it to be true we should store Maybe Int
as values of components instead of Int, but if we don't do that I think we could also remove this variant and during parsing if we see week format parse it as normal duration.
This haskell lib is related but |
src/Data/Interval/Interval.purs
Outdated
|
||
import Prelude | ||
|
||
data Interval a |
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.
a
is allowed to be Date
, DateTime
, LocalValue Date
and LocalValue DateTime
(only Time is not allowed)
4.4.4 Complete representations
4.4.4.1 Representations of time intervals identified by start and endWhen the application identifies the need for a complete representation of a time interval, identified by its start and its end, it shall use an expression in accordance with 4.4.2 combining any two complete date and time of day representations as defined in 4.3.2, provided that the resulting expression is either consistently in basic format or consistently in extended format
4.3.2 Complete representations
The time elements of a date and time of day expression shall be written in the following sequence:
a) For calendar dates: year – month – day of the month – time designator – hour – minute – second – zone designator
b) For ordinal dates: year – day of the year – time designator – hour – minute – second – zone designator
c) For week dates: year – week designator – week – day of the week – time designator – hour – minute – second – zone designator
....
Link to: |
src/Data/Interval/Interval.purs
Outdated
( Duration | ||
, Interval | ||
, RecurringInterval | ||
) where |
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.
With these exports, it's not possible to actually construct any of the interval values, to export the constructors to you need to list (..)
after the type; so Duration(..)
, and so on.
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.
Having said that, we might not want to export the constructors, since if I read what you're saying rightly below, we want to make it impossible to construct an Interval Time
- that means we'll have to provide "smart constructors" (functions for each of the valid constructions), and then only export the types, as you have here.
src/Data/Interval/Interval.purs
Outdated
data Duration = Duration DurationIn | ||
type DurationIn = List (Tuple DurationComponent Number) | ||
|
||
derive instance eqDuration ∷ Eq Duration |
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.
TODO make hours 24 == day 1
src/Data/Interval/Interval.purs
Outdated
} | ||
|
||
mkDuration ∷ DurationView → Duration | ||
mkDuration d = Duration $ |
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.
TODO remove mkDuration and DurationView as it's just:
mkDuration d = year d.year <> month d.month <> day d.day <> hours d.hours <> minutes d.minutes <> seconds d.seconds
src/Data/Interval/Interval.purs
Outdated
|
||
|
||
data Duration = Duration (Map.Map DurationComponent Number) | ||
-- TODO `day 1 == hours 24` |
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 derived Eq
instance is fine and we don't need to say that day 1
== hours 24
as they are build using different components and technically could be different (leap second).
If we really want we can, add other function which assumes day 1
== hours 24
and etc.
@garyb what would you suggest to validate that instances for RecuringInterval and Interval are correct? |
package.json
Outdated
"pulp": "^10.0.4", | ||
"purescript-psa": "^0.5.0-rc.1", | ||
"pulp": "^11.0.x", | ||
"purescript": "0.11.x", |
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.
^0.11.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.
It's same. do you prefer 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.
oh I forgot ^
src/Data/Interval/Interval.purs
Outdated
instance extendRecurringInterval ∷ Extend (RecurringInterval d) where | ||
extend f a@(RecurringInterval n i) = RecurringInterval n (extend (const $ f a) i ) | ||
|
||
data Interval d a |
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.
Can't this be a bit more typesafe? Like Interval String Unit
is valid type, but it has no sense
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.
format and unformat are "safe" i.e. they only take Interval IsoDuration DateTime
but someone someone might have Interval String Unit
and use bimap
to transform it into something format-able.
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.
Or one might have Interval (Maybe IsoDuration) (Maybe DateTime)
and then use bisequence
to transform it into Maybe (Interval IsoDuration DateTime)
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'm just a bit curious about this datatype because it has tons of typeclass instances. It looks like it could be something more generic. E.g. These d (These a a)
or something like this. Same for RecurringInterval
it looks like much more generic type than just RecurringInterval
, it's almost the Const
for bifunctors, right?
@garyb What do you think?
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 see what you're saying, but I think having a specialised type here is probably helpful in this case - it adds some context to what the meanings of the fields are (assuming a
and d
aren't populated with nonsensical types). When you pattern match on a StartDuration s d
it tells you a bit more about the intention of the code than a Both d (This s)
kinda thing.
@paf31 do you have any thoughts about alternative/better representations for this kind of thing?
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.
DateTime
is not functor/bifunctor/extend around Date|Time
😛
I don't mind having specialized type for this though, just want to be sure that there is as few ways of writing incorrect code as possible.
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.
-- BiTag to adds tag/metadata to bifunctor
newtype BiTag t p a b = BiTag (Tuple t (p a b))
-- Just made this up. BiTag might be more useful than Those
data Those d a
= Those a a
| Both a (Either d d)
| That d
newtype Interval d a
= Interval (Those d a)
newtype RecuringInterval d a
= RecuringInterval (BiTag (Maybe Int) Interval d a)
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.
but I suppose data BiTag t p a b = BiTag t (p a b)
or newtype BiTag t p a b = BiTag (Tuple t (p a b))
:)
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.
correct
src/Data/Interval/Interval.purs
Outdated
module Data.Interval | ||
( Interval(..) | ||
, RecurringInterval(..) | ||
, IsoDuration |
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.
IsoDuration
and related stuff could actually be moved to purescript-formatters.
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.
let's leave this as is for now.
some helper function could be adopted from here |
@garyb I think this could be merged, If you are fine with changes. |
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.
A whole bunch of minor picky comments, but I think it's almost there!
src/Data/Interval/Interval.purs
Outdated
show (IsoDuration d)= "(IsoDuration " <> show d <> ")" | ||
|
||
|
||
newtype Duration = Duration (Map.Map DurationComponent Number) |
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.
Can we move this & associated functions into Data.Interval.Duration
and re-export instead please?
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.
✅
src/Data/Interval/Interval.purs
Outdated
newtype Duration = Duration (Map.Map DurationComponent Number) | ||
|
||
derive instance eqDuration ∷ Eq Duration | ||
derive instance newtypeDuration ∷ Newtype Duration _ |
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.
No Ord
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.
✅
src/Data/Interval/Interval.purs
Outdated
derive instance newtypeDuration ∷ Newtype Duration _ | ||
|
||
instance showDuration ∷ Show Duration where | ||
show (Duration d)= "(Duration " <> show d <> ")" |
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.
Nitpick: space before =
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.
✅
src/Data/Interval/Interval.purs
Outdated
|
||
durationFromComponent ∷ DurationComponent → Number → Duration | ||
-- durationFromComponent _ 0.0 = mempty | ||
durationFromComponent k v= Duration $ Map.singleton k v |
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.
Does this commented line need restoring?
Also another space-before-=
nitpick 😄
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.
✅
src/Data/Interval/Interval.purs
Outdated
newtype IsoDuration = IsoDuration Duration | ||
derive instance eqIsoDuration ∷ Eq IsoDuration | ||
instance showIsoDuration ∷ Show IsoDuration where | ||
show (IsoDuration d)= "(IsoDuration " <> show d <> ")" |
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.
Can we move this & associated functions into Data.Interval.Duration.Iso
please? I wouldn't necessarily re-export either, since it's a more specific thing than "intervals in general".
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.
✅
src/Data/Interval/Interval.purs
Outdated
isFractional a = Math.floor a /= a | ||
|
||
isValidIsoDuration ∷ Duration → Boolean | ||
isValidIsoDuration (Duration m) = (not $ Map.isEmpty m) && (hasValidFractionalUse m) |
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.
Parens are unnecessary here, and not
applies to functions that return booleans as well as booleans, so not Map.isEmpty m && hasValidFractionalUse m
should work.
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.
"not applies to functions that return booleans" fantastic ✨
✅
src/Data/Interval/Interval.purs
Outdated
>>> (\vals -> fold (vals =>> validateFractionalUse) <> positiveNums vals) | ||
>>> extract | ||
validateFractionalUse vals = Conj $ case vals of | ||
(Tuple _ n):as | isFractional n → foldMap (snd >>> Additive) as == mempty |
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's a few usages of unicode, like the arrow here, but we don't use unicode in core/contrib.
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.
✅
src/Data/Interval/Interval.purs
Outdated
|
||
|
||
week ∷ Number → Duration | ||
week = durationFromComponent Day <<< (_ * 7.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.
Maybe we should just have a Week
component too. It can be represented by 7 days also, but that applies to any time measure up to Month
being representable just by Milliseconds
, so it wouldn't do any harm to add Week
as that will allow us to represent P1W
accurately too.
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.
✅
src/Data/Interval/Interval.purs
Outdated
instance monoidDuration ∷ Monoid Duration where | ||
mempty = Duration mempty | ||
|
||
data DurationComponent = Year | Month | Day | Hour | Minute | Second |
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'd reverse the order of these constructors so the derived Ord
instance is more sensible.
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.
✅
src/Data/Interval/Interval.purs
Outdated
bisequence = bisequenceDefault | ||
|
||
instance extendRecurringInterval ∷ Extend (RecurringInterval d) where | ||
extend f a@(RecurringInterval n i) = RecurringInterval n (extend (const $ f a) i ) |
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.
Extra space before closing paren.
This instance looks a little odd to me too! I don't think it breaks any laws though.
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.
space ✅
extend :: (RecurringInterval d a -> b) -> RecurringInterval d a -> RecurringInterval d b
extend f a@(RecurringInterval n i) = RecurringInterval n (extend (const $ f a) i )
n
is fixed and can't change. one thing which changes is i :: Interval d a
at first we call the f
to obtain b
. then we can delegate to
extend
of i :: Interval d a
to get Interval d b
. how? we already have
value of desired type f a :: b
so we can just call extend
on i
with
const (f a) :: Interval d a -> b
and we get Interval d b
.
Extend
instance for Interval
and RecurringInterval
might not be that useful tho. but someone might use it.
we can remove it and create an issue add Extend instance for Interval and RecurringInterval
, in which and ask for a use case for it to be added :d
- move Interval.purs up one level - move Duration and IsoDuration parts away
This way InvalidWeekComponentUsage will be first if it is present in errors
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.
Looks great, thanks!
No description provided.