These are my second impressions of the Agda programming language after writing (or trying to write) a few simple Agda programs.
If you only want the highlights, just read the Executive Summary. If you also want to see supporting material and notes, continue reading further.
Agda is simultaneously touted as (1) a dependently-typed programming language and (2) a proof assistant. I find that Agda is a considerably better proof assistant than it is a programming language at this time.
A few issues I found when trying to use Agda as a programming language to get real work done:
IO module. Yet another abstract academic construct.
I can work around this by restricting myself to the IO.Primitive module. This module directly mirrors Haskell’s IO system and thus only requires knowledge of monads.
div and mod operators for the default implementation of natural numbers (Data.Nat) run in linear time relative to the magnitude of the divisor.1
At least ≤?, ≟, +, and * appear to be logarithmic- or constant-time.
The “Hello World” program compiles to a 14 MB binary. Unacceptably large.
The “Hello World” program takes 3 seconds to compile. Unacceptably slow.
The following are all the simple programs I’ve written with Agda so far.
A manual reimplementation of natural numbers (i.e. non-negative integers) just so that I can get my Agda environment up and running.3
module Peano where
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
_+_ : ℕ → ℕ → ℕ
zero + zero = zero
zero + n = n
(suc n) + n' = suc (n + n')
A program that prints the number 5.
No more reimplementing silly stuff like numbers. Let’s use the standard library.
module HelloNumber where
-- Agda standard library 0.7
open import Data.Nat
open import Data.Nat.Show using (show)
open import Data.Unit using (⊤)
open import IO
import IO.Primitive as Prim
five : ℕ
five = 5
main' : IO ⊤
main' = putStrLn (show five)
main : Prim.IO ⊤
main = run main'
IO ⊤ (which would be equivalent to the Haskell IO ()). Instead it has type Prim.IO ⊤ which can be obtained by passing an IO ⊤ through the IO.run function.Now let’s try printing strings, which should allow writing the standard “Hello world” program.
module HelloWorld where
-- Agda standard library 0.7
open import Data.String
open import Data.Unit using (⊤)
open import IO
import IO.Primitive as Prim
greeting : String
greeting = "Hello World"
main' : IO ⊤
main' = putStrLn greeting
main : Prim.IO ⊤
main = run main'
Now let’s try stringing multiple IO actions together.
module PrintTwoThings where
-- Agda standard library 0.7
open import Coinduction using (♯_)
open import Data.String
open import Data.Unit using (⊤)
open import IO
import IO.Primitive as Prim
doThis : IO ⊤
doThis = putStr "Everybody say coinductive deep embedding! "
thenThat : IO ⊤
thenThat = putStrLn "Coinductive deep embedding!"
main' : IO ⊤
main' = (♯ doThis) >> (♯ thenThat)
main : Prim.IO ⊤
main = run main'
>> operator used to join two IO actions isn’t IO A -> IO B -> IO B. Rather it is ∞ (IO A) -> ∞ (IO B) -> IO B.
∞ type before I can chain them together.∞ type? No idea. It has something to do with “coinduction”, which Wikipedia failed to explain to me coherently.
∞ A represents a lazy unevaluated computation of type A that might not terminate when it is actually evaluated.Coinduction library, I can make a ∞ using the ♯ operator which takes a value of any type A and makes a ∞ A out of it.action1 >> action2 I need ♯ action1 >> ♯ action2Read a line from standard input. Write it back to the screen.
module EchoInput where
-- Agda standard library 0.7
open import Data.String
open import Foreign.Haskell using (Unit)
open import IO.Primitive
postulate
getLine : IO Costring
{-# COMPILED getLine getLine #-}
doThis : IO Costring
doThis = getLine
thenThat : Costring → IO Unit
thenThat = λ s → putStrLn s
main : IO Unit
main = doThis >>= thenThat
stdin itself. Unbelievable.
Primitive.IO module instead of the regular IO module. I no longer need to use ∞ or ♯ either, which is nice.module PromptForName where
-- Agda standard library 0.7
open import Data.String
open import Foreign.Haskell using (Unit)
open import IO.Primitive
postulate
getLine : IO Costring
{-# COMPILED getLine getLine #-}
main : IO Unit
main =
putStrLn (toCostring "What's your name?") >>= (λ _ →
getLine >>= (λ s →
putStr (toCostring "Hello ") >>= (λ _ →
putStrLn s)))
Primitive.IO defines the >>= operator but not >>. Asymmetric with the regular IO module.Costring objects. So a string literal has to be passed to toCostring to be used.Read a line from standard input. Reverse it in Agda. Write the reversed line back to the screen.
module EchoInputReverse where
-- Agda standard library 0.7
open import Data.List using (reverse)
open import Data.String
open import Foreign.Haskell using (Unit)
open import IO.Primitive
postulate
getLine : IO String
{-# COMPILED getLine getLine #-}
main : IO Unit
main =
getLine >>= (λ s →
return (toCostring (fromList (reverse (toList s)))) >>= (λ s' →
putStrLn s'))
Costring. I can’t reverse it or do anything useful with it without first converting it to a String. But there is no function in the standard library to do this conversion, even as a runtime cast.String maps to the Agda type Costring in the general case, it is still possible to postulate that it maps directly to the Agda type String for certain functions where you can assert that the string won’t be infinite.
Costring and String datatypes. This is not documented.getLine as being a finite string when it may not in fact be. See discussion at StackOverflow.Prompt the user for a number, parse it, and output it. If an invalid number was input, output zero.
module ParseInt where
-- Agda standard library 0.7
open import Data.Char
open import Data.List
open import Data.Maybe
open import Data.Nat
open import Data.Nat.Show
open import Data.String
open import Foreign.Haskell using (Unit)
open import IO.Primitive
postulate
getLine : IO String
{-# COMPILED getLine getLine #-}
parseInt : String → Maybe ℕ
parseInt s =
then? (unwrap (parseDigits s)) (λ s' →
just (digitsToℕ s'))
where
parseDigits : String → List (Maybe ℕ)
parseDigits s = map toDigit (toList s) where
toDigit : Char → Maybe ℕ
toDigit '0' = just 0
toDigit '1' = just 1
toDigit '2' = just 2
toDigit '3' = just 3
toDigit '4' = just 4
toDigit '5' = just 5
toDigit '6' = just 6
toDigit '7' = just 7
toDigit '8' = just 8
toDigit '9' = just 9
toDigit _ = nothing
-- TODO: There's probably a standard-library function that does this
-- but I can't find it.
unwrap : List (Maybe ℕ) → Maybe (List ℕ)
unwrap xs = unwrap' (just []) xs where
unwrap' : Maybe (List ℕ) → List (Maybe ℕ) → Maybe (List ℕ)
unwrap' (just xs) (just y ∷ ys) = unwrap' (just (Data.List._++_ xs [ y ])) ys -- makes unwrap O(N^2)!
unwrap' (just xs) (nothing ∷ _) = nothing
unwrap' (just xs) [] = just xs
unwrap' nothing _ = nothing
-- TODO: Look for a monadic _>>=_ that does the same thing
then? : {A : Set} → {B : Set} → Maybe A → (A → Maybe B) → Maybe B
then? nothing _ = nothing
then? (just r1) op2 = op2 r1
digitsToℕ : List ℕ → ℕ
digitsToℕ xs = digitsToℕ' (reverse xs) where
digitsToℕ' : List ℕ → ℕ
digitsToℕ' [] = 0
digitsToℕ' (x ∷ xs) = x + (10 * (digitsToℕ' xs))
ℕ? : Maybe ℕ -> ℕ
ℕ? (just x) = x
ℕ? nothing = 0
main : IO Unit
main =
getLine >>= (λ s →
return (show (ℕ? (parseInt s))) >>= (λ s' →
putStrLn (toCostring s')))
Actual:
List Char !=< _A_10 s → _B_11 s of type Set
when checking that the expression toList s has type
_A_10 s → _B_11 s
Better:
Expected type (A → B) but found type (List Char).
show function that converts a natural number to a string has quadratic performance!
+, *, mod} is not constant time. Not cool.
Indeed the _divMod_ operator runs in linear time with respect to the magnitude of the dividend. Madness.
= or == but rather ≟.
Bool either. Instead it returns a fancy boolean called a Decidable with an embedded equality proof.Decidable can be converted to a regular boolean with the ⌊_⌋ operator. Weird.Data.Nat take linear memory to represent rather than logarithmic.
Data.Bin which represents natural numbers using binary, but most of the standard library doesn’t use it.There is another module called Data.Bin which provides a representation of natural numbers which uses an efficient binary representation. But most of the standard library does not use it.↩
I found configuring Emacs for Agda to be nontrivial. I had to learn enough Emacs Lisp to debug my .emacs configuration file.↩
Kudos to Learn You an Agda > Hello, Peano for helping me implement this first Agda program.↩