How do I validate arguments to a Haskell “public safe” constructor?

风格不统一 提交于 2019-12-12 09:19:35

问题


In my Python package I have a function I use to create validated instances of my class, with something like

@staticmethod
def config_enigma(rotor_names, window_letters, plugs, rings):

    comps = (rotor_names + '-' + plugs).split('-')[::-1]
    winds = [num_A0(c) for c in 'A' + window_letters + 'A'][::-1]
    rngs = [int(x) for x in ('01.' + rings + '.01').split('.')][::-1]

    assert all(name in rotors for name in comps[1:-1])
    assert comps[-1] in reflectors
    assert len(rngs) == len(winds) == len(comps)
    assert all(1 <= rng <= 26 for rng in rngs)
    assert all(chr_A0(wind) in LETTERS for wind in winds)

    #...

and I would like to enforce the same behavior in Haskell. But doing so in the same way — with assertions — does not work, because Haskell assertions are disabled in general (unless certain compiler flags are set). For example, in something like

configEnigma rots winds plug rngs =
    assert ((and $ (==(length components')) <$> [length winds', length rngs']) &&
            (and $ [(>=1),(<=26)] <*> rngs') &&
            (and $ (`elem` letters) <$> winds') &&
            (and $ (`M.member` comps) <$> tail components'))
    -- ...
    where
        rngs' = reverse $ (read <$> (splitOn "." $ "01." ++ rngs ++ ".01") :: [Int])
        winds' = "A" ++ reverse winds ++ "A"
        components' = reverse $ splitOn "-" $ rots ++ "-" ++ plug

can't be relied on to work because the assertions will be removed in most contexts.

What is an idiomatic and reliable way to force all my instances to be validated in Haskell (using a "public safe" constructor)?


回答1:


The normal thing is to express failure explicitly. For example, one might write

configEnigma :: ... -> Maybe ...
configEnigma ... = do
    guard (all (((==) `on` length) components') [winds', rngs'])
    guard (all (inRange (1,26)) rngs')
    guard (all (`elem` letters) winds')
    guard (all (`M.member` comps) (tail components'))
    return ...
    where
    ...

You might even consider upgrading from Maybe to Except Error for some custom-defined type Error, to communicate to the caller what it was that went wrong during construction. Then instead of guard, you could use a construction like:

    unless (all (inRange (1,26)) rngs') (throwError OutOfRange)

The caller to configEnigma will have to express how to handle failures. For Maybe, this looks like

case configEnigma ... of
    Just v  -> -- use the configured enigma machine v
    Nothing -> -- failure case; perhaps print a message to the user and quit

while with Except you get information about what went wrong:

case runExcept (configEnigma ...) of
    Right v  -> -- use the configured enigma machine v
    Left err -> -- failure case; tell the user exactly what went wrong based on err and then quit


来源:https://stackoverflow.com/questions/33713212/how-do-i-validate-arguments-to-a-haskell-public-safe-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!