Skip to content

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

Merged
merged 34 commits into from
Jun 26, 2017
Merged

Add Interval #52

merged 34 commits into from
Jun 26, 2017

Conversation

safareli
Copy link
Contributor

@safareli safareli commented Apr 3, 2017

No description provided.


data RecurringInterval a = RecurringInterval (Maybe Int) (Interval a)
data Duration
= DurationWeek Week
Copy link
Contributor Author

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.

@safareli
Copy link
Contributor Author

safareli commented Apr 3, 2017

This haskell lib is related but Duration representation there is somewhat complex https://github.com/meteogrid/iso8601-duration/tree/master/src/Data/Time/ISO8601


import Prelude

data Interval a
Copy link
Contributor Author

@safareli safareli Apr 3, 2017

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 end

When 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
....

@safareli
Copy link
Contributor Author

safareli commented Apr 3, 2017

Link to:

( Duration
, Interval
, RecurringInterval
) where
Copy link
Member

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.

Copy link
Member

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.

data Duration = Duration DurationIn
type DurationIn = List (Tuple DurationComponent Number)

derive instance eqDuration ∷ Eq Duration
Copy link
Contributor Author

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

}

mkDuration ∷ DurationView → Duration
mkDuration d = Duration $
Copy link
Contributor Author

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



data Duration = Duration (Map.Map DurationComponent Number)
-- TODO `day 1 == hours 24`
Copy link
Contributor Author

@safareli safareli Apr 25, 2017

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.

@safareli
Copy link
Contributor Author

@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",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^0.11.0?

Copy link
Contributor Author

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 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I forgot ^

instance extendRecurringInterval ∷ Extend (RecurringInterval d) where
extend f a@(RecurringInterval n i) = RecurringInterval n (extend (const $ f a) i )

data Interval d a

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 bisequenceto transform it into Maybe (Interval IsoDuration DateTime)

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?

Copy link
Member

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?

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.

Copy link
Contributor Author

@safareli safareli Apr 27, 2017

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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@safareli 💯

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)):)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct

module Data.Interval
( Interval(..)
, RecurringInterval(..)
, IsoDuration
Copy link
Contributor Author

@safareli safareli Apr 27, 2017

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.

Copy link
Contributor Author

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.

@safareli
Copy link
Contributor Author

some helper function could be adopted from here
https://github.com/arnau/ISO8601/blob/master/spec/iso8601/duration_spec.rb

@safareli safareli changed the title WIP: Add Interval Add Interval Jun 23, 2017
@safareli
Copy link
Contributor Author

@garyb I think this could be merged, If you are fine with changes.

Copy link
Member

@garyb garyb left a 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!

show (IsoDuration d)= "(IsoDuration " <> show d <> ")"


newtype Duration = Duration (Map.Map DurationComponent Number)
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newtype Duration = Duration (Map.Map DurationComponent Number)

derive instance eqDuration ∷ Eq Duration
derive instance newtypeDuration ∷ Newtype Duration _
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Ord instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derive instance newtypeDuration ∷ Newtype Duration _

instance showDuration ∷ Show Duration where
show (Duration d)= "(Duration " <> show d <> ")"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: space before =

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


durationFromComponent ∷ DurationComponent → Number → Duration
-- durationFromComponent _ 0.0 = mempty
durationFromComponent k v= Duration $ Map.singleton k v
Copy link
Member

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 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newtype IsoDuration = IsoDuration Duration
derive instance eqIsoDuration ∷ Eq IsoDuration
instance showIsoDuration ∷ Show IsoDuration where
show (IsoDuration d)= "(IsoDuration " <> show d <> ")"
Copy link
Member

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".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFractional a = Math.floor a /= a

isValidIsoDuration ∷ Duration → Boolean
isValidIsoDuration (Duration m) = (not $ Map.isEmpty m) && (hasValidFractionalUse m)
Copy link
Member

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.

Copy link
Contributor Author

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 ✨

>>> (\vals -> fold (vals =>> validateFractionalUse) <> positiveNums vals)
>>> extract
validateFractionalUse vals = Conj $ case vals of
(Tuple _ n):as | isFractional n → foldMap (snd >>> Additive) as == mempty
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



week ∷ Number → Duration
week = durationFromComponent Day <<< (_ * 7.0)
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instance monoidDuration ∷ Monoid Duration where
mempty = Duration mempty

data DurationComponent = Year | Month | Day | Hour | Minute | Second
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bisequence = bisequenceDefault

instance extendRecurringInterval ∷ Extend (RecurringInterval d) where
extend f a@(RecurringInterval n i) = RecurringInterval n (extend (const $ f a) i )
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

@garyb garyb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks!

@garyb garyb merged commit 3cb01cd into purescript:master Jun 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants