问题
How can I use Idris to build a function that, given a string, returns a proof that such String is hexadecimal (i.e., 0x
followed by 2*N
characters from 0-9
and a-f
, such as "0x1a7f33b8"
)?
What I tried
First, I've constructed the following type for hex chars:
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'
And the following type for hex strings:
data IsHex : String -> Type where
IsHexNil : IsHex "0x"
IsHexApp : IsHex s -> IsNib c0 -> IsNib c1 -> IsHex (s ++ singleton c0 ++ singleton c1)
Now I can manually build a proof that a string is hexadecimal:
a : IsHex "0x12"
a = IsHexApp IsHexNil (IsNib '1') (IsNib '2')
Cool! So, what about a function that builds that proof to me? That'd have the following type:
isItHex : (s : String) -> Dec (IsHex s)
The idea is to implement it by proving that all chars on the string are hexadecimal. For that, I need:
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
isItNib a = ?nowWhat
Here I encounter the first problem. How do I prove that all chars that aren't 0-9
and a-f
aren't hexadecimals? I could, of course, do it one by one:
isItNib 'g' = No ?gIsntHex
But even proving that case is somewhat complex. I could do it by proving IsNib 'g'
is uninhabited:
implementation Uninhabited (IsNib 'g') where
uninhabited IsNib0 impossible
uninhabited IsNib1 impossible
uninhabited IsNib2 impossible
uninhabited IsNib3 impossible
uninhabited IsNib4 impossible
uninhabited IsNib5 impossible
uninhabited IsNib6 impossible
uninhabited IsNib7 impossible
uninhabited IsNib8 impossible
uninhabited IsNib9 impossible
uninhabited IsNibA impossible
uninhabited IsNibB impossible
uninhabited IsNibC impossible
uninhabited IsNibD impossible
uninhabited IsNibE impossible
uninhabited IsNibF impossible
And then completing it with:
isItNib 'g' = No absurd
But that's 18 lines of code to prove a single character isn't hex. Completing that function would take thousands of lines of code. Assume, though, I had successfully implemented it (I can cheat by using assert_unreachable
). How about isItHex
?
isItHex : (s : String) -> Dec (IsHex s)
isItHex s with (strList s)
isItHex "" | SNil = ?isItHex_rhs_1
isItHex (strCons x xs) | (SCons x rec) = ?isItHex_rhs_2
To complete the first case, I need to prove:
--------------------------------------
isItHex_rhs_1 : Dec (IsHex "")
For the second case, I need to prove:
x : Char
xs : String
rec : StrList xs
--------------------------------------
isItHex_rhs_2 : Dec (IsHex (prim__strCons x xs))
For the first case, it suffices to prove IsHex "" -> Void
, so:
emptyNotHex : IsHex "" -> Void
emptyNotHex IsHexNil impossible
emptyNotHex (IsHexApp _ _ _) impossible
This entire definition was generated by the compiler using a case-split, yet it fails to check, claiming that:
(IsHexApp s a b) is a valid case
Surprisingly, if we change the second case to:
emptyNotHex (IsHexApp s a b) = ?thisIsntEmpty
It also fails to compile:
Type mismatch between
IsHex (s ++ singleton c0 ++ singleton c1) (Type of IsHexApp s a b)
and
IsHex "" (Expected type)
Specifically:
Type mismatch between
prim__concat s (prim__concat (prim__strCons c0 "") (prim__strCons c1 ""))
and
""Unification failure
So, Idris doesn't consider it impossible
, but it considers any implementation of it a type error? Ha! Confusing. Let's forget that for now and try proving the second branch. Let's start applying isItHex
to xs
:
isItHex (strCons x xs) | (SCons x rec) = let foo = isItHex xs in ?isItHex_rhs_2
Error:
Main.isItHex is possibly not total due to: with block in Main.isItHex
What! But isItHex
is covering all cases! At this point I noticed I had way too many parallel issues and decided to give up and ask. This is my first more extensive experience with the non-haskellish parts of Idris, so hopefully someone sheds light on some things I could be doing wrong.
回答1:
I would try something a bit less direct. Instead of enumerating the nybbles in a big data type, build some infrastructure to assert elementhood in a list/set, if it is not already in the standard library (for example, Agda has Any with a proof of decidability).
Work more generically -- there are so many details in dependent types, too many specifics clutter things up. When you work abstractly you eliminate degrees of freedom. E.g. build a generic All
predicate for lists, and an EvenLength
predicate. Prove simple consequences of each.
I would also avoid worrying about the initial 0x
as long as possible -- certainly don't put it into the base case of a data type. It belongs in parsing and serialization; working with it as if it were data is an unnecessary nuisance.
来源:https://stackoverflow.com/questions/48123439/how-to-prove-that-a-string-is-in-hexadecimal-format