如何使此功能的签名更精确

时间:2019-06-19 02:56:10

标签: haskell types

我有两个功能:

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非常陌生。

2 个答案:

答案 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,您知道它必须是对SubPromptPrompt的调用-绝对不是任意的{{ 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