I am trying to translate to Idris an example from the Cayenne - a language with dependent types paper.
Here is what I have so far:
PrintfType : (List Char) -> Type
PrintfType Nil = String
PrintfType ('%' :: 'd' :: cs) = Int -> PrintfType cs
PrintfType ('%' :: 's' :: cs) = String -> PrintfType cs
PrintfType ('%' :: _ :: cs) = PrintfType cs
PrintfType ( _ :: cs) = PrintfType cs
printf : (fmt: List Char) -> PrintfType fmt
printf fmt = rec fmt "" where
rec : (f: List Char) -> String -> PrintfType f
rec Nil acc = acc
rec ('%' :: 'd' :: cs) acc = \i => rec cs (acc ++ (show i))
rec ('%' :: 's' :: cs) acc = \s => rec cs (acc ++ s)
rec ('%' :: _ :: cs) acc = rec cs acc -- this is line 49
rec ( c :: cs) acc = rec cs (acc ++ (pack [c]))
I am using List Char
instead of String
for the format argument in order to facilitate with pattern matching as I quickly ran into complexity with pattern matching on String
.
Unfortunately I get an error message I am not able to make sense of:
Type checking ./sprintf.idr
sprintf.idr:49:Can't unify PrintfType (Prelude.List.:: '%' (Prelude.List.:: t cs)) with PrintfType cs
Specifically:
Can't convert PrintfType (Prelude.List.:: '%' (Prelude.List.:: t cs)) with PrintfType cs
If I comment out all the pattern match cases with 3 elements (the ones with '%' :: ...
) in PrintfType
and printf
, then the code compiles (but obviously doesn't do anything interesting).
How do I fix my code so that printf "the %s is %d" "answer" 42
works?
It looks like there are some current limitations in idris when defining functions where the patterns overlap (for instance '%' :: 'd'
overlaps with c :: cs
. After many tries, I finally figured out a workaround for this:
data Format = End | FInt Format | FString Format | FChar Char Format
fromList : List Char -> Format
fromList Nil = End
fromList ('%' :: 'd' :: cs) = FInt (fromList cs)
fromList ('%' :: 's' :: cs) = FString (fromList cs)
fromList (c :: cs) = FChar c (fromList cs)
PrintfType : Format -> Type
PrintfType End = String
PrintfType (FInt rest) = Int -> PrintfType rest
PrintfType (FString rest) = String -> PrintfType rest
PrintfType (FChar c rest) = PrintfType rest
printf : (fmt: String) -> PrintfType (fromList $ unpack fmt)
printf fmt = printFormat (fromList $ unpack fmt) where
printFormat : (fmt: Format) -> PrintfType fmt
printFormat fmt = rec fmt "" where
rec : (f: Format) -> String -> PrintfType f
rec End acc = acc
rec (FInt rest) acc = \i: Int => rec rest (acc ++ (show i))
rec (FString rest) acc = \s: String => rec rest (acc ++ s)
rec (FChar c rest) acc = rec rest (acc ++ (pack [c]))
Format
is a recursive data type representing the format string. FInt
is a int placeholder, FString
is a string placeholder and FChar
is a char literal. Using Format
I can defined the PrintfType
and implement printFormat
. From there, I can smoothly extend to take a string rather than a List Char
or a Format
value. And the end result is:
*sprintf> printf "the %s is %d" "answer" 42
"the answer is 42" : String
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With