Попытка моего новичка выразить поведение operator ||
из C/C++ в Haskell:
orElse :: Bool -> (a -> Bool) -> (a -> Bool)
orElse True _ = const True
orElse False pred = pred
Можно ли это обобщить, чтобы принять a -> b -> Bool
, a -> b -> c -> Bool
и т. д. в качестве второго аргумента?
Посмотрел техники перегрузки, ХКЦ ет. ал. - Кажется, ничто не связано с обобщением по роду...
Редактировать: теперь я понимаю, почему это был плохой пример (помогло чтение определения operator||
в прелюдии), но я все еще задаюсь вопросом о более широком вопросе в том виде, в каком он был поставлен.
Аналогия с operator||
— это последовательность: если левая сторона равна True
, то правая сторона не оценивается, независимо от требований строгости. Возможно, я здесь совершенно не в теме... предполагаемое использование было примерно таким: anyOf :: Foldable f => (a -> Bool) -> f a -> Bool
anyOf pred = foldl' (flip orElse pred) False
elem' :: (Foldable f, Eq a) => a -> f a -> Bool
elem' y = anyOf (== y)
Haskell ||
уже ленив во втором аргументе...
Хорошо, этот конкретный случай является спорным - спасибо! Возникает более широкий вопрос: возможно ли вообще работать с функциями разных типов? Или это просто то, что никогда не требуется по разным причинам, например, указанным выше?
Я представлял себе использование семейства типов для рекурсивного распознавания функций, которые в конечном итоге возвращают Bool
, но я не уверен, как (или если бы) вы это написали.
Вероятно, вы можете создать нужную функцию, используя классы типов, но я не уверен, что это хорошая идея.
class MagicOr a where
magicOr :: a -> a -> a
instance MagicOr Bool where
magicOr = (||)
instance MagicOr b => MagicOr (a -> b) where
magicOr f g = \x -> f x `magicOr` g x
-- or, alternatively,
-- magicOr = liftA2 magicOr
В приведенном выше коде определяется функция magicOr
, которую можно использовать с двумя аргументами типа Bool
, типа a -> Bool
, типа a -> b -> Bool
и т. д.
На данный момент я думаю, что этот код не так уж и плох и вполне пригоден для использования.
Тем не менее, в зависимости от того, что вам действительно нужно сделать, вы можете в конечном итоге создать сложный механизм классов типов, который может быть несколько хрупким в использовании. Пример: printf. Его определение довольно сложно, поскольку ему приходится обрабатывать неизвестное количество параметров. Кроме того, при неправильном использовании это может сбить с толку программу проверки типов, которая будет выдавать сообщения об ошибках, которые трудно понять.
Конечно, не стесняйтесь экспериментировать и убедитесь сами. Имейте в виду, что слишком частое злоупотребление классами типов иногда приводит к громоздкому коду.
Спасибо! Думаю, я понимаю здесь шаблон типовых классов...
Обратите внимание на сходство с экземплярами Semigroup
для Any и для функций. Единственная разница здесь в том, что вы избегаете newtype
.
@NaïmFavier Хорошая мысль. При использовании a -> b -> ... -> Any
мой magicOr
равен просто (<>)
. А Any
— это просто Bool
с другим именем (так что используется операция или-полугруппа).
Как это связано с
operator ||
? Я ожидалorElse :: Bool -> Bool -> Bool
--- зачем дополнительный аргументa
и почему только второй аргумент?