如何解决如何优雅地处理算法的自定义点及其参数的约束?
以下面的算法为例,计算两个序列的最长公共子序列,复制自Rosetta Code:
longest xs ys = if length xs > length ys then xs else ys
lcs [] _ = []
lcs _ [] = []
lcs (x:xs) (y:ys)
| x == y = x : lcs xs ys
| otherwise = longest (lcs (x:xs) ys) (lcs xs (y:ys))
lcs
隐含地假设两个参数都是 Eq a => [a]
类型;实际上,如果我尝试明确地给出签名 lcs :: [a] -> [a] -> [a]
,那么 x == y
所在的行会发生错误,而签名 lcs :: Eq a => [a] -> [a] -> [a]
有效。
现在让我们假设我有两个列表 l1
和 l2
,都是 [(a,b)]
类型,并且我想要它们之间的 LCS,但是以使用 {{1 ==
定义中的 }} 运算符仅在每个元素的 lcs
之间(显然 snd
必须属于 b
类型类)。
我不仅可以为 Eq
提供两个列表,还可以提供相等运算符,在上面的特定示例中为 lcs
。 (==) `on` snd
的签名将是 lcs
。
然而,即使在想要使用普通 (a -> a -> Bool) -> [a] -> [a] -> [a]
的微不足道的情况下,这也会迫使用户提供相等运算符,并且将 (==)
参数包装在 (a -> a)
中不会也无济于事。
换句话说,我觉得在一般情况下,通过 Maybe
对参数的约束是可以的,但在其他情况下,人们可能希望传递一个自定义相等运算符来删除该约束,这使我成为功能的东西重载。
我应该怎么做?
解决方法
您只需提供两个 - 自定义运算符版本 lcsBy
和根据该版本实现的 Eq a =>
版本。
lcsBy :: (a->a->Bool) -> [a] -> [a] -> [a]
...
lcsBy compOp (x:xs) (y:ys)
| compOp x y = ...
...
lcs :: Eq a => [a] -> [a] -> [a]
lcs = lcsBy (==)
这类似于基础库如何提供 maximum
和 maximumBy
。
提供两者是更简单的选择,正如@leftaroundabout 所写。
不过,在某些特定情况下,还是有一些方法可以调用像
这样的函数lcs :: Eq a => [a] -> [a] -> [a]
提供像您这样的自定义相等运算符。例如,
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Coerce
import Data.Function
newtype OnSnd a b = OnSnd { getPair :: (a,b) }
instance Eq b => Eq (OnSnd a b) where
(==) = (==) `on` (snd . getPair)
lcsOnSnd :: forall a b. Eq b => [(a,b)] -> [(a,b)]
lcsOnSnd xs ys = coerce $ lcs (coerce xs) (coerce ys :: [OnSnd a b])
-- OR turn on TypeApplications,then:
-- lcsOnSnd = coerce (lcs @(OnSnd a b))
使用零成本安全强制将 [(a,b)]
转换为 [OnSnd a b]
,应用 lcs
(将使用 ==
的自定义 OnSnd a b
),并转换结果返回(零成本,再次)。
然而,要使这种方法起作用,==
必须在顶层定义,即它不能是依赖于 lcsOnSnd
的附加参数的泛型闭包。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。