Closed dmcclean closed 10 years ago
(...)
is just syntactic sugar for I
really, no laziness benefits to speak of.
I don't have a problem with adding a non-empty interval constructions, though I tend to favor shorter names. =)
I adopted the practice that in Numeric.Interval.NonEmpty
the (...)
combinator follows createIntervalUnordered
here.
Close enough?
Hmmm. Tough call. I am going to argue the side that it isn't close enough, but I might be persuaded.
If you get the numbers from a human, the createIntervalUnordered
semantics are "friendly". If you get the numbers from a calculation, silently accepting unordered ones potentially masks problems that it would be better to bring to the surface.
I am not totally sure how defensively the rest of your libraries are written, so I don't want to push you to a style that is discordant or not haskell-y.
If it was just me and I was writing from scratch, I would not export I
, I would make (...)
use error
, and I would make both of these flavors. In my C# interval class I routinely use all three flavors (exception, Optional<Interval
Maybe name createIntervalUnordered
as (~)
or (.~.)
or (..~)
or (~~~)
? And createInterval
as (.?.)
or (..?)
?
I should add that if we can only have one I want the one that errors. You can build the others around it, and it also (along with hiding I
) lets the consumer of an Interval a
safely assume that it satisfies the invariant.
(~)
isn't legal syntax. For NonEmpty
I think (...)
having the unordered semantics is the right call. It means you are always defining a legal interval.
For the other two, having (...)
yield a possible Empty
or a negative interval means you can null
check them and aren't forced into Maybe
everywhere.
let's go with interval :: Ord a => a -> a -> Maybe (Interval a)
for an explicit constructor with the ordered semantics. It can be sensibly defined for all 3.
Kaucher
can expose the I
constructor, its Functor
, Monad
, etc. and Empty
without guilt. That is one reason why that was the form of interval arithmetic I was using all along.
The other two have to sacrifice a lot of instances to get any semblance of protection, but I do have a policy of at least exposing the guts through an Internal
module if I hide it in the main public API, to avoid power-users having to go off to use another library when they have a need I don't anticipate.
That would lead to
Numeric.Interval
Numeric.Interval.Kaucher
Numeric.Interval.NonEmpty
Numeric.Interval.Internal
Numeric.Interval.NonEmpty.Internal
Messy, but I could live with it.
Independent of whether to hide I
, I'd actually like to remove the unsafe instances from Numeric.Interval
and Numeric.Interval.NonEmpty
. As you say, the Kaucher variant is fine, but the others really aren't.
The Functor
instance only works if the function is monotonically non-decreasing. (You could write a flavor that worked for monotonic functions in either direction, I suppose.)
The Foldable
instance is fine because it only has Interval
s in negative position. (I can't think what you would use it for, but I have a feeling that says more about my creativity today than it does about the definition.)
The Applicative
(and hence Monad
) instance is strange because it requires you at some point to have a value of the type Interval (a -> b)
, which it treats as an ordered pair but has to put under the I
constructor instead of the (,)
constructor to match the shape of the class. In my opinion this one is questionable even for Kaucher intervals, although I may be missing something. What does it mean to have an Interval (Double -> Double)
? Perhaps there are compelling use cases that justify it though.
I don't understand the Distributive
instance. The Distributive
class is like a magic eye poster for me, I only sometimes understand it.
I'm agree with removing those instances from those types. They were only sound before under the Kaucher
semantics.
Done. I've removed the instances, split out Internal modules and shipped out a version 0.5
since someone wanted to use it.
I'm happy to keep going on adding the interval
smart constructors though. I just forgot about it before shipping 0.5
0.5.1 has interval
with the createInterval
semantics above.
Am I correctly understanding the comment on
(...)
(excerpted below) to mean that the difference betweenI
and(...)
is that the latter is lazier? Does inlining change that?Both constructors allow constructing lots of syntactically distinct but semantically equivalent empty intervals when the first field is greater than the second field.
How do you feel about creating two new construction functions like the following. (I am having trouble coming up with the names.)
These come up in my code because I am converting from another language where I had a library that dynamically ensured that there was only one representation of the empty interval, and threw exceptions if you gave values the wrong way around. The unordered flavor is used for user interfaces where you want to guess what they most likely "really meant".