Skip to content

Commit 879b6a1

Browse files
committed
Initial release.
0 parents  commit 879b6a1

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# OS X
2+
.DS_Store
3+
4+
# Idris
5+
*.ibc
6+
7+
# Build outputs
8+
InsertionSort

InsertionSort.idr

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import Data.So
2+
import Data.Vect
3+
4+
--------------------------------------------------------------------------------
5+
-- Utility
6+
7+
-- Makes a best effort to return an error message.
8+
-- Use this only on code paths that you can deduce should be unreachable.
9+
unsafeError : String -> a
10+
unsafeError message = believe_me message
11+
12+
-- Unwraps a `Just a` to a plain `a`.
13+
-- Useful for command-line debugging but unsafe for general program usage.
14+
unsafeUnwrapJust : (Maybe a) -> a
15+
unsafeUnwrapJust (Just x) =
16+
x
17+
unsafeUnwrapJust (Nothing) =
18+
unsafeError "The specified Maybe was not a Just."
19+
20+
--------------------------------------------------------------------------------
21+
-- IsLte
22+
23+
-- Proof that `x <= y`.
24+
IsLte : Ord e => (x:e) -> (y:e) -> Type
25+
IsLte x y = So (x <= y)
26+
27+
mkIsLte : Ord e => (x:e) -> (y:e) -> Maybe (IsLte x y)
28+
mkIsLte x y =
29+
case choose (x <= y) of
30+
Left proofXLteY =>
31+
Just proofXLteY
32+
Right proofNotXLteY =>
33+
Nothing
34+
35+
-- Given an `x` and a `y`, returns a proof that either `x <= y` or `y <= x`.
36+
chooseLte :
37+
Ord e =>
38+
(x:e) -> (y:e) ->
39+
Either (IsLte x y) (IsLte y x)
40+
chooseLte x y =
41+
case choose (x <= y) of
42+
Left proofXLteY =>
43+
Left proofXLteY
44+
Right proofNotXLteY =>
45+
-- Given: not (x <= y)
46+
-- Derive: x > y
47+
-- Derive: y < x
48+
-- Derive: y <= x
49+
--
50+
-- Unfortunately Ord doesn't guarantee the preceding
51+
-- even though any sane implementation will conform
52+
-- to those rules.
53+
case choose (y <= x) of
54+
Left proofYLteX =>
55+
Right proofYLteX
56+
Right proofNotYLteX =>
57+
unsafeError "Impossible with a sane Ord implementation."
58+
59+
--------------------------------------------------------------------------------
60+
-- IsSorted
61+
62+
-- Proof that `xs` is sorted.
63+
data IsSorted : Ord e => (xs:Vect n e) -> Type where
64+
IsSortedZero :
65+
Ord e =>
66+
IsSorted Nil
67+
IsSortedOne :
68+
Ord e =>
69+
(x:e) ->
70+
IsSorted (x::Nil)
71+
IsSortedMany :
72+
Ord e =>
73+
(x:e) -> (y:e) -> (ys:Vect n'' e) -> -- (n'' == (n - 2))
74+
(IsLte x y) -> IsSorted (y::ys) ->
75+
IsSorted (x::(y::ys))
76+
77+
mkIsSorted : Ord e => (xs:Vect n e) -> Maybe (IsSorted xs)
78+
mkIsSorted Nil =
79+
Just IsSortedZero
80+
mkIsSorted (x::Nil) =
81+
Just (IsSortedOne x)
82+
mkIsSorted (x::(y::ys)) =
83+
case (mkIsLte x y) of
84+
Just proofXLteY =>
85+
case (mkIsSorted (y::ys)) of
86+
Just proofYYsIsSorted =>
87+
Just (IsSortedMany x y ys proofXLteY proofYYsIsSorted)
88+
Nothing =>
89+
Nothing
90+
Nothing =>
91+
Nothing
92+
93+
--------------------------------------------------------------------------------
94+
-- ElemsAreSame
95+
96+
-- Proof that set `xs` and set `ys` contain the same elements.
97+
data ElemsAreSame : (xs:Vect n e) -> (ys:Vect n e) -> Type where
98+
NilIsNil :
99+
ElemsAreSame Nil Nil
100+
PrependXIsPrependX :
101+
(x:e) -> ElemsAreSame zs zs' ->
102+
ElemsAreSame (x::zs) (x::zs')
103+
PrependXYIsPrependYX :
104+
(x:e) -> (y:e) -> ElemsAreSame zs zs' ->
105+
ElemsAreSame (x::(y::zs)) (y::(x::(zs')))
106+
-- NOTE: Probably could derive this last axiom from the prior ones
107+
SamenessIsTransitive :
108+
ElemsAreSame xs zs -> ElemsAreSame zs ys ->
109+
ElemsAreSame xs ys
110+
111+
XsIsXs : (xs:Vect n e) -> ElemsAreSame xs xs
112+
XsIsXs Nil =
113+
NilIsNil
114+
XsIsXs (x::ys) =
115+
PrependXIsPrependX x (XsIsXs ys)
116+
117+
flip : ElemsAreSame xs ys -> ElemsAreSame ys xs
118+
flip NilIsNil =
119+
NilIsNil
120+
flip (PrependXIsPrependX x proofXsTailIsYsTail) =
121+
PrependXIsPrependX x (flip proofXsTailIsYsTail)
122+
flip (PrependXYIsPrependYX x y proofXsLongtailIsYsLongtail) =
123+
PrependXYIsPrependYX y x (flip proofXsLongtailIsYsLongtail)
124+
flip (SamenessIsTransitive proofXsIsZs proofZsIsYs) =
125+
let proofYsIsZs = flip proofZsIsYs in
126+
let proofZsIsXs = flip proofXsIsZs in
127+
let proofYsIsXs = SamenessIsTransitive proofYsIsZs proofZsIsXs in
128+
proofYsIsXs
129+
130+
-- NOTE: Needed to explicitly pull out the {x}, {y}, {zs}, {us} implicit parameters.
131+
swapFirstAndSecondOfLeft : ElemsAreSame (x::(y::zs)) us -> ElemsAreSame (y::(x::zs)) us
132+
swapFirstAndSecondOfLeft {x} {y} {zs} {us} proofXYZsIsUs =
133+
let proofYXZsIsXYZs = PrependXYIsPrependYX y x (XsIsXs zs) in
134+
let proofYZZsIsUs = SamenessIsTransitive proofYXZsIsXYZs proofXYZsIsUs in
135+
proofYZZsIsUs
136+
137+
--------------------------------------------------------------------------------
138+
-- HeadIs, HeadIsEither
139+
140+
-- Proof that the specified vector has the specified head.
141+
data HeadIs : Vect n e -> e -> Type where
142+
MkHeadIs : HeadIs (x::xs) x
143+
144+
-- Proof that the specified vector has one of the two specified heads.
145+
--
146+
-- NOTE: Could implement this as an `Either (HeadIs xs x) (HeadIs xs y)`,
147+
-- but an explicit formulation feels cleaner.
148+
data HeadIsEither : Vect n e -> (x:e) -> (y:e) -> Type where
149+
HeadIsLeft : HeadIsEither (x::xs) x y
150+
HeadIsRight : HeadIsEither (x::xs) y x
151+
152+
--------------------------------------------------------------------------------
153+
-- Insertion Sort
154+
155+
-- Inserts an element into a non-empty sorted vector, returning a new
156+
-- sorted vector containing the new element plus the original elements.
157+
insert' :
158+
Ord e =>
159+
(xs:Vect (S n) e) -> (y:e) -> (IsSorted xs) -> (HeadIs xs x) ->
160+
(xs':(Vect (S (S n)) e) ** ((IsSorted xs'), (HeadIsEither xs' x y), (ElemsAreSame (y::xs) xs')))
161+
insert' (x::Nil) y (IsSortedOne x) MkHeadIs =
162+
case (chooseLte x y) of
163+
Left proofXLteY =>
164+
let yXNilSameXYNil = PrependXYIsPrependYX y x (XsIsXs Nil) in
165+
(x::(y::Nil) **
166+
(IsSortedMany x y Nil proofXLteY (IsSortedOne y),
167+
HeadIsLeft,
168+
yXNilSameXYNil))
169+
Right proofYLteX =>
170+
let yXNilSameYXNil = XsIsXs (y::(x::Nil)) in
171+
(y::(x::Nil) **
172+
(IsSortedMany y x Nil proofYLteX (IsSortedOne x),
173+
HeadIsRight,
174+
yXNilSameYXNil))
175+
insert' (x::(y::ys)) z proofXYYsIsSorted MkHeadIs =
176+
case proofXYYsIsSorted of
177+
(IsSortedMany x y ys proofXLteY proofYYsIsSorted) =>
178+
case (chooseLte x z) of
179+
Left proofXLteZ =>
180+
-- x::(insert' (y::ys) z)
181+
let proofHeadYYsIsY = the (HeadIs (y::ys) y) MkHeadIs in
182+
case (insert' (y::ys) z proofYYsIsSorted proofHeadYYsIsY) of
183+
-- rest == (_::tailOfRest)
184+
((y::tailOfRest) ** (proofRestIsSorted, HeadIsLeft, proofZYYsSameRest)) =>
185+
let proofXZYYsIsXRest = PrependXIsPrependX x proofZYYsSameRest in
186+
let proofZXYYsIsXRest = swapFirstAndSecondOfLeft proofXZYYsIsXRest in
187+
(x::(y::tailOfRest) **
188+
(IsSortedMany x y tailOfRest proofXLteY proofRestIsSorted,
189+
HeadIsLeft,
190+
proofZXYYsIsXRest))
191+
((z::tailOfRest) ** (proofRestIsSorted, HeadIsRight, proofZYYsSameRest)) =>
192+
let proofXZYYsIsXRest = PrependXIsPrependX x proofZYYsSameRest in
193+
let proofZXYYsIsXRest = swapFirstAndSecondOfLeft proofXZYYsIsXRest in
194+
(x::(z::tailOfRest) **
195+
(IsSortedMany x z tailOfRest proofXLteZ proofRestIsSorted,
196+
HeadIsLeft,
197+
proofZXYYsIsXRest))
198+
Right proofZLteX =>
199+
-- z::(x::(y::ys))
200+
let proofZXYYsIsZXYYs = XsIsXs (z::(x::(y::ys))) in
201+
(z::(x::(y::ys)) **
202+
(IsSortedMany z x (y::ys) proofZLteX proofXYYsIsSorted,
203+
HeadIsRight,
204+
proofZXYYsIsZXYYs))
205+
206+
-- Inserts an element into a sorted vector, returning a new
207+
-- sorted vector containing the new element plus the original elements.
208+
insert :
209+
Ord e =>
210+
(xs:Vect n e) -> (y:e) -> (IsSorted xs) ->
211+
(xs':(Vect (S n) e) ** ((IsSorted xs'), (ElemsAreSame (y::xs) xs')))
212+
insert Nil y IsSortedZero =
213+
([y] ** (IsSortedOne y, XsIsXs [y]))
214+
insert (x::xs) y proofXXsIsSorted =
215+
let proofHeadOfXXsIsX = the (HeadIs (x::xs) x) MkHeadIs in
216+
case (insert' (x::xs) y proofXXsIsSorted proofHeadOfXXsIsX) of
217+
(xs' ** (proofXsNewIsSorted, proofHeadXsNewIsXOrY, proofYXXsIsXsNew)) =>
218+
(xs' ** (proofXsNewIsSorted, proofYXXsIsXsNew))
219+
220+
-- Sorts the specified vector,
221+
-- returning a new sorted vector with the same elements.
222+
insertionSort :
223+
Ord e =>
224+
(xs:Vect n e) ->
225+
(xs':Vect n e ** (IsSorted xs', ElemsAreSame xs xs'))
226+
insertionSort Nil =
227+
(Nil ** (IsSortedZero, NilIsNil))
228+
insertionSort (x::ys) =
229+
case (insertionSort ys) of
230+
(ysNew ** (proofYsNewIsSorted, proofYsIsYsNew)) =>
231+
case (insert ysNew x proofYsNewIsSorted) of
232+
(result ** (proofResultIsSorted, proofXYsNewIsResult)) =>
233+
let proofXYsIsXYsNew = PrependXIsPrependX x proofYsIsYsNew in
234+
let proofXYsIsResult = SamenessIsTransitive proofXYsIsXYsNew proofXYsNewIsResult in
235+
(result ** (proofResultIsSorted, proofXYsIsResult))
236+
237+
--------------------------------------------------------------------------------
238+
-- Main
239+
240+
-- Parses an integer from a string, returning 0 if there is an error.
241+
parseInt : String -> Integer
242+
parseInt s =
243+
the Integer (cast s)
244+
245+
-- Joins a list of elements with the provided separator,
246+
-- returning a separator-separated string.
247+
intercalate : Show e => (xs:Vect n e) -> (sep:String) -> String
248+
intercalate Nil sep =
249+
""
250+
intercalate (x::Nil) sep =
251+
show x
252+
intercalate (x::(y::zs)) sep =
253+
(show x) ++ sep ++ (intercalate (y::zs) sep)
254+
255+
main : IO ()
256+
main = do
257+
putStrLn "Please type a space-separated list of integers: "
258+
csv <- getLine
259+
let numbers = map parseInt (words csv)
260+
let (sortedNumbers ** (_, _)) = insertionSort (fromList numbers)
261+
putStrLn "After sorting, the integers are: "
262+
putStrLn (intercalate sortedNumbers " ")
263+
264+
--------------------------------------------------------------------------------

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
compile:
2+
idris -o InsertionSort InsertionSort.idr
3+
4+
run: compile
5+
./InsertionSort

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# idris-insertion-sort
2+
3+
This is a provably correct implementation of insertion sort in Idris.
4+
5+
Specifically, it is an implementation of the following function definition:
6+
7+
```
8+
insertionSort :
9+
Ord e =>
10+
(xs:Vect n e) ->
11+
(xs':Vect n e ** (IsSorted xs', ElemsAreSame xs xs'))
12+
```
13+
14+
Given a list of elements, this function will return:
15+
16+
1. an output list,
17+
2. an `IsSorted` proof that the output list is sorted, and
18+
3. an `ElemsAreSame` proof that the input list and output lists contain
19+
the same elements.
20+
21+
This program makes heavy use of proof terms, a special facility only available
22+
in dependently-typed programming languages like Idris.
23+
24+
## Prerequisites
25+
26+
* Idris 0.9.16
27+
* Make
28+
29+
## How to Run
30+
31+
```
32+
make run
33+
```
34+
35+
## Example Output
36+
37+
```
38+
$ make run
39+
idris -o InsertionSort InsertionSort.idr
40+
./InsertionSort
41+
Please type a space-separated list of integers:
42+
3 2 1
43+
After sorting, the integers are:
44+
1 2 3
45+
```

0 commit comments

Comments
 (0)