Agda: run function for Conor's stack example - agda

At ICFP 2012, Conor McBride gave a keynote with the tile "Agda-curious?".
It featured a small stack machine implementation.
The video is on youtube:
http://www.youtube.com/watch?v=XGyJ519RY6Y
The code is online too:
http://personal.cis.strath.ac.uk/conor.mcbride/ICFP12Code.tgz
I am wondering about the run function of Part 5 (i.e. "Part5Done.agda" if you downloaded the code). The talk stops before the run function is explained.
data Inst : Rel Sg SC Stk where
PUSH : {t : Ty} (v : El t) -> forall {ts vs} ->
Inst (ts & vs) ((t , ts) & (E v , vs))
ADD : {x y : Nat}{ts : SC}{vs : Stk ts} ->
Inst ((nat , nat , ts) & (E y , E x , vs))
((nat , ts) & (E (x + y) , vs))
IFPOP : forall {ts vs ts' vst vsf b} ->
List Inst (ts & vs) (ts' & vst) -> List Inst (ts & vs) (ts' & vsf)
-> Inst ((bool , ts) & (E b , vs)) (ts' & if b then vst else vsf)
postulate
Level : Set
zl : Level
sl : Level -> Level
{-# BUILTIN LEVEL Level #-}
{-# BUILTIN LEVELZERO zl #-}
{-# BUILTIN LEVELSUC sl #-}
data _==_ {l : Level}{X : Set l}(x : X) : X -> Set l where
refl : x == x
{-# BUILTIN EQUALITY _==_ #-}
{-# BUILTIN REFL refl #-}
fact : forall {T S} -> (b : Bool)(t f : El T)(s : Stk S) ->
(E (if b then t else f) , s) ==
(if b then (E t , s) else (E f , s))
fact tt t f s = refl
fact ff t f s = refl
compile : {t : Ty} -> (e : Expr t) -> forall {ts vs} ->
List Inst (ts & vs) ((t , ts) & (E (eval e) , vs))
compile (val y) = PUSH y , []
compile (e1 +' e2) = compile e1 ++ compile e2 ++ ADD , []
compile (if' e0 then e1 else e2) {vs = vs}
rewrite fact (eval e0) (eval e1) (eval e2) vs
= compile e0 ++ IFPOP (compile e1) (compile e2) , []
{-
-- ** old run function from Part4Done.agda
run : forall {ts ts'} -> List Inst ts ts' -> List Elt ts [] -> List Elt ts' []
run [] vs = vs
run (PUSH v , is) vs = run is (E v , vs)
run (ADD , is) (E v2 , E v1 , vs) = run is (E 0 , vs)
run (IFPOP is1 is2 , is) (E tt , vs) = run is (run is2 vs)
run (IFPOP is1 is2 , is) (E ff , vs) = run is (run is1 vs)
-}
run : forall {i j} -> List Inst i j -> Sg Stack (λ s -> s == i) → (Sg SC Stk)
run {vs & vstack} [] _
= (vs & vstack)
run _ _ = {!!} -- I have no clue about the other cases...
--Perhaps the correct type is:
run' : forall {i j} -> List Inst i j -> Sg Stack (λ s -> s == i) → Sg (Sg SC Stk) (λ s → s == j)
run' _ _ = {!!}
What is the correct type signature of the run function? How should the run function be implemented?
The compile function is explained about 55 minutes into the talk .
The full code is available from Conor's webpage.

Guilty as charged, the type of the run function from Part4Done.agda is
run : forall {ts ts'} -> List Inst ts ts' -> List Elt ts [] -> List Elt ts' []
run [] vs = vs
run (PUSH v , is) vs = run is (E v , vs)
run (ADD , is) (E v2 , E v1 , vs) = run is (E 0 , vs)
run (IFPOP is1 is2 , is) (E tt , vs) = run is (run is2 vs)
run (IFPOP is1 is2 , is) (E ff , vs) = run is (run is1 vs)
which amounts to saying "Given code which starts from stack configuration ts and finishes in stack configuration ts' and a stack in configuration ts, you will get a stack in configuration ts'. A "stack configuration" is a list of the types of the things pushed on the stack.
In Part5Done.agda, the code is indexed not only by the types of what the stack holds initially and finally but also by the values. The run function is thus woven into the definition of the code. The compiler is then typed to require that the code produced must have a run behaviour which corresponds to eval. That is, the run-time behaviour of compiled code is bound to respect the reference semantics. If you want to run that code, to see with your own eyes what you know to be true, type the same function along the same lines, except that we need to select the types alone from the types-and-values pairs which index the code.
run : forall {ts ts' vs vs'} -> List Inst (ts & vs) (ts' & vs') ->
List Elt ts [] -> List Elt ts' []
run [] vs = vs
run (PUSH v , is) vs = run is (E v , vs)
run (ADD , is) (E v2 , E v1 , vs) = run is (E (v1 + v2) , vs)
run (IFPOP is1 is2 , is) (E tt , vs) = run is (run is1 vs)
run (IFPOP is1 is2 , is) (E ff , vs) = run is (run is2 vs)
Alternatively, you can apply the obvious erasure function which maps the types-and-values-indexed code to types-indexed code, then use the old run function. My work with Pierre-Évariste Dagand on ornaments automates these patterns of layering an extra index induced by a program systematically over a type then rubbing it out later. It's a generic theorem that if you erase the computed index then recompute it from the erased version, you get (GASP!) exactly the index you erased. In this case, that means running the code which is typed to agree with eval will actually give you the same answer as eval.

Related

Proving two dependent (AVL tree key-value) pairs equal

The standard library's AVL tree implementation uses dependent pairs to store key-value pairs. I have two such pairs whose keys (k and k′) I have shown to be equal (k≡k′). They also contain the same value (v). I'd like to prove that the pairs are equal. Here's the goal:
open import Agda.Primitive
open import Data.Nat
open import Data.Nat.Properties
open import Data.Product
open import Relation.Binary.PropositionalEquality
open import Data.AVL.Indexed <-strictTotalOrder
module Repro (ℓ : Level) (V : Value ℓ) where
Val = Value.family V
V≈ = Value.respects V
proof : (k k′ : ℕ) → (v : Val k) → (k≡k′ : k ≡ k′) → (k , v) ≡ (k′ , V≈ k≡k′ v)
proof k k′ v k≡k′ = {!!}
I tried rewriting with k≡k′, which turns k on the LHS into k′ and k≡k′ on the RHS into refl. That's where I get stuck. I feel like I'm missing something obvious, as this seems a pretty basic thing to do.
(As an exercise, I'm trying to prove the standard library's AVL tree insertion correct. Hence my recent obsession with its AVL tree code.)
Update
Hmm. Maybe this isn't as trivial as I thought, at least without knowing more about V. After all, what I currently know is that:
There's a way to turn a value of type Val k into a value of type Val k′ - by way of V≈.
That V≈ refl takes a Val foo and returns a Val foo.
What I don't know at this point is that V≈ refl is the identity function, which seems to be what I'd need to do my proof.
Update II
If, for example, I knew that V≈ was actually subst Val, then my proof would be:
proof′ : (k k′ : ℕ) → (v : Val k) → (k≡k′ : k ≡ k′) → (k , v) ≡ (k′ , subst Val k≡k′ v)
proof′ k k′ v k≡k′ rewrite k≡k′ = cong (k′ ,_) refl
So, I guess my question ultimately is:
Can I complete my original proof with what I have?
If not, then what property do I need V≈ to have to complete my original proof (i.e., to "restrict it to functions that are like subst")?
Update III
I am providing more context in response to MrO's comment. Instead of stripping down my actual proof, I'll provide something even simpler. Let's prove a special case of AVL tree insertion, which leads to the same problem that I'm facing in the general case.
Let's prove that inserting a value v for key k into an empty tree and then looking up the value for key k yields the value v that we inserted.
Let's get set up:
open import Agda.Primitive using (Level)
open import Data.Maybe using (just)
open import Data.Nat using (ℕ)
open import Data.Nat.Properties using (<-strictTotalOrder ; <-cmp)
open import Data.Product using (proj₂)
open import Function using () renaming (const to constᶠ)
open import Relation.Binary using (tri< ; tri≈ ; tri>)
open import Relation.Binary.PropositionalEquality using (_≡_ ; refl )
open import Relation.Nullary.Negation using (contradiction)
open import Data.AVL.Indexed <-strictTotalOrder
module Simple {ℓ : Level} (V : Value ℓ) where
private
Val = Value.family V
V≈ = Value.respects V
Now, a function to create an empty tree:
make-empty : ∀ {l u} → l <⁺ u → Tree V l u 0
make-empty = leaf
And the proof:
proof : ∀ {l u} (k : ℕ) (v : Val k) (l<u : l <⁺ u) (l<k<u : l < k < u) →
lookup k (proj₂ (insertWith k (constᶠ v) (make-empty l<u) l<k<u)) l<k<u ≡ just v
proof k v l<u l<k<u with <-cmp k k
... | tri< _ p _ = contradiction refl p
... | tri> _ p _ = contradiction refl p
... | tri≈ _ p _ rewrite p = {!!}
The goal that I'm trying to fill in is just (V≈ refl v) ≡ just v.
You could postulate, or take as a module parameter, the fact that V≈ leaves the value unchanged, as follows:
postulate lemma : ∀ {k k′} {v : Val k} → (k≡k′ : k ≡ k′) → v ≡ subst _ (sym k≡k′) (V≈ k≡k′ v)
After that, your proof becomes:
proof : (k k′ : ℕ) → (v : Val k) → (k≡k′ : k ≡ k′) → (k , v) ≡ (k′ , V≈ k≡k′ v)
proof k ._ _ refl = cong (k ,_) (lemma refl)

How to convince the Agda compiler that an expression terminates?

I'm learning Agda using Philip Wadler's Programming Language Foundations In Agda, and I can't figure out how to convince the compiler that a computation terminates.
I've got types for unary and binary naturals:
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
data Bin : Set where
⟨⟩ : Bin
_O : Bin → Bin
_I : Bin → Bin
And I wrote a function to convert between the two representations (using some helpers):
-- try to count to a given power of two
--
-- to-count m t f n =
-- t (n - 2^m) if n >= 2^m
-- (f * 2^m) + n otherwise
to-count : ℕ → (ℕ → Bin) → Bin → ℕ → Bin
to-count zero t f zero = f
to-count zero t f (suc n) = t n
to-count (suc m) t f n = to-count m (to-count m t (f I)) (f O) n
-- keep trying to count bigger and bigger powers of two
to-next : ℕ → ℕ → Bin
to-next m = to-count m (to-next (suc m)) (⟨⟩ I)
to : ℕ → Bin
to = to-count zero (to-next zero) ⟨⟩
Later, when trying to prove that my conversion is faithful:
import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl; cong)
open Eq.≡-Reasoning using (begin_; _≡⟨⟩_; _≡⟨_⟩_; _∎)
_ : to zero ≡ ⟨⟩
_ = refl
_ : to (suc zero) ≡ ⟨⟩ I
_ = refl
The compiler complains that termination checking failed:
Checking Naturals (Naturals.agda).
Naturals.agda:23,1-24,48
Termination checking failed for the following functions:
to-next
Problematic calls:
to-next (suc m)
(at Naturals.agda:24,25-32)
Naturals.agda:37,5-9
to-next zero zero != ⟨⟩ I of type Bin
when checking that the expression refl has type
to (suc zero) ≡ (⟨⟩ I)
What are some strategies I can use to help convince the compiler that it terminates?
Using pragma is not how you need to convince the compiler that the function terminates.
The compiler indicated the problematic call: to-next (suc m) cannot be seen as unused in the cases you think, and obviously it creates a structurally bigger value than on input.
A way to deal with this problem is express the construction of Bin from ℕ differently.
inc-bin : Bin -> Bin
inc-bin ⟨⟩ = ⟨⟩ I
inc-bin (bb O) = bb I
inc-bin (bb I) = (inc-bin bb) O
to-bin-daft : ℕ -> Bin
to-bin-daft zero = b O
to-bin-daft (suc m) = inc-bin (to-bin-daft m)
This is "daft", as it literally increments Bin by one at a time, for every suc, but more complex algorithms involving, say, division by 2, require evidence that the result of division is smaller than the input.
Not sure if this is the most idiomatic solution, but I got it working using the TERMINATING pragma:
{-# TERMINATING #-}
to-next : ℕ → ℕ → Bin
to-next m = to-count m (to-next (suc m)) (⟨⟩ I)

Why is defining an instance of Choice failing with unknown value?

UPDATE: I'm inlining the code here instead.
I'm trying to define an instance of Data.Profunctor.Choice where right is defined by calling left, but for some reason the compiler complains that left is unknown.
newtype StateTrans s i o = ST (Tuple s i → Tuple s o)
instance stFunctor ∷ Functor (StateTrans s a) where
map f (ST st) = ST $ second f <<< st
instance stProfunctor ∷ Profunctor (StateTrans s) where
dimap f g (ST st) = ST $ second g <<< st <<< second f
instance stChoice ∷ Choice (StateTrans s) where
left (ST f) = ST lf
where
lf (Tuple s (Left a)) = let (Tuple s' b) = f (Tuple s a)
in Tuple s' (Left b)
lf (Tuple s (Right c)) = Tuple s (Right c)
-- Fails to compile with: Unknown value left
right f = arr mirror <<< left f <<< arr mirror
where
mirror Left x = Right x
mirror Right x = Left x
Probably a silly mistake but I've been looking at my code for so long, I can't figure out what's wrong.
(Of minor importance and unrelated: in the Right case for left, I have to unwrap and repackage the value so that it type-aligns. Adding a type ascription fails to compile too.)
Strangely enough, I have no problem doing the same for Strong, see:
instance stStrong ∷ Strong (StateTrans s) where
first (ST f) = ST ff
where
ff (Tuple s (Tuple a c)) = let (Tuple s' b) = f $ Tuple s a
in Tuple s' (Tuple b c)
second f = arr swap <<< first f <<< arr swap
I'm not 100% sure as the pasted snippet doesn't include imports, but I suspect you have an import for Data.Profunctor.Choice (class Choice) rather than Data.Profunctor.Choice (class Choice, left, right).
Importing a class does not import its members implicitly, even though it is possible to define them in an instance without doing so.
First, to use arr you need to declare the Category instance for StateTrans s:
instance stSemigroupoid :: Semigroupoid (StateTrans s) where
compose (ST f1) (ST f2) = ST $ f1 <<< f2
instance stCategory :: Category (StateTrans s) where
id = ST $ \x -> x
For the second step, I needed to add more type annotations (not sure why they're necessary, but this way the build succeeded):
choiceLeft :: forall input output a s. (StateTrans s) input output -> (StateTrans s) (Either input a) (Either output a)
choiceLeft (ST f) = ST lf
where
lf (Tuple s (Left a)) = let (Tuple s' b) = f (Tuple s a)
in Tuple s' (Left b)
lf (Tuple s (Right c)) = Tuple s (Right c)
choiceRight :: forall input output t s. (StateTrans s) input output -> (StateTrans s) (Either t input) (Either t output)
choiceRight f = amirror <<< choiceLeft f <<< amirror
where
mirror :: forall a b. Either a b -> Either b a
mirror (Left x) = Right x
mirror (Right x) = Left x
amirror :: forall a b. StateTrans s (Either b a) (Either a b)
amirror = arr mirror
instance stChoice ∷ Choice (StateTrans s) where
left = choiceLeft
right = choiceRight
Note: used PureScript version 0.11.7 and purescript-profunctor version 3.2.0.

L-product-0 Theorem

I would like to prove the following:
𝕃-product-0 : ∀{l : 𝕃 ℕ} → list-any (_=ℕ_ 0) l ≡ tt → 𝕃-product l ≡ 0
𝕃-product-0 = {!!}
'list-any' is defined as:
list-any : ∀{ℓ}{A : Set ℓ}(pred : A → 𝔹)(l : 𝕃 A) → 𝔹
list-any pred [] = ff
list-any pred (x :: xs) = pred x || list-any pred xs
And _=ℕ_ is defined as:
_=ℕ_ : ℕ → ℕ → 𝔹
0 =ℕ 0 = tt
suc x =ℕ suc y = x =ℕ y
_ =ℕ _ = ff
I'm trying to understand this part: list-any (_=ℕ_ 0) l ≡ tt
Is (_=ℕ_ 0) ≡ (pred : A → 𝔹) and l ≡ (l : 𝕃 A)?
If so, I would like help understanding the predicate. What does (=ℕ 0) mean? I'm assuming =ℕ is applied like:
Foreach element in l return (element =ℕ 0).
Is this correct? I tried to prove the theorem by using a list l1:
𝕃-product-0 : ∀{l : 𝕃 ℕ} → list-any (_=ℕ_ 0) l ≡ tt → 𝕃-product l ≡ 0
𝕃-product-0 l1 = {!!}
but got the following error:
I'm not sure if there should be a case for the constructor refl,
because I get stuck when trying to solve the following unification
problems (inferred index ≟ expected index):
list-any (_=ℕ_ 0) l ≟ tt
when checking that the expression ? has type 𝕃-product .l ≡ 0
I appreciate any help given!
Edit (response 1):
I split the the case on the hole and got:
𝕃-product-0 : ∀{l : 𝕃 ℕ} → list-any (_=ℕ_ 0) l ≡ tt → 𝕃-product l ≡ 0
𝕃-product-0 x = {!!}
Is this the case that I want? It filled in x when I split.
It can also see that:
Goal: 𝕃-product .l ≡ 0
Where:
x : list-any (_=ℕ_ 0) .l ≡ tt
.l : 𝕃 ℕ
What is the program wanting me to solve at this point? How can I show that
𝕃-product-0 x is logically equivalent to 𝕃-product .l ≡ 0?
I'm trying to understand this part: list-any (=ℕ 0) l ≡ tt
Is (=ℕ 0) ≡ (pred : A → 𝔹) and l ≡ (l : 𝕃 A)?
You're correct in that _=ℕ_ 0 is substituted for pred. _=ℕ_ 0 means the result of applying the function _=ℕ_ to 0. Since _=ℕ_ is a binary function, applying it to just one argument yields a function ℕ -> 𝔹, which is what list-any expects.
As to the other question, you're trying to pattern match there on l, but it's implicit: in your example l1 actually denotes the list-any (_=ℕ_ 0) l ≡ tt argument, because that's the first explicit argument. You have to introduce the implicit argument using brackets:
𝕃-product-0 : ∀{l : 𝕃 ℕ} → list-any (_=ℕ_ 0) l ≡ tt → 𝕃-product l ≡ 0
𝕃-product-0 {l1} = {!!}
Edit:
I assume you're in Emacs agda-mode. When you look at the the context, the implicit arguments are prefixed with a dot, like .l : 𝕃 ℕ. If you're on a new-ish Agda, if you want to split on .l, write { .l } in the hole, then hit C-c-c. Another solution is to introduce the argument in the function definition using brackets, like how I wrote above, then write { l1 } in the hole, then hit C-c-c.
Alternatively, you can make the list argument explicit and save yourself the brackets:
𝕃-product-0 : (l : 𝕃 ℕ) → list-any (_=ℕ_ 0) l ≡ tt → 𝕃-product l ≡ 0
𝕃-product-0 l = ?
Just remember that in a function definition, arguments without brackets are the explicit arguments, and you can insert implicit arguments before or between them corresponding to the order they appear in the type. You can also refer to implicit arguments by their name in the type. For example:
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n m = ? -- now "n" refers to the first Nat and "m" refers to the last argument
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n {l} m = ? -- now "l" refers to the list.
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n {l = list} m = ? -- refer to "l" by the name "list" here.

Agda: parse a string with numbers

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

Resources