在 Idris 中 运行 时间输入函数
Type functions at run-time in Idris
我已经了解了类型驱动开发,其中有一节是关于类型良好的 printf
,其中类型是根据格式字符串的第一个参数计算的:
printf : (fmt : String) -> PrintfType (toFormat (unpack fmt))
toFormat
从 List Char
创建格式表示,PrintfType
从这种格式表示创建函数类型。完整代码在这里:https://github.com/edwinb/TypeDD-Samples/blob/master/Chapter6/Printf.idr
我明白 printf "%c %c" 'a' 'b'
这样的代码是怎么回事——printf "%c %c
的类型被计算为 Char -> Char -> String
.
我不明白的是,当第一个参数在 运行 时未知时会发生什么。我明白为什么 通常 这样的代码不应该编译:如果用户在 运行 输入格式字符串和结果类型,则在编译时无法知道它们-时间,但我不知道这种直觉背后的实际技术推理。
例如:
main : IO ()
main = do fmt <- getLine
c1 <- getChar
c2 <- getChar
printf fmt c1 c2
导致以下错误消息:
When checking an application of function Prelude.Monad.>>=:
printf fmt does not have a function type (PrintfType (toFormat (unpack fmt)))
我想我正在尝试理解此错误消息。 Idris 是否将此类函数视为特例?
无法进一步评估 printf fmt : PrintfType (toFormat (unpack fmt))
的类型,因为 fmt : String
在编译时未知。所以对于 Idris 这不是一个函数——它是 A
而不是 A -> B
。这就是错误消息所说的内容。
对于您的情况,您必须在 运行 时检查 PrintfType (toFormat (unpack fmt))
是否为 String -> String
。
例如,这里有两个函数采用一种格式,也许 return 证明它是正确的格式:
endFmt : (fmt : Format) -> Maybe (PrintfType fmt = String)
endFmt End = Just Refl
endFmt (Lit str fmt) = endFmt fmt
endFmt fmt = Nothing
strFmt : (fmt : Format) -> Maybe ((PrintfType fmt) = (String -> String))
strFmt (Str fmt) = case endFmt fmt of
Just ok => rewrite ok in Just Refl
Nothing => Nothing
strFmt (Lit str fmt) = strFmt fmt
strFmt fmt = Nothing
然后我们可以用 toFormat (unpack fmt)
调用 strFmt
并且必须处理两个 Maybe
情况。因为 Idris 在应用证明时遇到问题,我们帮助 replace
。
main : IO ()
main = do fmt <- getLine
str <- getLine
case strFmt (toFormat (unpack fmt)) of
Just ok => let printer = replace ok {P=id} (printf fmt) in
putStrLn $ printer str
-- in a better world you would only need
-- putStrLn $ printf fmt str
Nothing => putStrLn "Wrong format"
有了这个我们可以运行:
:exec main
foo %s bar -- fmt
and -- str
foo and bar -- printf fmt str
我已经了解了类型驱动开发,其中有一节是关于类型良好的 printf
,其中类型是根据格式字符串的第一个参数计算的:
printf : (fmt : String) -> PrintfType (toFormat (unpack fmt))
toFormat
从 List Char
创建格式表示,PrintfType
从这种格式表示创建函数类型。完整代码在这里:https://github.com/edwinb/TypeDD-Samples/blob/master/Chapter6/Printf.idr
我明白 printf "%c %c" 'a' 'b'
这样的代码是怎么回事——printf "%c %c
的类型被计算为 Char -> Char -> String
.
我不明白的是,当第一个参数在 运行 时未知时会发生什么。我明白为什么 通常 这样的代码不应该编译:如果用户在 运行 输入格式字符串和结果类型,则在编译时无法知道它们-时间,但我不知道这种直觉背后的实际技术推理。
例如:
main : IO ()
main = do fmt <- getLine
c1 <- getChar
c2 <- getChar
printf fmt c1 c2
导致以下错误消息:
When checking an application of function Prelude.Monad.>>=:
printf fmt does not have a function type (PrintfType (toFormat (unpack fmt)))
我想我正在尝试理解此错误消息。 Idris 是否将此类函数视为特例?
无法进一步评估 printf fmt : PrintfType (toFormat (unpack fmt))
的类型,因为 fmt : String
在编译时未知。所以对于 Idris 这不是一个函数——它是 A
而不是 A -> B
。这就是错误消息所说的内容。
对于您的情况,您必须在 运行 时检查 PrintfType (toFormat (unpack fmt))
是否为 String -> String
。
例如,这里有两个函数采用一种格式,也许 return 证明它是正确的格式:
endFmt : (fmt : Format) -> Maybe (PrintfType fmt = String)
endFmt End = Just Refl
endFmt (Lit str fmt) = endFmt fmt
endFmt fmt = Nothing
strFmt : (fmt : Format) -> Maybe ((PrintfType fmt) = (String -> String))
strFmt (Str fmt) = case endFmt fmt of
Just ok => rewrite ok in Just Refl
Nothing => Nothing
strFmt (Lit str fmt) = strFmt fmt
strFmt fmt = Nothing
然后我们可以用 toFormat (unpack fmt)
调用 strFmt
并且必须处理两个 Maybe
情况。因为 Idris 在应用证明时遇到问题,我们帮助 replace
。
main : IO ()
main = do fmt <- getLine
str <- getLine
case strFmt (toFormat (unpack fmt)) of
Just ok => let printer = replace ok {P=id} (printf fmt) in
putStrLn $ printer str
-- in a better world you would only need
-- putStrLn $ printf fmt str
Nothing => putStrLn "Wrong format"
有了这个我们可以运行:
:exec main
foo %s bar -- fmt
and -- str
foo and bar -- printf fmt str