haskell 中的自动转换类型
Auto convert type in haskell
我编写了一些有用的函数来进行逻辑运算。看起来像 (a and b or c) `belongs` x
.
感谢 Num
、IsList
和 OverloadedLists
,我可以将它用于整数和列表。
λ> (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
的情况可以直接用 intersect
和 union
的形式表示 Data.List
:
GHCi> [1, 2] `intersect` [2, 3] `union` [3, 4] & elem 1
False
我编写了一些有用的函数来进行逻辑运算。看起来像 (a and b or c) `belongs` x
.
感谢 Num
、IsList
和 OverloadedLists
,我可以将它用于整数和列表。
λ> (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
的情况可以直接用 intersect
和 union
的形式表示 Data.List
:
GHCi> [1, 2] `intersect` [2, 3] `union` [3, 4] & elem 1
False