haskell 中的自动转换类型

Auto convert type in haskell

我编写了一些有用的函数来进行逻辑运算。看起来像 (a and b or c) `belongs` x.

感谢 NumIsListOverloadedLists,我可以将它用于整数和列表。

λ> (1 && 2 || 3) `belongs` [2]
False
λ> (1 && 2 || 3) `belongs` [1, 2]
True
λ> (1 && 2 || 3) `belongs` [3]
True

λ> :set -XOverloadedLists
λ> ([1, 2] && [2, 3] || [3, 4]) `contains` 1
False
λ> ([1, 2] && [2, 3] || [3, 4]) `contains` 2
True

我是这样做的:

newtype BoolLike a = BoolLike ((a -> Bool) -> Bool)

(&&) :: BoolLike a -> BoolLike a -> BoolLike a
BoolLike f && BoolLike g = BoolLike $ \k -> f k P.&& g k
infixr 3 &&

toBoolLike :: a -> BoolLike a
toBoolLike x = BoolLike $ \k -> k x

belongs :: Eq a => BoolLike a -> [a] -> Bool
belongs (BoolLike f) xs = f (\x -> x `elem` xs)

contains :: Eq a => BoolLike [a] -> a -> Bool
contains (BoolLike f) x = f (\xs -> x `elem` xs)

instance Num a => Num (BoolLike a) where
  fromInteger = toBoolLike . fromInteger

我要做的是让它适用于任何类型,而无需手动将 a 转换为 BoolLike a

如何实现?

第一次尝试:

class IsBool a where
  type BoolItem a
  toBool :: a -> BoolItem a

instance IsBool (BoolLike a) where
  type BoolItem (BoolLike a) = BoolLike a
  toBool = id

instance IsBool a where
  type BoolItem a = BoolLike a
  toBool = toBoolLike

失败:

Conflicting family instance declarations:
  BoolItem (BoolLike a) -- Defined at BoolLike.hs:54:8
  BoolItem a -- Defined at BoolLike.hs:58:8

你可以试试这个

type family BoolItem a where
    BoolItem (BoolLike a) = BoolLike a
    BoolItem a = BoolLike a

class IsBool a where
  toBool :: a -> BoolItem a

instance IsBool (BoolLike a) where
  toBool = id

instance (BoolItem a ~ BoolLike a) => IsBool a where
  toBool = toBoolLike

通过将类型族从 class 中移出,您可以为所有类型定义它。然后剩下的就是限制其中一个实例。别忘了你还需要制作那个 OVERLAPPABLE

这个答案可能对您没有用,因为您很可能已经考虑过我将建议的替代方案,并认为它不足以满足您的邪恶目的。尽管如此,偶然发现这个问题的读者可能会发现了解如何实现与您正在寻找的东西类似的东西很有用,如果不是很漂亮,没有类型 class 技巧。

在您的计划中,BoolLike a ...

newtype BoolLike a = BoolLike ((a -> Bool) -> Bool)

... 由一个函数组成,该函数在给定 a -> Bool 延续时会产生 Bool 结果。您的使用示例全部归结为在提供延续之前将 Bool 结果与 (&&)(||) 组合。这可以使用函数的 Applicative 实例(在本例中为 (->) (a -> Bool))并使用 (&)/flip ($) 将普通值提升为 (a -> Bool) -> Bool "suspended computations":

GHCi> ((||) <$> ((&&) <$> ($ 1) <*> ($ 2)) <*> ($ 3)) (`elem` [2])
False

当然,写起来一点也不整洁。但是,我们可以通过定义来改进很多东西:

(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
x <&&> y = (&&) <$> x <*> y

(<||>) :: Applicative f => f Bool -> f Bool -> f Bool
x <||> y = (||) <$> x <*> y

(有关定义它们的小库,请查看 control-bool。)

有了这些,额外的线路噪声变得非常轻:

GHCi> (($ 1) <&&> ($ 2) <||> ($ 3)) (`elem` [2])
False

这对于 contains 情况也是开箱即用的——它所需要的只是改变提供的延续:

GHCi> (($ [1, 2]) <&&> ($ [2, 3]) <||> ($ [3, 4])) (elem 1)
False

作为最后的说明,值得指出的是 contains 的情况可以直接用 intersectunion 的形式表示 Data.List:

GHCi> [1, 2] `intersect` [2, 3] `union` [3, 4] & elem 1
False