I am learning how "typeclasses" are implemented in Agda. As an example, I am trying to implement Roman numerals whose composition with # would type-check.
I am not clear why Agda complains there is no instance for Join (Roman _ _) (Roman _ _) _ - clearly, it couldn't work out what natural numbers to substitute there.
Is there a nicer way to introduce Roman numbers that don't have "constructor" form? I have a constructor "madeup", which probably would need to be private, to be sure I have only "trusted" ways to construct other Roman numbers through Join.
module Romans where
data ℕ : Set where
zero : ℕ
succ : ℕ → ℕ
infixr 4 _+_ _*_ _#_
_+_ : ℕ → ℕ → ℕ
zero + x = x
succ y + x = succ (y + x)
_*_ : ℕ → ℕ → ℕ
zero * x = zero
succ y * x = x + (y * x)
one = succ zero
data Roman : ℕ → ℕ → Set where
i : Roman one one
{- v : Roman one five
x : Roman ten one
... -}
madeup : ∀ {a b} (x : Roman a b) → (c : ℕ) → Roman a c
record Join (A B C : Set) : Set where
field jo : A → B → C
two : ∀ {a} → Join (Roman a one) (Roman a one) (Roman a (one + one))
two = record { jo = λ l r → madeup l (one + one) }
_#_ : ∀ {a b c d C} → {{j : Join (Roman a b) (Roman c d) C}} → Roman a b → Roman c d → C
(_#_) {{j}} = Join.jo j
-- roman = (_#_) {{two}} i i -- works
roman : Roman one (one + one)
roman = {! i # i!} -- doesn't work
Clearly, if I specify the implicit explicitly, it works - so I am confident it is not the type of the function that is wrong.
Your example works fine in development version of Agda. If you are using a version older than 2.3.2, this passage from release notes could clarify why it doesn't compile for you:
* Instance arguments resolution will now consider candidates which
still expect hidden arguments. For example:
record Eq (A : Set) : Set where
field eq : A → A → Bool
open Eq {{...}}
eqFin : {n : ℕ} → Eq (Fin n)
eqFin = record { eq = primEqFin }
testFin : Bool
testFin = eq fin1 fin2
The type-checker will now resolve the instance argument of the eq
function to eqFin {_}. This is only done for hidden arguments, not
instance arguments, so that the instance search stays non-recursive.
(source)
That is, before 2.3.2, the instance search would completly ignore your two instance because it has a hidden argument.
While instance arguments behave a bit like type classes, note that they will only commit to an instance if there's only one type correct version in scope and they will not perform a recursive search:
Instance argument resolution is not recursive. As an example,
consider the following "parametrised instance":
eq-List : {A : Set} → Eq A → Eq (List A)
eq-List {A} eq = record { equal = eq-List-A }
where
eq-List-A : List A → List A → Bool
eq-List-A [] [] = true
eq-List-A (a ∷ as) (b ∷ bs) = equal a b ∧ eq-List-A as bs
eq-List-A _ _ = false
Assume that the only Eq instances in scope are eq-List and eq-ℕ.
Then the following code does not type-check:
test = equal (1 ∷ 2 ∷ []) (3 ∷ 4 ∷ [])
However, we can make the code work by constructing a suitable
instance manually:
test′ = equal (1 ∷ 2 ∷ []) (3 ∷ 4 ∷ [])
where eq-List-ℕ = eq-List eq-ℕ
By restricting the "instance search" to be non-recursive we avoid
introducing a new, compile-time-only evaluation model to Agda.
(source)
Now, as for the second part of the question: I'm not exactly sure what your final goal is, the structure of the code ultimately depends on what you want to do once you construct the number. That being said, I wrote down a small program that allows you to enter roman numerals without going through the explicit data type (forgive me if I didn't catch your intent clearly):
A roman numeral will be a function which takes a pair of natural numbers - the value of previous numeral and the running total. If it's smaller than previous numeral, we'll subtract its value from the running total, otherwise we add it up. We return the new running total and value of current numeral.
Of course, this is far from perfect, because there's nothing to prevent us from typing I I X and we end up evaluating this as 10. I leave this as an exercise for the interested reader. :)
Imports first (note that I'm using the standard library here, if you do not want to install it, you can just copy the definition from the online repo):
open import Data.Bool
open import Data.Nat
open import Data.Product
open import Relation.Binary
open import Relation.Nullary.Decidable
This is our numeral factory:
_<?_ : Decidable _<_
m <? n = suc m ≤? n
makeNumeral : ℕ → ℕ × ℕ → ℕ × ℕ
makeNumeral n (p , c) with ⌊ n <? p ⌋
... | true = n , c ∸ n
... | false = n , c + n
And we can make a few numerals:
infix 500 I_ V_ X_
I_ = makeNumeral 1
V_ = makeNumeral 5
X_ = makeNumeral 10
Next, we have to apply this chain of functions to something and then extract the running total. This is not the greatest solution, but it looks nice in code:
⟧ : ℕ × ℕ
⟧ = 0 , 0
infix 400 ⟦_
⟦_ : ℕ × ℕ → ℕ
⟦ (_ , c) = c
And finally:
test₁ : ℕ
test₁ = ⟦ X I X ⟧
test₂ : ℕ
test₂ = ⟦ X I V ⟧
Evaluating test₁ via C-c C-n gives us 19, test₂ then 14.
Of course, you can move these invariants into the data type, add new invariants and so on.
Related
I am a noob in agda and reading http://www.cse.chalmers.se/~ulfn/papers/afp08/tutorial.pdf. My shallow knowledge somehow finds dot pattern not quite necessary. For example,
data Image_∋_ {A B : Set}(f : A → B) : B → Set where
im : (x : A) → Image f ∋ f x
inv : {A B : Set}(f : A → B)(y : B) → Image f ∋ y → A
inv f .(f x) (im x) = x
I find inv can well be defined as
inv : {A B : Set}(f : A → B)(y : B) → Image f ∋ y → A
inv _ _ (im x) = x
because from the types, we've already known y is an image of f for some x, so it cannot possibly go wrong.
Another example is
data _==_ {A : Set}(x : A) : A → Set where
refl : x == x
data _≠_ : ℕ → ℕ → Set where
z≠s : {n : ℕ} → zero ≠ suc n
s≠z : {n : ℕ} → suc n ≠ zero
s≠s : {n m : ℕ} → n ≠ m → suc n ≠ suc m
data Equal? (n m : ℕ) : Set where
eq : n == m → Equal? n m
neq : n ≠ m → Equal? n m
equal? : (n m : ℕ) → Equal? n m
equal? zero zero = eq refl
equal? zero (suc _) = neq z≠s
equal? (suc _) zero = neq s≠z
equal? (suc n') (suc m') with equal? n' m'
... | eq refl = eq refl
... | neq n'≠m' = neq (s≠s n'≠m')
consider equal? function, the second last line is written in the paper as (suc n') (suc .n') | eq refl = eq refl. Again, eq refl in with construct has provided a proof, for these two values being the same, so why do I bother writing them out using dot pattern?
I am more familiar with coq, and I am not aware of similar thing in coq. Am I missing something here?
In Coq you write the pattern-matches explicitly whereas Agda's equation-based approaches forces the typechecker to reconstruct a case-tree which ought to correspond to what you wrote.
Dotted-patterns help the typechecker see that a given pattern was not the product of a match but rather forced by a match on one of the other arguments (e.g.: a match on a Vec Bool n will force the value of n, or a match on an equality proof will, as you've observed, force some variables to be the same).
They're not always necessary and, in fact, some have been slowly made optional as you can see in the CHANGELOG for version 2.5.3:
Dot patterns.
The dot in front of an inaccessible pattern can now be skipped if the pattern consists entirely of constructors or literals. For example:
I defined the natural numbers as usually:
data Nat : Set where
zero : Nat
succ : Nat → Nat
and i.e the number one should be
one : Nat
one = succ zero
Later on, we can define the image datatype,
data Image_∋_ {A B : Set} (f : A → B) : B -> Set where
im : (x : A) → Image f ∋ (f x)
And to prove something like "the one is in the Image of the successor function" I wrote:
one-succ : Image succ ∋ one
one-succ = im zero
I would like to have the following.
Define the predecessor function that not allowed zero as an input just its successors. So the next is not valid.
pred : Nat → Nat
pred zero = zero
pred (succ n) = n
I would like to have a variable called Z⁺ representing the positive numbers but using in its definition the image of successor function (Image_∋_ data type defined above).
Image f ∋ y reads as "there is some x such that y ≡ f x". Pattern matching on Image f ∋ y by im x reveals the x.
So an element of type Image succ ∋ n is a proof that n is of the form succ m where m is carried inside that element. Hence the definition is simply
ipred : ∀ {n} → Image succ ∋ n → Nat
ipred (im m) = m
because n ≡ succ m and the predecessor of succ m is m.
It reads nicer if we rename im to isucc:
open Image_∋_ renaming (im to isucc)
ipred : ∀ {n} → Image succ ∋ n → Nat
ipred (isucc m) = m
Another way of writing the same thing is
data Image_∋_ {A B : Set} : (A → B) → B → Set where
_·_ : (f : A → B) → (x : A) → Image f ∋ f x
pred : ∀ {n} → Image succ ∋ n → Nat
pred (.succ · m) = m
Here f in Image f ∋ y is an index rather than a parameter, so _·_ (previously im) now receives two arguments: a function and its argument. It's not possible to pattern match on functions, but .succ is an "irrefutable pattern", i.e. it says "f can be nothing, but succ".
Nat⁺ can be defined as
data Nat⁺ : Set where
nat⁺ : ∀ {n} → Image succ ∋ n → Nat⁺
succ⁺ receives a natural number (implicitly) and a proof that this number is of the form succ m for some m.
You can always take the predecessor of a positive natural number:
pred⁺ : Nat⁺ → Nat
pred⁺ (nat⁺ (im m)) = m
But since Nat⁺ is a non-indexed one-constructor data type, it can be defined as a record:
record Nat⁺ : Set where
constructor nat⁺
field
{pred⁺} : Nat
image : Image succ ∋ pred⁺
open Nat⁺
open Nat⁺ introduces pred⁺ : Nat⁺ → Nat in scope.
Consider the following self-contained program:
module Test where
record Σ {A : Set} (B : A -> Set) : Set where
constructor _,_
field
fst : A
snd : B fst
open Σ public
infixr 0 _,_
_×_ : Set -> Set -> Set
A × B = Σ (\ (_ : A) -> B)
infixr 10 _×_
f : {A B : Set} → A × B → A
f x = {!!}
If you C-c C-l in the goal, you get:
Goal: .A
————————————————————————————————————————————————————————————
x : Σ (λ _ → .B)
.B : Set
.A : Set
i.e. you see the underlying sigma, and the type of the binder of the lambda is hidden. This is pretty annoying. Is there a way to make Agda show the type of binders that don't bind names by default?
This is Agda 2.3.2.2.
Agda 2.4.3 displays x : .A × .B.
You can use the abstract keyword:
abstract
_×_ : Set -> Set -> Set
A × B = Σ (\ (_ : A) -> B)
fst' : ∀ {A B} -> A × B -> A
fst' (x , y) = x
snd' : ∀ {A B} -> A × B -> B
snd' (x , y) = y
But that's definitely overkill (and it looks like pattern synonyms do not work in abstract blocks).
It's much more annoying to me, that Agda doesn't want to reduce functions like flip and especially _∘_, but unfolds some functions with really long definitions. Agda needs a mechanism for fixing these problems.
It appears that this problem has been fixed in the latest version of Agda, so you should upgrade.
Assume I have a value x : A and I want to define a set containing only x.
This is what I tried:
open import Data.Product
open import Relation.Binary.PropositionalEquality
-- Singleton x is the set that only contains x. Its values are tuples containing
-- a value of type A and a proof that this value is equal to x.
Singleton : ∀ {ℓ} {A : Set ℓ} → (x : A) → Set ℓ
Singleton {A = A} x = Σ[ y ∈ A ] (y ≡ x)
-- injection
singleton : ∀ {ℓ} {A : Set ℓ} → (x : A) → Singleton x
singleton x = x , refl
-- projection
fromSingleton : ∀ {ℓ} {A : Set ℓ} {x : A} → Singleton x → A
fromSingleton s = proj₁ s
Is there a better way to do it?
An example for why I want this: If you have a monoid over some set A, then you can form a category with A as the only object. To express that in Agda you need a way to write "the set containing only A".
I think this is a very good way to do it. Usually, when you want to create a "subset" of a type, it looks like:
postulate
A : Set
P : A → Set
record Subset : Set where
field
value : A
prop : P value
However, this might not be a subset in the sense that it can actually contain more elements than the original type. That is because prop might have more propositionally different values. For example:
open import Data.Nat
data ℕ-prop : ℕ → Set where
c1 : ∀ n → ℕ-prop n
c2 : ∀ n → ℕ-prop n
record ℕ-Subset : Set where
field
value : ℕ
prop : ℕ-prop value
And suddenly, the subset has twice as many elements as the original type. This example is a bit contrived, I agree, but imagine you had a subset relation on sets (sets from set theory). Something like this is actually fairly possible:
sub₁ : {1, 2} ⊆ {1, 2, 3, 4}
sub₁ = drop 3 (drop 4 same)
sub₂ : {1, 2} ⊆ {1, 2, 3, 4}
sub₂ = drop 4 (drop 3 same)
The usual approach to this problem is to use irrelevant arguments:
record Subset : Set where
field
value : A
.prop : P value
This means that two values of type Subset are equal if they have the same value, the prop field is irrelevant to the equality. And indeed:
record Subset : Set where
constructor sub
field
value : A
.prop : P value
prop-irr : ∀ {a b} {p : P a} {q : P b} →
a ≡ b → sub a p ≡ sub b q
prop-irr refl = refl
However, this is more of a guideline, because your representation doesn't suffer from this problem. This is because the implementation of pattern matching in Agda implies axiom K:
K : ∀ {a p} {A : Set a} (x : A) (P : x ≡ x → Set p) (h : x ≡ x) →
P refl → P h
K x P refl p = p
Well, this doesn't tell you much. Luckily, there's another property that is equivalent to axiom K:
uip : ∀ {a} {A : Set a} {x y : A} (p q : x ≡ y) → p ≡ q
uip refl refl = refl
This tells us that there's only one way in which two elements can be equal, namely refl (uip means uniqueness of identity proofs).
This means that when you use propositional equality to make a subset, you're getting a true subset.
Let's make this explicit:
isSingleton : ∀ {ℓ} → Set ℓ → Set _
isSingleton A = Σ[ x ∈ A ] (∀ y → x ≡ y)
isSingleton A expresses the fact that A contains only one element, up to propositonal equality. And indeed, Singleton x is a singleton:
Singleton-isSingleton : ∀ {ℓ} {A : Set ℓ} (x : A) →
isSingleton (Singleton x)
Singleton-isSingleton x = (x , refl) , λ {(.x , refl) → refl}
Interestingly, this also works without axiom K. If you put {-# OPTIONS --without-K #-} pragma at the top of your file, it will still compile.
You can define a record without projections:
record Singleton {α} {A : Set α} (x : A) : Set α where
fromSingleton : ∀ {α} {A : Set α} {x : A} -> Singleton x -> A
fromSingleton {x = x} _ = x
singleton : ∀ {α} {A : Set α} -> (x : A) -> Singleton x
singleton _ = _
Or equalently
record Singleton {α} {A : Set α} (x : A) : Set α where
fromSingleton = x
open Singleton public
singleton : ∀ {α} {A : Set α} -> (x : A) -> Singleton x
singleton _ = _
Now you can use it like this:
open import Relation.Binary.PropositionalEquality
open import Data.Nat
f : Singleton 5 -> ℕ
f x = fromSingleton x
test : f (singleton 5) ≡ 5
test = refl
test' : f _ ≡ 5
test' = refl
And this is rejected:
fail : f (singleton 4) ≡ 4
fail = ?
The error:
4 != 5 of type ℕ
when checking that the expression singleton 4 has type Singleton 5
It can be defined as an indexed datatype:
data Singleton {ℓ : _} {A : Set ℓ} : A -> Set where
singleton : (a : A) -> Singleton a
This is analogous to how propositional equality a ≡ b is indexed by two particular elements a and b, or how Vector X n is indexed by a particular n ∈ ℕ.
I am trying to parse a string with natural numbers in Agda.
e.g., the result of stringListToℕ "1,2,3" should be Just (1 ∷ 2 ∷ 3 ∷ [])
My current code is not quite right or by any means nice, but it works.
However it returns the type:
Maybe (List (Maybe ℕ))
The Question is:
How to implement the function stringListToℕ in a nice way (compared to my code);
it should have the type Maybe (List ℕ)
(optional, not important) How can I convert the type Maybe (List (Maybe ℕ)) to Maybe (List ℕ)?
My Code:
charToℕ : Char → Maybe ℕ
charToℕ '0' = just 0
charToℕ '1' = just 1
charToℕ '2' = just 2
charToℕ '3' = just 3
charToℕ '4' = just 4
charToℕ '5' = just 5
charToℕ '6' = just 6
charToℕ '7' = just 7
charToℕ '8' = just 8
charToℕ '9' = just 9
charToℕ _ = nothing
stringToℕ' : List Char → (acc : ℕ) → Maybe ℕ
stringToℕ' [] acc = just acc
stringToℕ' (x ∷ xs) acc = charToℕ x >>= λ n → stringToℕ' xs ( 10 * acc + n )
stringToℕ : String → Maybe ℕ
stringToℕ s = stringToℕ' (toList s) 0
isComma : Char → Bool
isComma h = h Ch.== ','
notComma : Char → Bool
notComma ',' = false
notComma _ = true
{-# NO_TERMINATION_CHECK #-}
split : List Char → List (List Char)
split [] = []
split s = l ∷ split (drop (length(l) + 1) s)
where l : List Char
l = takeWhile notComma s
isNothing' : Maybe ℕ → Bool
isNothing' nothing = true
isNothing' _ = false
isNothing : List (Maybe ℕ) → Bool
isNothing l = any isNothing' l
-- wrong type, should be String -> Maybe (List N)
stringListToℕ : String → Maybe (List (Maybe ℕ))
stringListToℕ s = if (isNothing res) then nothing else just res
where res : List (Maybe ℕ)
res = map stringToℕ (map fromList( split (Data.String.toList s)))
test1 = stringListToℕ "1,2,3"
-- => just (just 1 ∷ just 2 ∷ just 3 ∷ [])
EDIT
I tried to write a conversion function using from-just, but this gives a error when type checking:
conv : Maybe (List (Maybe ℕ)) → Maybe (List ℕ)
conv (just xs) = map from-just xs
conv _ = nothing
the error is:
Cannot instantiate the metavariable _143 to solution
(Data.Maybe.From-just (_145 xs) x) since it contains the variable x
which is not in scope of the metavariable or irrelevant in the
metavariable but relevant in the solution
when checking that the expression from-just has type
Maybe (_145 xs) → _143 xs
I took the liberty of rewriting your split function into something more general which also works with the termination check:
open import Data.List
open import Data.Product
open import Function
splitBy : ∀ {a} {A : Set a} → (A → Bool) → List A → List (List A)
splitBy {A = A} p = uncurry′ _∷_ ∘ foldr step ([] , [])
where
step : A → List A × List (List A) → List A × List (List A)
step x (cur , acc) with p x
... | true = x ∷ cur , acc
... | false = [] , cur ∷ acc
Also, stringToℕ "" should most likely be nothing, unless you really want:
stringListToℕ "1,,2" ≡ just (1 ∷ 0 ∷ 2 ∷ [])
Let's rewrite it a bit (note that helper is your original stringToℕ function):
stringToℕ : List Char → Maybe ℕ
stringToℕ [] = nothing
stringToℕ list = helper list 0
where {- ... -}
And now we can put it all together. For simplicity I'm using List Char everywhere, sprinkle with fromList/toList as necessary):
let x1 = s : List Char -- start
let x2 = splitBy notComma x1 : List (List Char) -- split at commas
let x3 = map stringToℕ x2 : List (Maybe ℕ) -- map our ℕ-conversion
let x4 = sequence x3 : Maybe (List ℕ) -- turn Maybe inside out
You can find sequence in Data.List; we also have to specify which monad instance we want to use. Data.Maybe exports its monad instance under the name monad. Final code:
open import Data.Char
open import Data.List
open import Data.Maybe
open import Data.Nat
open import Function
stringListToℕ : List Char → Maybe (List ℕ)
stringListToℕ = sequence Data.Maybe.monad ∘ map stringToℕ ∘ splitBy notComma
And a small test:
open import Relation.Binary.PropositionalEquality
test : stringListToℕ ('1' ∷ '2' ∷ ',' ∷ '3' ∷ []) ≡ just (12 ∷ 3 ∷ [])
test = refl
Considering your second question: there are many ways to turn a Maybe (List (Maybe ℕ)) into a Maybe (List ℕ), for example:
silly : Maybe (List (Maybe ℕ)) → Maybe (List ℕ)
silly _ = nothing
Right, this doesn't do much. We'd like the conversion to preserve the elements if they are all just. isNothing already does this part of checking but it cannot get rid of the inner Maybe layer.
from-just could work since we know that when we use it, all elements of the List must be just x for some x. The problem is that conv in its current form is just wrong - from-just works as a function of type Maybe A → A only when the Maybe value is just x! We could very well do something like this:
test₂ : Maybe (List ℕ)
test₂ = conv ∘ just $ nothing ∷ just 1 ∷ []
And since from-list behaves as a Maybe A → ⊤ when given nothing, we are esentially trying to construct a heterogeneous list with elements of type both ⊤ and ℕ.
Let's scrap this solution, I'll show a much simpler one (in fact, it should resemble the first part of this answer).
We are given a Maybe (List (Maybe ℕ)) and we gave two goals:
take the inner List (Maybe ℕ) (if any), check if all elements are just x and in this case put them all into a list wrapped in a just, otherwise return nothing
squash the doubled Maybe layer into one
Well, the second point sounds familiar - that's something monads can do! We get:
join : {A : Set} → Maybe (Maybe A) → Maybe A
join mm = mm >>= λ x → x
where
open RawMonad Data.Maybe.monad
This function could work with any monad but we'll be fine with Maybe.
And for the first part, we need a way to turn a List (Maybe ℕ) into a Maybe (List ℕ) - that is, we want to swap the layers while propagating the possible error (i.e. nothing) into the outer layer. Haskell has specialized typeclass for this kind of stuff (Traversable from Data.Traversable), this question has some excellent answers if you'd like to know more. Basically, it's all about rebuilding the structure while collecting the "side effects". We'll be fine with the version that works just for Lists and we're back at sequence again.
There's still one piece missing, let's look at what we have so far:
sequence-maybe : List (Maybe ℕ) → Maybe (List ℕ)
sequence-maybe = sequence Data.Maybe.monad
join : Maybe (Maybe (List ℕ)) → Maybe (List ℕ)
-- substituting A with List ℕ
We need to apply sequence-maybe inside one Maybe layer. That's where the Maybe functor instance comes into play (you could do it with a monad instance alone, but it's more convenient). With this functor instance, we can lift an ordinary function of type a → b into a function of type Maybe a → Maybe b. And finally:
open import Category.Functor
open import Data.Maybe
final : Maybe (List (Maybe ℕ)) → Maybe (List ℕ)
final mlm = join (sequence-maybe <$> mlm)
where
open RawFunctor functor
I had a go at it trying not to be clever and using simple recursive functions rather than stdlib magic. parse xs m ns parses xs by recording the (possibly empty) prefix already read in m while keeping the list of numbers already parsed in the accumulator ns.
If a parsing failure happens (non recognized character, two consecutive ,, etc.) everything is thrown away and we return nothing.
module parseList where
open import Data.Nat
open import Data.List
open import Data.Maybe
open import Data.Char
open import Data.String
isDigit : Char → Maybe ℕ
isDigit '0' = just 0
isDigit '1' = just 1
isDigit '2' = just 2
isDigit '3' = just 3
isDigit _ = nothing
attach : Maybe ℕ → ℕ → ℕ
attach nothing n = n
attach (just m) n = 10 * m + n
Quote : List Char → Maybe (List ℕ)
Quote xs = parse xs nothing []
where
parse : List Char → Maybe ℕ → List ℕ → Maybe (List ℕ)
parse [] nothing ns = just ns
parse [] (just n) ns = just (n ∷ ns)
parse (',' ∷ tl) (just n) ns = parse tl nothing (n ∷ ns)
parse (hd ∷ tl) m ns with isDigit hd
... | nothing = nothing
... | just n = parse tl (just (attach m n)) ns
stringListToℕ : String → Maybe (List ℕ)
stringListToℕ xs with Quote (toList xs)
... | nothing = nothing
... | just ns = just (reverse ns)
open import Relation.Binary.PropositionalEquality
test : stringListToℕ ("12,3") ≡ just (12 ∷ 3 ∷ [])
test = refl
Here is the Code from Vitus as a running example that uses the Agda Prelude
module Parse where
open import Prelude
-- Install Prelude
---- clone this git repo:
---- https://github.com/fkettelhoit/agda-prelude
-- Configure Prelude
--- press Meta/Alt and the letter X together
--- type "customize-group" (i.e. in the mini buffer)
--- type "agda2"
--- expand the Entry "Agda2 Include Dirs:"
--- add the directory
open import Data.Product using (uncurry′)
open import Data.Maybe using ()
open import Data.List using (sequence)
splitBy : ∀ {a} {A : Set a} → (A → Bool) → List A → List (List A)
splitBy {A = A} p = uncurry′ _∷_ ∘ foldr step ([] , [])
where
step : A → List A × List (List A) → List A × List (List A)
step x (cur , acc) with p x
... | true = x ∷ cur , acc
... | false = [] , cur ∷ acc
charsToℕ : List Char → Maybe ℕ
charsToℕ [] = nothing
charsToℕ list = stringToℕ (fromList list)
notComma : Char → Bool
notComma c = not (c == ',')
-- Finally:
charListToℕ : List Char → Maybe (List ℕ)
charListToℕ = Data.List.sequence Data.Maybe.monad ∘ map charsToℕ ∘ splitBy notComma
stringListToℕ : String → Maybe (List ℕ)
stringListToℕ = charListToℕ ∘ toList
-- Test
test1 : charListToℕ ('1' ∷ '2' ∷ ',' ∷ '3' ∷ []) ≡ just (12 ∷ 3 ∷ [])
test1 = refl
test2 : stringListToℕ "12,33" ≡ just (12 ∷ 33 ∷ [])
test2 = refl
test3 : stringListToℕ ",,," ≡ nothing
test3 = refl
test4 : stringListToℕ "abc,def" ≡ nothing
test4 = refl