类型

时间:2017-05-12 18:46:03

标签: haskell

在编写一些网页请求代码时,我注意到我并没有清楚地了解在haskell中严格打字是多少被认为是好习惯。
首先是例子:

  • 会话可能包含一次使用密钥提取令牌
  • Hawk用于消息身份验证,但消息哈希以十六进制或base64编码,具体取决于端点

值得注意的是,sum类型无法解决这些问题,因为无效状态总是意味着程序员错误,并且处理此问题的唯一方法是崩溃。我认为有两种方法可以解决这个问题:

  • 少输入 - 需要keyFetch标记的函数是部分的,如果缺少则是错误,消息散列被编码为ByteString而没有任何newtype包装器
  • 更多输入 - 会话/身份验证adt在其包含的类型上进行参数化。因此Session' () ...是没有keyFetch令牌的会话,Session' Base64 ...是一个带有一个
  • 的会话

显然,这些方法中的任何一种都有缺点。如果没有额外的类型检查,很容易忘记一些边缘情况或错误处理数据 然而,参数化类型带来了一些相当大的可用性成本。类型别名使代码本身看起来不错,但它们不一定有助于编译器错误。将会话置于状态monad也不错,但是改变类型需要索引monad和RebindableSyntax,这看起来非常繁重。

这大致是打字版本较少的版本:

fetchKey :: Base64 -> IO PrivateKey
fetchKey = undefined

data Session = Session
             { _uid :: Base64
             , _keyFetchToken :: Maybe Base64
             -- ...
             }

getAuthorization :: Session -> IO Auth
getAuthorization session = case session ^. keyFetchToken of
    Just token -> ... (fetchKey token)
    Nothing -> throw MissingKeyFetchToken -- No way to recover

提问时间:社区是否就如何应对这种轻微动态的情况达成共识?

1 个答案:

答案 0 :(得分:1)

这里的解决方案是不依赖于您的类型,并使用不包含底部/未定义的Haskell的受限子集。

我的意思是你的类型应该准确地表示一个值是什么。例如,您的函数getAuthorization :: Session -> IO Auth是谎言,因为它表示

∀(s :: Session), (getAuthorization s :: IO Auth)

重要的部分是fo(forall)。它告诉您每个 Session映射到IO Auth,这不是真的; Session _keyFetchToken Nothing的{​​{1}}不会映射到正常的IO Auth值。 (它们映射到_|_,我们正在考虑将其作为我们受限制的Haskell子集的一部分)

要解决此问题,您可以更准确地将Session建模为

data Session keyType = Session {..., _keyFetchToken :: keyType}

现在,getAuthorization :: Session Base64 -> IO Auth不再撒谎;它将始终Session Base64映射到IO Auth。现在问题是如何从Session Base64获得Session (Maybe Base64)。这是迫使您为Nothing建立基本案例(恢复)的步骤。如果Nothing 总是一个不可恢复的错误,那么首先不要有Nothing,而只是崩溃,因为没有别的事可做,因为错误无法恢复。