The calculational package provides a syntax, similar to the one used in calculational mathematics for defining quantifiers, lists, sets, and bags. A novelty in Calculational is that such objects can be executed!
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
doc
examples
src
tests
.gitignore
LICENSE
README.md
Setup.hs
calculational.cabal
package.yaml
stack.yaml

README.md

Calculational

The calculational package provides a syntax, similar to the one used in calculational mathematics (Dijkstra, Backhouse, Gries), for defining quantifiers, lists, sets, and bags. A novelty in Calculational is that such objects can be executed!

Usage with ghci

For an interactive session use ghci with QuasiQuotes flag:

$ ghci -XQuasiQuotes

The [calc| ... |] syntax is used for the Disjktra-like notation:

ghci> :m + Calculational
ghci> [calc| (+ x <- [-100 .. 100] | 0 <= x < 10 : x*x) |] 
285

Usage in a program

{-# LANGUAGE QuasiQuotes #-}

import Calculational

sum' :: Num a => [a] -> a
sum' xs = [calc| (+ x <- xs | : x) |]

product' :: Num a => [a] -> a
product' xs = [calc| (* x <- xs | : x) |]

Boolean Expressions

The following operators are defined:

Operator Description
~   ¬ Negation
/\   Conjunction
\/   Disjunction
==>   Implication
<==   Consequence
===   Equivalence (Double implication)
=/=   Exclusive or (XOR)

Both and have the same precedence as in Gries; therefore, they cannot be mixed without parenthesis.

The unary # operator converts a boolean to a number; #True = 1 and #False = 0.

Quantifier

A general quantifier notation has the form:

(* x <- S | R : E)

Where x is a dummy (i.e., bound variable), S is a collection of elements (x <- S is a generator), R is the range, and E is the body expression. In the executable interpretation, the variable x takes values over each element of S, and E is evalutaed using the * operator for those values of x satisfying R. The operator * and its type must be a monoid.

For example, consider the operational interpretation of the following quantifier:

ghci> [calc| (+x <- [-100 .. 100] : 0 <= x < 10 : x^2) |]
285 

In this example, variable x is assigned each value in the set [-100 .. 100]. The evaluation corresponds to summing up the square x^2 for those values of x satisfying 0 <= x < 10.

The expression in this example is equivalent to the following Haskell expression:

ghci> sum [ x^2 | x <- [-100 .. 100], 0 <= x && x < 10]
285

Consider another example. The following definition:

ghci> let sorted xs = [calc| (/\ (x,y) <- zip xs (tail xs) : : x <= y) |]

tests if a list is sorted in ascending order.

ghci> sorted ['a' .. 'z']
True

As explained above, operator /\ represents the and operator. If the range R is empty, like in the last example, it is equivalent to True.

Alternatively, in a quantifier, the and operator /\ can be interchanged with forall and the or binary operator \/ with exists:

ghci> let s = [-100 .. 100]
ghci> [calc| (exists x <- s : 0 <= x <= 10 : even x) |]
True
ghci> [calc| (forall x <- s : 0 <= x <= 10 /\ x `mod` 2 == 0 :  even x) |]
True

The following is a table with the operators that could be used in quantifiers:

Operator Alternative Description
+   sum Sumation
*   product Product
# Apply the # operator to the body and sums it
/\     forall Forall quantifier
\/     exists Exists quantifier
===   Generalized equivalence
=/=   Generalized inequivalence
++ concat Generalized concatenation
union Generalized union
intersection Generalized intersection
max Maximum
min Minimum

Multiple quantifier variables

In a quantifier, there can be more than one generator and they are separated by commas.

ghci> [calc| (+ x<-[0 .. 5],y<-[0 .. 5] : 0 <= x < y <= 3 : x+y) |]
18

In this example for each x value between 0 and 5, y takes the values from 0 to 5.

The bounded variables can be used on later expressions, including other generators:

ghci> [calc| (+ x<-[0 .. 2],y<-[x+1 .. 3] : : x+y) |]
18

Also, tuples and some patterns for the left generator part can be used:

ghci> [calc| (+ (x,y) <- zip [0 .. 9] [1 .. 10] : : x+y) |]
100

Alternative bar syntax

While Dijkstra uses : between dummy variables and the range, Gries uses the bar | symbol. The Calculational package supports both notations:

ghci> let s = [-100 .. 100]
ghci> [calc| (+ x<-s,y<-s : 0 <= x < y <= 10 /\ even x /\ even y: x+y) |]
150
ghci> [calc| (+ x<-s,y<-s | 0 <= x < y <= 10 /\ even x /\ even y: x+y) |]
150

Maximum and minimum

The maximum and minimun operators have the same precedence:

ghci> [calc| 5 ↓ (3 ↑ 1) |]
3

In order to use the maximun and minimun in quantifiers, it is necesary to have identities. In this purpose, constants minbound and maxbound of the Bounded class are used: the corresponding identity is returned in the case of a quantifier with empty range.

ghci> [calc| (max x <- [-10 .. 10] | False : x^2 ) |] :: Int
-9223372036854775808
ghci> [calc| (max x <- [-10 .. 10] | : x^2 ) |] :: Int
100

The data type Infty is used to extend an ordered unbounded data type with a lower (NegInfty) and upper (PosInfty) bounds, and they are the identities of the max and min operators.

data Infty a = NegInfty
             | Value { getValue :: a }
             | PosInfty
             deriving (Eq, Ord, Read, Show)
ghci> [calc| (max x <- [-10 .. 10] | False : Value(x^2) ) |] 
NegInfty

ghci> [calc| (min x <- [-10 .. 10] | False : Value(x^2) ) |] 
PosInfty

ghci> [calc| (max x <- [-10 .. 10] | : Value(x^2) ) |] 
Value {getValue = 100}

ghci> [calc| (min x <- [-10 .. 10] | : Value(x^2) ) |] 
Value {getValue = 0}

Sets

A set is defined between braces, listing its elements separated by commas:

ghci> [calc| { 1,2,3 } |]
fromList [1,2,3]

Alternatively, the .. syntax could be used:

ghci> [calc| { 1 .. 3 } |]
fromList [1,2,3]

The spaces around the .. are necessary in some cases to avoid the parser fails.

Set comprehensions

With a syntax similar to that of quantifiers, a set comprehension has the same parts but enclosed with braces and without operator:

{ x <- S | R : E }

Some set comprehension examples:

ghci> [calc| { x <- [-10 .. 10] | 0 <= x < 4 : x^2 } |]
fromList [0,1,4,9]

ghci> let s = [-100 .. 100]
ghci> [calc| { x <- s | -4 <= x <= 4 : x^2 } |]
fromList [0,1,4,9,16]

ghci> [calc| { x <- s,y <- s | 1 <= x <= y <= 3 : (x,y) } |]
fromList [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]

ghci> [calc| { x <- s,y <- s | 1 <= x <= y <= 3 /\ even x : x*y } |]
fromList [4,6]

Optional body

If there is only one generator, and the body is ommited, it is equivalent to the left part of the generator.

ghci> [calc| { x <- [0 .. 10] : x^2 < 6 } |]
fromList [0,1,2]

Membership

The member function is used to test if an element is a member of a set.

ghci> [calc| 0 ∊ { x <- [0 .. 10] : x^2 < 6 } |]
True
ghci> [calc| 0 `member` { x <- [0 .. 10] : x^2 < 6 } |]
True

Number of elements

The number of elements of a finite set is obtained by using the unary # operator:

ghci> [calc| # {1,2,3,4} |] 
4

Operator # is overloaded.

Subset, Superset

The subset and superset definitions:

ghci> [calc| {1,2} ⊆ {1,2,3,4} |] 
True
ghci> [calc| {1,2} ⊂ {1,2,3,4} |] 
True
ghci> [calc| {1..4} ⊂ {1,2,3,4} |] 
False
ghci> [calc| {1..4} ⊇ {1,2} |] 
True
ghci> [calc| {1..4} ⊃ {1,2,3,4} |] 
False

Set difference

The notation for set difference is \

ghci> let s = [-100 .. 100]
ghci> [calc| { x <- s | -4 <= x <= 4 : x^2 } \ { x <- s | 0 <= x <= 10 } |]
fromList [16]

Set Union and Intersection

The union and intersection functions are defined for sets:

ghci> let s = [-100 .. 100]
ghci> [calc| { x <- s | -4 <= x <= 4 : x^2 } ∪ { x <- s | 0 <= x <= 10 } |]
fromList [0,1,2,3,4,5,6,7,8,9,10,16]
ghci> [calc| { x <- s | -4 <= x <= 4 : x^2 } `union` { x <- s | 0 <= x <= 10 } |]
fromList [0,1,2,3,4,5,6,7,8,9,10,16]
ghci> [calc| { 1 .. 4 } ∩ { 3 .. 5 } |]
fromList [3,4]
ghci> [calc| { 1 .. 4 } `intersection` { 3 .. 5 } |]
fromList [3,4]

Generalized union and intersection

The set union and intersection can be used as quantifier operators.

The generalized union is the union multiple sets:

ghci> [calc| (∪ s <- {{1},{2,3},{4}} : : s) |]
fromList [1,2,3,4]
ghci> [calc| (union s <- {{1},{2,3},{4}} : : s) |]
fromList [1,2,3,4]
ghci> [calc| (union s <- {{1},{2,3},{4}} : False : s) |]
fromList []
ghci> [calc| (union s <- {{1},{2,3},{4}} : : { x <- s : : x^2}) |]
fromList [1,4,9,16]

A wrapper data type Universe gives an upper bound to ordered monoids which lower bound is mempty.

data Universe m a = Container { getContainer :: m a }
                  | Universe
                  deriving (Eq, Ord, Read, Show)
ghci> [calc| (intersection s <- {{1,2},{2,3},{2}} : False : Container s) |]
Universe

ghci> [calc| (intersection s <- {{1,2},{2,3},{2}} : : Container s) |]
Container {getContainer = fromList [2]}

Lists

List comprehension is defined similar to set comprehensions:

ghci> [calc| [ x <- [0 .. 10] : 1 <= x <= 3 /\ even x : x^2 ] |]
[4]

Preppend and append

The prepend operator <| prepends an element to a list.

ghci> [calc| 1 <| [ x <- [0 .. 5] : 2 <= x <= 3 : x^2 ] |]
[1,4,9]
ghci> [calc| 1 ⊲ [ x <- [0 .. 5] : 2 <= x <= 3 : x^2 ] |]
[1,4,9]

The append operator |> appends an element to a list.

ghci> [calc| [ x <- [0 .. 5] : 1 <= x <= 2 : x^2 ]  |> 9 |]
[1,4,9]
ghci> [calc| [ x <- [0 .. 5] : 1 <= x <= 2 : x^2 ]  ⊳ 9 |]
[1,4,9]

As is the case for sets, operators like member (), union (), intersection (), difference (\), sublist (,), superlist (,), number of elements of finite lists # are also defined for lists.

Bags or multisets

ghci>  [calc| {| x <- [-3 .. 3] | : abs x |} |] 
fromOccurList [(0,1),(1,2),(2,2),(3,2)]

Occurs

The number of ocurrences of an element in a bag could be obtained using the binary # operator:

ghci> [calc| 2 # {| 1,1,2,2,2 |} |]
3

As is the case for sets and lists, operators member (), union (), intersection (), subbag (,), superbag (,), difference \ number of elements of finite bag # are defined for lists.