如何用Idris实现十六进制字符串证明函数及问题求助
Hey there! Let's tackle your two questions about verifying hex strings and building proof functions in Idris step by step.
Based on your requirements (strings starting with 0x followed by an even number of 0-9/a-f characters), we can use dependent types to formalize this:
- First, define
IsNibto prove a single character is a valid hex nibble. - Then, define
IsHexto prove the entire string follows the0x+ even nibbles pattern.
Your initial type definitions are on the right track—we just need to adjust them slightly to make writing automatic proof functions easier.
Let's break down the two pain points you're facing and fix them one by one.
Problem 1: Simplifying isItNib : Char -> Dec (IsNib c)
Writing negation proofs for every non-hex character is tedious. Instead of handling each invalid case manually, we can use a helper function to extract the character from an IsNib proof, then use decEq to generate the contradiction automatically.
First, keep your original IsNib definition (since it's explicit and clear), then add this helper to reduce repetition:
data IsNib : Char -> Type where IsNib0 : IsNib '0' IsNib1 : IsNib '1' IsNib2 : IsNib '2' IsNib3 : IsNib '3' IsNib4 : IsNib '4' IsNib5 : IsNib '5' IsNib6 : IsNib '6' IsNib7 : IsNib '7' IsNib8 : IsNib '8' IsNib9 : IsNib '9' IsNibA : IsNib 'a' IsNibB : IsNib 'b' IsNibC : IsNib 'c' IsNibD : IsNib 'd' IsNibE : IsNib 'e' IsNibF : IsNib 'f' -- Helper to get the character from an IsNib proof nibChar : IsNib c -> Char nibChar IsNib0 = '0' nibChar IsNib1 = '1' nibChar IsNib2 = '2' nibChar IsNib3 = '3' nibChar IsNib4 = '4' nibChar IsNib5 = '5' nibChar IsNib6 = '6' nibChar IsNib7 = '7' nibChar IsNib8 = '8' nibChar IsNib9 = '9' nibChar IsNibA = 'a' nibChar IsNibB = 'b' nibChar IsNibC = 'c' nibChar IsNibD = 'd' nibChar IsNibE = 'e' nibChar IsNibF = 'f' isItNib : (c : Char) -> Dec (IsNib c) isItNib '0' = Yes IsNib0 isItNib '1' = Yes IsNib1 isItNib '2' = Yes IsNib2 isItNib '3' = Yes IsNib3 isItNib '4' = Yes IsNib4 isItNib '5' = Yes IsNib5 isItNib '6' = Yes IsNib6 isItNib '7' = Yes IsNib7 isItNib '8' = Yes IsNib8 isItNib '9' = Yes IsNib9 isItNib 'a' = Yes IsNibA isItNib 'b' = Yes IsNibB isItNib 'c' = Yes IsNibC isItNib 'd' = Yes IsNibD isItNib 'e' = Yes IsNibE isItNib 'f' = Yes IsNibF -- For invalid chars, use the helper to generate a contradiction isItNib c = No (\proof => absurd $ decEq c (nibChar proof))
This cuts down the negation code to a single line—no more repetitive case handling for every invalid character!
Problem 2: Implementing Total isItHex : String -> Dec (IsHex s)
Your original IsHex definition is based on string concatenation, which makes parsing tricky (since we process strings left-to-right). Let's redefine IsHex to align with how we actually check strings: first confirm it starts with 0x, then verify the rest has an even number of valid nibbles.
Step 1: Redefine the Types
-- Helper type: proves a string is an even number of valid hex nibbles data HexTail : String -> Type where HexTailEmpty : HexTail "" HexTailPair : IsNib c1 -> IsNib c2 -> HexTail rest -> HexTail (singleton c1 ++ singleton c2 ++ rest) -- Main IsHex type: starts with "0x", followed by a valid HexTail data IsHex : String -> Type where MkIsHex : HexTail tail -> IsHex ("0x" ++ tail)
This structure makes parsing straightforward—we split the string at the first two characters to check for 0x, then process the rest in pairs.
Step 2: Implement the Decision Functions
isItHexTail : (s : String) -> Dec (HexTail s) isItHexTail "" = Yes HexTailEmpty isItHexTail s with (strSplitAt 2 s) -- Case 1: We can split into two characters + rest isItHexTail (c1 :: c2 :: rest) | (MkPair (c1 :: c2 :: []) rest) = case (isItNib c1, isItNib c2) of (Yes nib1, Yes nib2) => case isItHexTail rest of Yes restProof => Yes (HexTailPair nib1 nib2 restProof) No contra => No (\(HexTailPair _ _ p) => contra p) (No contra1, _) => No (\(HexTailPair nib1 _ _) => contra1 nib1) (_, No contra2) => No (\(HexTailPair _ nib2 _) => contra2 nib2) -- Case 2: String length is odd (can't split into pairs) isItHexTail _ | _ = No (\proof => case proof of HexTailEmpty => absurd $ decEq s "" HexTailPair _ _ _ => absurd $ length s `mod` 2 = 1) isItHex : (s : String) -> Dec (IsHex s) isItHex s with (strSplitAt 2 s) -- Case 1: String starts with "0x" isItHex ("0x" ++ tail) | (MkPair "0x" tail) = case isItHexTail tail of Yes tailProof => Yes (MkIsHex tailProof) No contra => No (\(MkIsHex p) => contra p) -- Case 2: String doesn't start with "0x" (or is shorter than 2 chars) isItHex _ | _ = No (\(MkIsHex _) => absurd $ decEq (take 2 s) "0x")
Key Fixes:
- Type Mismatch: By redefining
IsHexto use prefix checking instead of suffix concatenation, we eliminate the type mismatch when handling empty strings or invalid prefixes. - Total Function: Using
withclauses to cover all possible string cases (starts with0xor not, even/odd length in the tail) ensures the function is total. EachNobranch provides a clear contradiction proof, so Idris recognizes it covers all cases.
Testing the Function
Now you can generate proofs automatically:
-- Valid hex string: gets a proof validHex : IsHex "0x1a7f33b8" validHex = case isItHex "0x1a7f33b8" of Yes p => p No _ => impossible -- Invalid character: returns No invalidChar : Dec (IsHex "0x1g") invalidChar = isItHex "0x1g" -- Odd number of nibbles after 0x: returns No oddLength : Dec (IsHex "0x1") oddLength = isItHex "0x1"
内容的提问来源于stack exchange,提问作者MaiaVictor

