我正在尝试用Agda中的自然数解析字符串。
例如,stringListToℕ "1,2,3"
的结果应为Just (1 ∷ 2 ∷ 3 ∷ [])
我当前的代码不太正确,或者说无论如何都不错,但它确实有效。
但是它返回类型:
Maybe (List (Maybe ℕ))
问题是:
如何以一种很好的方式实现函数stringListToℕ
(与我的代码相比);
它应该具有Maybe (List ℕ)
(可选,不重要)如何将类型Maybe (List (Maybe ℕ))
转换为Maybe (List ℕ)
?
我的代码:
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 ∷ [])
编辑
我尝试使用from-just
编写转换函数,但在类型检查时会出错:
conv : Maybe (List (Maybe ℕ)) → Maybe (List ℕ)
conv (just xs) = map from-just xs
conv _ = nothing
错误是:
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
答案 0 :(得分:7)
我冒昧地将您的split
函数重写为更通用的函数,这也适用于终止检查:
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
另外,stringToℕ ""
最有可能是nothing
,除非您真的想要:
stringListToℕ "1,,2" ≡ just (1 ∷ 0 ∷ 2 ∷ [])
让我们重写一下(注意helper
是您原来的stringToℕ
函数):
stringToℕ : List Char → Maybe ℕ
stringToℕ [] = nothing
stringToℕ list = helper list 0
where {- ... -}
现在我们可以把它们放在一起了。为简单起见,我在任何地方使用List Char
,根据需要使用fromList
/ toList
:
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
您可以在sequence
中找到Data.List
;我们还必须指定我们想要使用哪个monad实例。 Data.Maybe
以名称monad
导出其monad实例。最终代码:
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
还有一个小测试:
open import Relation.Binary.PropositionalEquality
test : stringListToℕ ('1' ∷ '2' ∷ ',' ∷ '3' ∷ []) ≡ just (12 ∷ 3 ∷ [])
test = refl
考虑您的第二个问题:有很多方法可以将Maybe (List (Maybe ℕ))
转换为Maybe (List ℕ)
,例如:
silly : Maybe (List (Maybe ℕ)) → Maybe (List ℕ)
silly _ = nothing
是的,这并没有多大帮助。如果元素全部为just
,我们希望转换为保留元素。 isNothing
已经完成了这部分检查,但它无法摆脱内部Maybe
层。
from-just
可以工作,因为我们知道,当我们使用它时,List
的所有元素对于某些just x
必须为x
。问题是当前形式的conv
是错误的 - from-just
仅在Maybe A → A
值为Maybe
时才作为just x
类型的函数运行!我们可以做这样的事情:
test₂ : Maybe (List ℕ)
test₂ = conv ∘ just $ nothing ∷ just 1 ∷ []
由于from-list
在给定Maybe A → ⊤
时表现为nothing
,我们实际上是在尝试使用⊤
和{{1}类型的元素构建异构列表}。
让我们废弃这个解决方案,我会展示一个更简单的解决方案(事实上,它应该类似于这个答案的第一部分)。
我们获得ℕ
,我们给出了两个目标:
取内部Maybe (List (Maybe ℕ))
(如果有的话),检查所有元素是否都是List (Maybe ℕ)
,并在这种情况下将它们全部放入包含在just x
中的列表中,否则返回just
将加倍的nothing
图层压缩成一个
嗯,第二点听起来很熟悉 - monad可以做的事情!我们得到:
Maybe
此功能适用于任何monad,但我们可以使用join : {A : Set} → Maybe (Maybe A) → Maybe A
join mm = mm >>= λ x → x
where
open RawMonad Data.Maybe.monad
。
对于第一部分,我们需要一种方法将Maybe
转换为List (Maybe ℕ)
- 也就是说,我们希望在传播可能的错误时交换图层(即Maybe (List ℕ)
)进入外层。 Haskell为这类内容提供了专门的类型类(nothing
来自Traversable
),this question如果你想了解更多内容,我会有一些很好的答案。基本上,所有关于重建结构同时收集"副作用"。我们可以使用仅适用于Data.Traversable
的版本,我们会再次返回List
。
还有一件遗失,让我们看看到目前为止我们所拥有的东西:
sequence
我们需要在sequence-maybe : List (Maybe ℕ) → Maybe (List ℕ)
sequence-maybe = sequence Data.Maybe.monad
join : Maybe (Maybe (List ℕ)) → Maybe (List ℕ)
-- substituting A with List ℕ
图层中应用sequence-maybe
。 Maybe
仿函数实例发挥作用的地方(你可以单独使用monad实例,但它更方便)。使用此仿函数实例,我们可以将Maybe
类型的普通函数提升为类型a → b
的函数。最后:
Maybe a → Maybe b
答案 1 :(得分:1)
我试图不去做聪明并使用简单的递归函数而不是stdlib魔法。 parse xs m ns
通过记录xs
中已读取的(可能为空)前缀,同时保留已在累加器m
中解析的数字列表来解析ns
。
如果发生解析失败(无法识别的角色,连续两次,
等),一切都会被丢弃,我们会返回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
答案 2 :(得分:1)
这是来自Vitus的代码,作为使用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