Тип автоматического преобразования в 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

person wenlong    schedule 29.10.2016    source источник
comment
Есть пакет Boolean.   -  person Alex R    schedule 01.11.2016


Ответы (2)


Вы можете попробовать это

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

Вынося семейство типов из класса, вы можете определить его для всех типов. Тогда остается только ограничить один из экземпляров. Не забывай, тебе тоже нужно сделать это OVERLAPPABLE

person Luka Horvat    schedule 29.10.2016
comment
Я определяю && как (toBool -> BoolLike f) && (toBool -> BoolLike g) = BoolLike $ \k -> f k P.&& g k, а || аналогично. Выглядит хорошо, пока let relation = 1 && 2 || 3, ghci не может вывести (BoolItem a10 ~ BoolLike a3). Полный исходный код: github.com/qzchenwl/BoolLike/blob/master /src/BoolLike.hs - person wenlong; 29.10.2016
comment
Это нормально для Char. Я обновил Main.hs - person wenlong; 29.10.2016
comment
@wenlong, это потому, что 1, 2 и т. д. имеют немономорфный тип, то есть Num a => a. Если тип однозначный, например (1 :: Int) && (2 :: Int) || (3 :: Int), работает. - person dkasak; 29.10.2016
comment
Чтобы немного расширить, потенциально может быть экземпляр Num для BoolLike, и тогда другой экземпляр будет соответствовать. Есть способы обойти это иногда, и я сейчас смотрю, смогу ли я его найти, но аннотирование вещей, вероятно, самый чистый вариант. - person Luka Horvat; 29.10.2016

Этот ответ, вероятно, не будет вам полезен, поскольку, по всей вероятности, вы уже рассмотрели альтернативу, которую я собираюсь предложить, и сочли ее недостаточной для ваших дьявольских целей. Тем не менее, читатели, которые сталкиваются с этим вопросом, могут счесть полезным узнать, как достичь чего-то похожего на то, что вы искали, если не совсем такого изящного, без уловок с классами типов.

В ваших планах BoolLike a ...

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

... состоит из функции, которая выдает Bool результат при a -> Bool продолжении. Все ваши примеры использования сводятся к объединению результатов Bool с (&&) и (||) перед предоставлением продолжения. Этого можно достичь, используя экземпляр Applicative для функций (в данном случае (->) (a -> Bool)) и используя _10 _ / _ 11_ для продвижения простых значений в (a -> Bool) -> Bool «приостановленные вычисления»:

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 < / em>.)

С их помощью дополнительный линейный шум становится довольно легким:

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
person duplode    schedule 29.10.2016
comment
Также стоит отметить, что если вы пометите свои нижние значения, как здесь, с помощью $, это также устранит проблему OP, поскольку тогда операторы могут быть реализованы только для BoolLikes, и они не обязательно должны быть полиморфными. - person Luka Horvat; 29.10.2016