问题
I've written some helpful functions to do logical operation. It looks like (a and b or c) `belongs` x
.
Thanks to Num
, IsList
and OverloadedLists
, I can have it for integers and lists.
λ> (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
I do it this way:
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
What I am going to do is make it suit for any types, without manually convert a
to BoolLike a
.
How can I achieve it?
First try:
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
Failed:
Conflicting family instance declarations:
BoolItem (BoolLike a) -- Defined at BoolLike.hs:54:8
BoolItem a -- Defined at BoolLike.hs:58:8
回答1:
You could try this
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
By moving the type family out of the class you can define it for all types. Then all that remains is restricting one of the instances. Don't forget you'll also need to make that one OVERLAPPABLE
回答2:
This answer probably won't be useful to you, as in all likelihood you have already considered the alternative I am going to suggest and deemed it not enough for your devilish purposes. Still, readers who stumble upon this question might find it useful to know how to achieve something similar to what you were looking for, if not quite as nifty, without type class trickery.
In your plans, a BoolLike a
...
newtype BoolLike a = BoolLike ((a -> Bool) -> Bool)
... consists of a function which produces a Bool
result when given an a -> Bool
continuation. Your usage examples all boil down to combining the Bool
results with (&&)
and (||)
before supplying the continuation. That can be achieved using the Applicative
instance for functions (in this case, (->) (a -> Bool)
) and using (&)
/flip ($)
to promote plain values into (a -> Bool) -> Bool
"suspended computations":
GHCi> ((||) <$> ((&&) <$> ($ 1) <*> ($ 2)) <*> ($ 3)) (`elem` [2])
False
That, of course, is not at all tidy to write. However, we can improve things quite a lot by defining:
(<&&>) :: Applicative f => f Bool -> f Bool -> f Bool
x <&&> y = (&&) <$> x <*> y
(<||>) :: Applicative f => f Bool -> f Bool -> f Bool
x <||> y = (||) <$> x <*> y
(For a little library defining them, have a look at control-bool.)
Armed with these, the extra line noise becomes quite lightweight:
GHCi> (($ 1) <&&> ($ 2) <||> ($ 3)) (`elem` [2])
False
This works out of the box for the contains
case as well — all that it takes is changing the supplied continuation:
GHCi> (($ [1, 2]) <&&> ($ [2, 3]) <||> ($ [3, 4])) (elem 1)
False
As a final note, it is worth pointing out the contains
case can be straightforwardly expressed in terms of intersect
and union
from Data.List
:
GHCi> [1, 2] `intersect` [2, 3] `union` [3, 4] & elem 1
False
来源:https://stackoverflow.com/questions/40316051/auto-convert-type-in-haskell