我有两个功能:
prompt :: Text -> (Text -> Either Text a) -> IO a
subPrompt :: Text -> (Text -> Bool) -> IO a -> IO (Maybe (Text, a))
subPrompt将显示第二个提示(参数3),如果在运行第一个提示后参数2中的函数返回true,则显示该提示。
我不喜欢参数3是IO a
,我希望它更像是:
subPrompt :: Text -> (Text -> Bool) -> prompt -> IO (Maybe (Text, a))
但是我知道我做不到。我一直想尝试一种方法,以使签名中的第三个参数更清楚。有什么办法可以定义更清晰的类型?或者,也许我想得太多,IO a
真的很好-Haskell非常陌生。
答案 0 :(得分:2)
一种方法是将这两件事作为数据结构进行验证。所以:
{-# LANGUAGE GADTs #-}
data Prompt a where
Prompt :: Text -> (Text -> Either Text a) -> Prompt a
SubPrompt :: Text -> (Text -> Bool) -> Prompt a -> Prompt (Maybe (Text, a))
现在,由于SubPrompt
的第三个参数是Prompt
,您知道它必须是对SubPrompt
或Prompt
的调用-绝对不是任意的{{ 1}}可能会执行文件系统访问或其他讨厌操作的操作。
然后,您可以将此小型DSL的解释器写入IO
:
IO
除了确保您没有将任意runPrompt :: Prompt a -> IO a
runPrompt (Prompt cue validator) = {- what your old prompt used to do -}
runPrompt (SubPrompt cue deeper sub) = {- what your old subPrompt used to do,
calling runPrompt on sub where needed -}
作为IO
的参数的好处之外,这还有一个好处,即它使测试更加容易。稍后,您可以实现第二个完全纯净的解释器。例如,类似这样的内容,它接受要被视为用户输入的文本列表,并返回提示输出的文本列表:
SubPrompt
答案 1 :(得分:1)
将第二个提示符简化为简单的IO a
没什么问题-特别是如果您在其中记录了什么。
也就是说,是的,好的做法是使类型尽可能不言自明。您可以创建一个别名:
type Prompt a = IO a
,然后在subPrompt的签名中使用它:
subPrompt :: Text -> (Text -> Bool) -> Prompt a -> IO (Maybe (Text, a))
这使签名更不言自明,同时仍然允许您将任何IO a
作为第三个参数(关键字type
仅创建别名)。
但是,等等,还有更多:您宁可不要不小心传递实际上不是提示的任何IO a
!您不想让它通过IO操作,例如发射导弹...
因此,我们可以声明一个实际的Prompt类型(不仅是一个别名,而是一个实类型):
newtype Prompt a = Prompt { getPrompt :: IO a }
这使您可以将类型IO a
的任何值包装在类型内,以确保不会与具有相同类型但语义不同的其他函数混淆。
subPrompt的签名与以前相同:
subPrompt :: Text -> (Text -> Bool) -> Prompt a -> IO (Maybe (Text, a))
但是现在您不能仅将任何旧的IO a
传递给它;例如,要传递prompt
,则必须将其包装:
subPrompt "Do we proceed?" askYesNo (Prompt (prompt "Please enter your name" processName))
(subPrompt将无法直接调用它,但必须从包装器内部提取“提示”:let actualPrompt = getPrompt wrappedPrompt
)