如何验证 Haskell "public safe" 构造函数的参数?
How do I validate arguments to a Haskell "public safe" constructor?
在我的 Python package 中,我有一个函数可以用来创建我的 class 的经过验证的实例,例如
@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)
#...
我想在 Haskell 中强制执行相同的行为。但是以同样的方式(使用断言)这样做是行不通的,因为 Haskell 断言通常被禁用(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
不能依赖它来工作,因为在大多数情况下断言将被删除。
强制在 Haskell 中验证我的所有实例(使用 "public safe" 构造函数)的惯用且可靠的方法是什么?
正常的事情是明确地表达失败。例如,可以写
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
...
对于某些自定义类型 Error
,您甚至可以考虑从 Maybe
升级到 Except Error
,以便与调用者沟通在构造过程中出了什么问题。然后,您可以使用如下结构代替 guard
:
unless (all (inRange (1,26)) rngs') (throwError OutOfRange)
configEnigma
的调用者必须说明如何处理失败。对于 Maybe
,这看起来像
case configEnigma ... of
Just v -> -- use the configured enigma machine v
Nothing -> -- failure case; perhaps print a message to the user and quit
而使用 Except
时,您会得到有关错误信息:
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
在我的 Python package 中,我有一个函数可以用来创建我的 class 的经过验证的实例,例如
@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)
#...
我想在 Haskell 中强制执行相同的行为。但是以同样的方式(使用断言)这样做是行不通的,因为 Haskell 断言通常被禁用(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
不能依赖它来工作,因为在大多数情况下断言将被删除。
强制在 Haskell 中验证我的所有实例(使用 "public safe" 构造函数)的惯用且可靠的方法是什么?
正常的事情是明确地表达失败。例如,可以写
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
...
对于某些自定义类型 Error
,您甚至可以考虑从 Maybe
升级到 Except Error
,以便与调用者沟通在构造过程中出了什么问题。然后,您可以使用如下结构代替 guard
:
unless (all (inRange (1,26)) rngs') (throwError OutOfRange)
configEnigma
的调用者必须说明如何处理失败。对于 Maybe
,这看起来像
case configEnigma ... of
Just v -> -- use the configured enigma machine v
Nothing -> -- failure case; perhaps print a message to the user and quit
而使用 Except
时,您会得到有关错误信息:
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