aiken/interval
In a eUTxO-based blockchain like Cardano, the management of time can be finicky.
Indeed, in order to maintain a complete determinism in the execution of scripts, it is impossible to introduce a notion of “current time” since the execution would then depend on factor that are external to the transaction itself: the ineluctable stream of time flowing in our universe.
Hence, to work around that, we typically define time intervals, which gives window – a.k.a intervals – within which the transaction can be executed. From within a script, it isn’t possible to know when exactly the script is executed, but we can reason about the interval bounds to validate pieces of logic.
Types
A type to represent intervals of values. Interval are inhabited by a type
a
which is useful for non-infinite intervals that have a finite
lower-bound and/or upper-bound.
This allows to represent all kind of mathematical intervals:
// [1; 10]
let i0: Interval<PosixTime> = Interval
{ lower_bound:
IntervalBound { bound_type: Finite(1), is_inclusive: True }
, upper_bound:
IntervalBound { bound_type: Finite(10), is_inclusive: True }
}
// (20; infinity)
let i1: Interval<PosixTime> = Interval
{ lower_bound:
IntervalBound { bound_type: Finite(20), is_inclusive: False }
, upper_bound:
IntervalBound { bound_type: PositiveInfinity, is_inclusive: False }
}
Constructors
-
Interval { lower_bound: IntervalBound<a>, upper_bound: IntervalBound<a> }
An interval bound, either inclusive or exclusive.
Constructors
-
IntervalBound { bound_type: IntervalBoundType<a>, is_inclusive: Bool }
A type of interval bound. Where finite, a value of type a
must be
provided. a
will typically be an Int
, representing a number of seconds or
milliseconds.
Constructors
-
NegativeInfinity
-
Finite(a)
-
PositiveInfinity
Functions
after(lower_bound: a) -> Interval<a>
Create an interval that includes all values greater than the given bound. i.e [lower_bound, +INF)
interval.after(10) == Interval {
lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
}
before(upper_bound: a) -> Interval<a>
Create an interval that includes all values before (and including) the given bound. i.e (-INF, upper_bound]
interval.before(100) == Interval {
lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
}
between(lower_bound: a, upper_bound: a) -> Interval<a>
Create an interval that includes all values between two bounds, including the bounds. i.e. [lower_bound, upper_bound]
interval.between(10, 100) == Interval {
lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
}
contains(self: Interval<Int>, elem: Int) -> Bool
Checks whether an element is contained within the interval.
let iv =
Interval {
lower_bound: IntervalBound {
bound_type: Finite(14),
is_inclusive: True
},
upper_bound: IntervalBound {
bound_type: Finite(42),
is_inclusive: False
},
}
interval.contains(iv, 25) == True
interval.contains(iv, 0) == False
interval.contains(iv, 14) == True
interval.contains(iv, 42) == False
empty() -> Interval<a>
Create an empty interval that contains no value.
interval.contains(empty(), 0) == False
interval.contains(empty(), 1000) == False
entirely_after(lower_bound: a) -> Interval<a>
Create an interval that includes all values after (and not including) the given bound. i.e (lower_bound, +INF)
interval.entirely_after(10) == Interval {
lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
}
entirely_before(upper_bound: a) -> Interval<a>
Create an interval that includes all values before (and not including) the given bound. i.e (-INF, upper_bound)
interval.entirely_before(10) == Interval {
lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
upper_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
}
entirely_between(lower_bound: a, upper_bound: a) -> Interval<a>
Create an interval that includes all values between two bounds, excluding the bounds. i.e. (lower_bound, upper_bound)
interval.entirely_between(10, 100) == Interval {
lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
}
everything() -> Interval<a>
Create an interval that contains every possible values. i.e. (-INF, +INF)
interval.contains(everything(), 0) == True
interval.contains(everything(), 1000) == True
hull(iv1: Interval<Int>, iv2: Interval<Int>) -> Interval<Int>
Computes the smallest interval containing the two given intervals, if any
let iv1 = between(0, 10)
let iv2 = between(2, 14)
hull(iv1, iv2) == between(0, 14)
let iv1 = between(5, 10)
let iv2 = before(0)
hull(iv1, iv2) == before(10)
let iv1 = entirely_after(0)
let iv2 = between(10, 42)
hull(iv1, iv2) = entirely_after(0)
intersection(iv1: Interval<Int>, iv2: Interval<Int>) -> Interval<Int>
Computes the largest interval contains in the two given intervals, if any.
let iv1 = interval.between(0, 10)
let iv2 = interval.between(2, 14)
interval.intersection(iv1, iv2) == interval.between(2, 10)
let iv1 = interval.entirely_before(10)
let iv2 = interval.entirely_after(0)
interval.intersection(iv1, iv2) == interval.entirely_between(0, 10)
let iv1 = interval.between(0, 1)
let iv2 = interval.between(2, 3)
interval.intersection(iv1, iv2) |> interval.is_empty
is_empty(self: Interval<Int>) -> Bool
Tells whether an interval is empty; i.e. that is contains no value.
let iv1 = interval.empty()
let iv2 = Interval {
lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
upper_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
}
let iv3 = Interval {
lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
}
interval.is_empty(iv1) == True
interval.is_empty(iv2) == True
interval.is_empty(iv3) == False
// Note: Two empty intervals are not necessarily equal.
iv1 != iv2
is_entirely_after(self: Interval<Int>, point: Int) -> Bool
Check whether the interval is entirely after the point “a”
interval.is_entirely_after(interval.after(10), 5) == True
interval.is_entirely_after(interval.after(10), 10) == False
interval.is_entirely_after(interval.after(10), 15) == False
interval.is_entirely_after(interval.between(10, 20), 30) == False
interval.is_entirely_after(interval.between(10, 20), 5) == True
is_entirely_before(self: Interval<Int>, point: Int) -> Bool
Check whether the interval is entirely before the point “a”
interval.is_entirely_before(interval.before(10), 15) == True
interval.is_entirely_before(interval.before(10), 10) == False
interval.is_entirely_before(interval.before(10), 5) == False
interval.is_entirely_before(interval.between(10, 20), 30) == True
interval.is_entirely_before(interval.between(10, 20), 5) == False
max(left: IntervalBound<Int>, right: IntervalBound<Int>) -> IntervalBound<Int>
Return the highest bound of the two.
let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
interval.max(ib1, ib2) == ib2
min(left: IntervalBound<Int>, right: IntervalBound<Int>) -> IntervalBound<Int>
Return the smallest bound of the two.
let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
interval.min(ib1, ib2) == ib1