A cost-aware logical framework, embedded in Agda.
See the codeThe calf language is a cost-aware logical framework for studying quantitative aspects of functional programs.
This repository contains the Agda implementation of calf, as well as some case studies of varying complexity.
The source code may be viewed interactively with (semantic) syntax highlighting in the browser using the HTML files in the ./html directory.
These files were generated by running:
agda --html --html-dir=html src/index.agda
You may want to start by opening html/index.html.
To view a specific module M, open html/M.html in a web browser.
For example, open html/Examples.Sorting.Parallel.html to view the module Examples.Sorting.Parallel.
This implementation of calf has been tested using:
agda-stdlib v2.0 experimentalInstallation instructions may be found in INSTALL.md.
calf is parameterized by a cost monoid (ℂ, +, zero, ≤).
The formal definition, CostMonoid, is given in Algebra.Cost.
The definition of a parallel cost monoid (ℂ, ⊕, 𝟘, ⊗, 𝟙, ≤) is given, as well, as ParCostMonoid.
Some common cost monoids and parallel cost monoids are given in Algebra.Cost.Instances; for example, ℕ-CostMonoid simply tracks sequential cost.
Note that every ParCostMonoid induces a CostMonoid via the additive substructure (ℂ, ⊕, 𝟘, ≤).
The language itself is implemented via the following files, which are given in a dependency-respecting order.
The following modules are not parameterized:
Calf.Prelude contains commonly-used definitions.Calf.CBPV defines the basic dependent Call-By-Push-Value (CBPV) language, using Agda postulates and rewrite rules.Calf.Directed defines a preorder on each type, per the developments in decalf.Calf.Phase defines the phase distinction of extension and intension:
Calf.Phase.Core postulates a proposition, ext, for the extensional phase.Calf.Phase.Open defines the open/extensional modality ◯ for ext.Calf.Phase.Closed defines the closed/intensional modality ● for ext.Calf.Phase.Directed postulates the decalf law that under ext, inequality coincides with equality.Calf.Phase.Noninterference contains theorems related to the phase distinction/noninterference.The following modules are parameterized by a CostMonoid:
Calf.Step defines the computational effect step and the associated coherence laws via rewrite rules.The following modules are parameterized by a ParCostMonoid:
Calf.Parallel defines the parallel execution operation _∥_ whose cost structure is given by the product operation of a ParCostMonoid (i.e., _⊗_).In src/Calf/Data, we provide commonly-used data types.
The following modules are not parameterized and simply internalize the associated Agda types via the meta⁺ primitive:
Calf.Data.BoolCalf.Data.EqualityCalf.Data.ListCalf.Data.MaybeCalf.Data.NatCalf.Data.ProductCalf.Data.SumThe following modules define custom, calf-specific data types for cost analysis and are parameterized by a CostMonoid:
Calf.Data.IsBoundedG defines a generalized notion of cost bound, IsBoundedG, where a bound is a program of type F unit.
Additionally, it provides lemmas for proving the boundedness of common forms of computations.Calf.Data.IsBounded instantiates IsBoundedG for cost bounds of the form step (F unit) c (ret triv).Calf.Data.BoundedFunction defines cost-bounded functions using IsBounded.Calf.Data.BigO gives a definition of "big-O" asymptotic bounds via IsBounded.
In particular, an element of the type given A measured-via size , f ∈𝓞(g) (i.e., "given an input of type A and a size measure size on A, f is in 𝓞(g)) is a lower bound on input sizes n' and a constant multiplier k along with a proof h that for all inputs x with n' ≤ size x, f x is bounded by k multiples of g (size x), denoted n' ≤n⇒f[n]≤ k g[n]via h.We provide a variety of case studies in src/Examples.
Examples.Idmodule Easy
id that trivially returns its input.id/bound, which here is the same as id.id/is-bounded showing that id is bounded by id/bound.id/correct stating the extensional correctness of id as a corollary of id/is-bounded.id/asymptotic : given nat measured-via (λ n → n) , id ∈𝓞(λ n → 0) stating that id is in 𝓞(0).module Hard
id that reconstructs its input via induction.id/bound, which incurs n cost before returning n.id/is-bounded showing that id is bounded by id/bound.id/correct stating the extensional correctness of id as a corollary of id/is-bounded.id/asymptotic : given nat measured-via (λ n → n) , id ∈𝓞(λ n → n) stating that id is in 𝓞(n), where n is the input number.Easy.id and Hard.id are extensionally equivalent, easy≡hard : ◯ (Easy.id ≡ Hard.id), as a corollary of the id/correct proofs.Examples.TreeSumsum that sums the elements of a tree, incurring unit cost when performing each addition operation.
At each node, the recursive calls are computed in parallel.sum/bound, which incurs size t , depth t cost before returning the sum of the tree via a value-level function.sum/has-cost stating that sum and sum/bound are equivalent.sum/is-bounded stating that the cost of sum t is bounded by sum/bound, as a corollary of sum/has-cost.Examples.Exp2module Slow
exp₂ that computes the exponentation of two by its input by performing two identical recursive calls.
Since two identical recursive calls are made in parallel, the work is exponential, but the span is still linear.exp₂/bound, incurring 2 ^ n - 1 , n cost before returning result 2 ^ n.exp₂/is-bounded showing that exp₂ is bounded by exp₂/bound.exp₂/correct stating the extensional correctness of exp₂ as a corollary of exp₂/is-bounded.exp₂/asymptotic : given nat measured-via (λ n → n) , exp₂ ∈𝓞(λ n → 2 ^ n , n) stating that exp₂ is in 𝓞(2 ^ n , n).module Fast
exp₂ which computes the exponentation of two by its input via a standard recursive algorithm.exp₂/bound, incurring n , n cost before returning result 2 ^ n.exp₂/is-bounded showing that exp₂ is bounded by exp₂/bound.exp₂/correct stating the extensional correctness of exp₂ as a corollary of exp₂/is-bounded.exp₂/asymptotic : given nat measured-via (λ n → n) , exp₂ ∈𝓞(λ n → n , n) stating that exp₂ is in 𝓞(n , n).Slow.exp₂ and Fast.exp₂ are extensionally equivalent, slow≡fast : ◯ (Slow.exp₂ ≡ Fast.exp₂).Examples.SortingFirst, we develop a common collection of definitions and theorems used in both sequential and parallel sorting.
Examples.Sorting.Comparable
Comparable describing the requirements for a type to be comparable, including h-cost, a hypothesis that each comparison is bounded by unit cost.
This serves as the cost model for sorting.Examples.Sorting.Core
Sorted and the permutation relation ↭ from agda-stdlib.
The predicate IsSort sort states that sort is a correct sorting algorithm.IsSort⇒≡, which states that any two correct sorting algorithms are extensionally equivalent.Examples.Sorting.SequentialHere, we use cost monoid ℕ-CostMonoid, tracking the total number of sequential steps incurred.
Examples.Sorting.Sequential.InsertionSort
sort implementing insertion sort.sort/correct : IsSort sort verifying the correctness of sort.sort≤sort/cost/closed stating that the cost of sort l is bounded by sort/cost/closed l = length l ².sort/asymptotic : given (list A) measured-via length , sort ∈𝓞(λ n → n ²) stating that sort is in 𝓞(n ²), where n is the length of the input list.Examples.Sorting.Sequential.MergeSort
Examples.Sorting.Sequential.MergeSort.Split
split, which splits a list in halves.split/correct verifying correctness properties of split.split≤split/cost stating that the cost of split l is bounded by zero, since splitting a list into halves requires no comparisons.Examples.Sorting.Sequential.MergeSort.Merge
merge, which merges a pair of sorted lists.merge/correct verifying correctness properties of merge.merge≤merge/cost/closed stating that the cost of merge (l₁ , l₂) is bounded by length l₁ + length l₂.sort implementing merge sort.sort/correct : IsSort sort verifying the correctness of sort.sort≤sort/cost/closed stating that the cost of sort l is bounded by sort/cost/closed l = ⌈log₂ length l ⌉ * length l.sort/asymptotic : given (list A) measured-via length , sort ∈𝓞(λ n → n * ⌈log₂ n ⌉) stating that sort is in 𝓞(n * ⌈log₂ n ⌉), where n is the length of the input list.Theorem isort≡msort : ◯ (ISort.sort ≡ MSort.sort) states that InsertionSort.sort and MergeSort.sort are extensionally equivalent.
Examples.AmortizedAmortized data structures, via coinduction.
Examples.Amortized.Simple provides an amortized implementation of a simple amortized stream abstract data type.Examples.Amortized.Queue provides an implementation of amortized queues.Examples.Amortized.DynamicArray provides an implementation of dynamically-growing arrays.The examples introduced in decalf are included in Examples.Decalf.
Examples.Decalf.BasicWe implement and analyze the basic double example.
Examples.Decalf.NondeterminismWe introduce the branch and fail primitives for nondeterminism and give the corresponding examples.
module QuickSort includes the nondeterministic quicksort algorithm using primitives from Examples.Sorting.Sequential.Core.module Lookup includes the list lookup function that fails on out-of-bounds indices.module Pervasive includes a simple example of pervasive (non-benign) nondeterminism.Examples.Decalf.ProbabilisticChoiceWe introduce the probabilistic flip primitive and give the corresponding example, showing how the cost of sublist is bounded by the binomial distribution.
Examples.Decalf.GlobalStateWe introduce the get and set primitives for global state and show a simple imperative program whose cost bound involves get and set.
Examples.Decalf.HigherOrderFunctionWe define the twice and map higher-order functions and analyze them under assumptions about their input costs.
Agda
99.4%
A cost-aware logical framework, embedded in Agda.
See the codeThe calf language is a cost-aware logical framework for studying quantitative aspects of functional programs.
This repository contains the Agda implementation of calf, as well as some case studies of varying complexity.
The source code may be viewed interactively with (semantic) syntax highlighting in the browser using the HTML files in the ./html directory.
These files were generated by running:
agda --html --html-dir=html src/index.agda
You may want to start by opening html/index.html.
To view a specific module M, open html/M.html in a web browser.
For example, open html/Examples.Sorting.Parallel.html to view the module Examples.Sorting.Parallel.
This implementation of calf has been tested using:
agda-stdlib v2.0 experimentalInstallation instructions may be found in INSTALL.md.
calf is parameterized by a cost monoid (ℂ, +, zero, ≤).
The formal definition, CostMonoid, is given in Algebra.Cost.
The definition of a parallel cost monoid (ℂ, ⊕, 𝟘, ⊗, 𝟙, ≤) is given, as well, as ParCostMonoid.
Some common cost monoids and parallel cost monoids are given in Algebra.Cost.Instances; for example, ℕ-CostMonoid simply tracks sequential cost.
Note that every ParCostMonoid induces a CostMonoid via the additive substructure (ℂ, ⊕, 𝟘, ≤).
The language itself is implemented via the following files, which are given in a dependency-respecting order.
The following modules are not parameterized:
Calf.Prelude contains commonly-used definitions.Calf.CBPV defines the basic dependent Call-By-Push-Value (CBPV) language, using Agda postulates and rewrite rules.Calf.Directed defines a preorder on each type, per the developments in decalf.Calf.Phase defines the phase distinction of extension and intension:
Calf.Phase.Core postulates a proposition, ext, for the extensional phase.Calf.Phase.Open defines the open/extensional modality ◯ for ext.Calf.Phase.Closed defines the closed/intensional modality ● for ext.Calf.Phase.Directed postulates the decalf law that under ext, inequality coincides with equality.Calf.Phase.Noninterference contains theorems related to the phase distinction/noninterference.The following modules are parameterized by a CostMonoid:
Calf.Step defines the computational effect step and the associated coherence laws via rewrite rules.The following modules are parameterized by a ParCostMonoid:
Calf.Parallel defines the parallel execution operation _∥_ whose cost structure is given by the product operation of a ParCostMonoid (i.e., _⊗_).In src/Calf/Data, we provide commonly-used data types.
The following modules are not parameterized and simply internalize the associated Agda types via the meta⁺ primitive:
Calf.Data.BoolCalf.Data.EqualityCalf.Data.ListCalf.Data.MaybeCalf.Data.NatCalf.Data.ProductCalf.Data.SumThe following modules define custom, calf-specific data types for cost analysis and are parameterized by a CostMonoid:
Calf.Data.IsBoundedG defines a generalized notion of cost bound, IsBoundedG, where a bound is a program of type F unit.
Additionally, it provides lemmas for proving the boundedness of common forms of computations.Calf.Data.IsBounded instantiates IsBoundedG for cost bounds of the form step (F unit) c (ret triv).Calf.Data.BoundedFunction defines cost-bounded functions using IsBounded.Calf.Data.BigO gives a definition of "big-O" asymptotic bounds via IsBounded.
In particular, an element of the type given A measured-via size , f ∈𝓞(g) (i.e., "given an input of type A and a size measure size on A, f is in 𝓞(g)) is a lower bound on input sizes n' and a constant multiplier k along with a proof h that for all inputs x with n' ≤ size x, f x is bounded by k multiples of g (size x), denoted n' ≤n⇒f[n]≤ k g[n]via h.We provide a variety of case studies in src/Examples.
Examples.Idmodule Easy
id that trivially returns its input.id/bound, which here is the same as id.id/is-bounded showing that id is bounded by id/bound.id/correct stating the extensional correctness of id as a corollary of id/is-bounded.id/asymptotic : given nat measured-via (λ n → n) , id ∈𝓞(λ n → 0) stating that id is in 𝓞(0).module Hard
id that reconstructs its input via induction.id/bound, which incurs n cost before returning n.id/is-bounded showing that id is bounded by id/bound.id/correct stating the extensional correctness of id as a corollary of id/is-bounded.id/asymptotic : given nat measured-via (λ n → n) , id ∈𝓞(λ n → n) stating that id is in 𝓞(n), where n is the input number.Easy.id and Hard.id are extensionally equivalent, easy≡hard : ◯ (Easy.id ≡ Hard.id), as a corollary of the id/correct proofs.Examples.TreeSumsum that sums the elements of a tree, incurring unit cost when performing each addition operation.
At each node, the recursive calls are computed in parallel.sum/bound, which incurs size t , depth t cost before returning the sum of the tree via a value-level function.sum/has-cost stating that sum and sum/bound are equivalent.sum/is-bounded stating that the cost of sum t is bounded by sum/bound, as a corollary of sum/has-cost.Examples.Exp2module Slow
exp₂ that computes the exponentation of two by its input by performing two identical recursive calls.
Since two identical recursive calls are made in parallel, the work is exponential, but the span is still linear.exp₂/bound, incurring 2 ^ n - 1 , n cost before returning result 2 ^ n.exp₂/is-bounded showing that exp₂ is bounded by exp₂/bound.exp₂/correct stating the extensional correctness of exp₂ as a corollary of exp₂/is-bounded.exp₂/asymptotic : given nat measured-via (λ n → n) , exp₂ ∈𝓞(λ n → 2 ^ n , n) stating that exp₂ is in 𝓞(2 ^ n , n).module Fast
exp₂ which computes the exponentation of two by its input via a standard recursive algorithm.exp₂/bound, incurring n , n cost before returning result 2 ^ n.exp₂/is-bounded showing that exp₂ is bounded by exp₂/bound.exp₂/correct stating the extensional correctness of exp₂ as a corollary of exp₂/is-bounded.exp₂/asymptotic : given nat measured-via (λ n → n) , exp₂ ∈𝓞(λ n → n , n) stating that exp₂ is in 𝓞(n , n).Slow.exp₂ and Fast.exp₂ are extensionally equivalent, slow≡fast : ◯ (Slow.exp₂ ≡ Fast.exp₂).Examples.SortingFirst, we develop a common collection of definitions and theorems used in both sequential and parallel sorting.
Examples.Sorting.Comparable
Comparable describing the requirements for a type to be comparable, including h-cost, a hypothesis that each comparison is bounded by unit cost.
This serves as the cost model for sorting.Examples.Sorting.Core
Sorted and the permutation relation ↭ from agda-stdlib.
The predicate IsSort sort states that sort is a correct sorting algorithm.IsSort⇒≡, which states that any two correct sorting algorithms are extensionally equivalent.Examples.Sorting.SequentialHere, we use cost monoid ℕ-CostMonoid, tracking the total number of sequential steps incurred.
Examples.Sorting.Sequential.InsertionSort
sort implementing insertion sort.sort/correct : IsSort sort verifying the correctness of sort.sort≤sort/cost/closed stating that the cost of sort l is bounded by sort/cost/closed l = length l ².sort/asymptotic : given (list A) measured-via length , sort ∈𝓞(λ n → n ²) stating that sort is in 𝓞(n ²), where n is the length of the input list.Examples.Sorting.Sequential.MergeSort
Examples.Sorting.Sequential.MergeSort.Split
split, which splits a list in halves.split/correct verifying correctness properties of split.split≤split/cost stating that the cost of split l is bounded by zero, since splitting a list into halves requires no comparisons.Examples.Sorting.Sequential.MergeSort.Merge
merge, which merges a pair of sorted lists.merge/correct verifying correctness properties of merge.merge≤merge/cost/closed stating that the cost of merge (l₁ , l₂) is bounded by length l₁ + length l₂.sort implementing merge sort.sort/correct : IsSort sort verifying the correctness of sort.sort≤sort/cost/closed stating that the cost of sort l is bounded by sort/cost/closed l = ⌈log₂ length l ⌉ * length l.sort/asymptotic : given (list A) measured-via length , sort ∈𝓞(λ n → n * ⌈log₂ n ⌉) stating that sort is in 𝓞(n * ⌈log₂ n ⌉), where n is the length of the input list.Theorem isort≡msort : ◯ (ISort.sort ≡ MSort.sort) states that InsertionSort.sort and MergeSort.sort are extensionally equivalent.
Examples.AmortizedAmortized data structures, via coinduction.
Examples.Amortized.Simple provides an amortized implementation of a simple amortized stream abstract data type.Examples.Amortized.Queue provides an implementation of amortized queues.Examples.Amortized.DynamicArray provides an implementation of dynamically-growing arrays.The examples introduced in decalf are included in Examples.Decalf.
Examples.Decalf.BasicWe implement and analyze the basic double example.
Examples.Decalf.NondeterminismWe introduce the branch and fail primitives for nondeterminism and give the corresponding examples.
module QuickSort includes the nondeterministic quicksort algorithm using primitives from Examples.Sorting.Sequential.Core.module Lookup includes the list lookup function that fails on out-of-bounds indices.module Pervasive includes a simple example of pervasive (non-benign) nondeterminism.Examples.Decalf.ProbabilisticChoiceWe introduce the probabilistic flip primitive and give the corresponding example, showing how the cost of sublist is bounded by the binomial distribution.
Examples.Decalf.GlobalStateWe introduce the get and set primitives for global state and show a simple imperative program whose cost bound involves get and set.
Examples.Decalf.HigherOrderFunctionWe define the twice and map higher-order functions and analyze them under assumptions about their input costs.
Agda
99.4%