Agda:用数字解析一个字符串

时间:2012-08-01 16:30:37

标签: agda dependent-type

我正在尝试用Agda中的自然数解析字符串。 例如,stringListToℕ "1,2,3"的结果应为Just (1 ∷ 2 ∷ 3 ∷ [])

我当前的代码不太正确,或者说无论如何都不错,但它确实有效。 但是它返回类型:     Maybe (List (Maybe ℕ))

问题是:

  1. 如何以一种很好的方式实现函数stringListToℕ(与我的代码相比); 它应该具有Maybe (List ℕ)

  2. 类型
  3. (可选,不重要)如何将类型Maybe (List (Maybe ℕ))转换为Maybe (List ℕ)

  4. 我的代码:

    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
    

3 个答案:

答案 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-maybeMaybe仿函数实例发挥作用的地方(你可以单独使用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