How to prove that a string is in hexadecimal format?

删除回忆录丶 提交于 2019-12-24 18:16:53

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!