В Haskell, выполняющем `and` и` or` для булевых функций
Я только что написал следующие две функции:
fand :: (a -> Bool) -> (a -> Bool) -> a -> Bool
fand f1 f2 x = (f1 x) && (f2 x)
f_or :: (a -> Bool) -> (a -> Bool) -> a -> Bool
f_or f1 f2 x = (f1 x) || (f2 x)
Они могут использоваться для объединения значений двух булевых функций, таких как:
import Text.ParserCombinators.Parsec
import Data.Char
nameChar = satisfy (isLetter `f_or` isDigit)
Посмотрев на эти две функции, я пришел к осознанию того, что они очень полезны. настолько, что теперь я подозреваю, что они либо включены в стандартную библиотеку, либо, скорее всего, есть чистый способ сделать это с помощью существующих функций.
Каков был "правильный" способ сделать это?
Ответы
Ответ 1
Одно упрощение,
f_and = liftM2 (&&)
f_or = liftM2 (||)
или
= liftA2 (&&)
= liftA2 (||)
в аппликативном функторе ((->) r)
.
Аппликационная версия
Почему? Мы имеем:
instance Applicative ((->) a) where
(<*>) f g x = f x (g x)
liftA2 f a b = f <$> a <*> b
(<$>) = fmap
instance Functor ((->) r) where
fmap = (.)
Итак:
\f g -> liftA2 (&&) f g
= \f g -> (&&) <$> f <*> g -- defn of liftA2
= \f g -> ((&&) . f) <*> g -- defn of <$>
= \f g x -> (((&&) . f) x) (g x) -- defn of <*> - (.) f g = \x -> f (g x)
= \f g x -> ((&&) (f x)) (g x) -- defn of (.)
= \f g x -> (f x) && (g x) -- infix (&&)
версия Monad
Или для liftM2
, имеем:
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
так:
\f g -> liftM2 (&&) f g
= \f g -> do { x1 <- f; x2 <- g; return ((&&) x1 x2) } -- defn of liftM2
= \f g -> f >>= \x1 -> g >>= \x2 -> return ((&&) x1 x2) -- by do notation
= \f g -> (\r -> (\x1 -> g >>= \x2 -> return ((&&) x1 x2)) (f r) r) -- defn of (>>=)
= \f g -> (\r -> (\x1 -> g >>= \x2 -> const ((&&) x1 x2)) (f r) r) -- defn of return
= \f g -> (\r -> (\x1 ->
(\r -> (\x2 -> const ((&&) x1 x2)) (g r) r)) (f r) r) -- defn of (>>=)
= \f g x -> (\r -> (\x2 -> const ((&&) (f x) x2)) (g r) r) x -- beta reduce
= \f g x -> (\x2 -> const ((&&) (f x) x2)) (g x) x -- beta reduce
= \f g x -> const ((&&) (f x) (g x)) x -- beta reduce
= \f g x -> ((&&) (f x) (g x)) -- defn of const
= \f g x -> (f x) && (g x) -- inline (&&)
Ответ 2
Это ужасно, если вы всегда хотите две функции, но я думаю, что я бы обобщил ее:
mapAp fs x = map ($x) fs
fAnd fs = and . mapAp fs
fOr fs = or . mapAp fs
> fOr [(>2), (<0), (== 1.1)] 1.1
True
> fOr [(>2), (<0), (== 1.1)] 1.2
False
> fOr [(>2), (<0), (== 1.1)] 4
True
Ответ 3
Полностью разрывая TomMD, я увидел and . map
и or . map
и не мог не хотеть его настроить:
fAnd fs x = all ($x) fs
fOr fs x = any ($x) fs
Я читаю это хорошо. fAnd
: все функции в списке True
, когда к ним применяется x
? fOr
: есть ли какие-либо функции в списке True
, когда к ним применяется x
?
ghci> fAnd [even, odd] 3
False
ghci> fOr [even, odd] 3
True
fOr - выбор нечетного имени. Конечно, хороший, чтобы бросить этих императивных программистов на цикл. =)
Ответ 4
В дополнение к тому, что сказал Дон, версии liftA2/liftM2
могут быть недостаточно ленивыми:
> let a .&&. b = liftA2 (&&) a b in pure False .&&. undefined
*** Exception: Prelude.undefined
Woops!
Поэтому вместо этого вам может понадобиться несколько другая функция. Обратите внимание, что для этой новой функции требуется ограничение Monad
- Applicative
.
> let a *&&* b = a >>= \a' -> if a' then b else return a' in pure False *&&* undefined
False
Это лучше.
Что касается ответа, который предлагает функцию on
, это для того, когда функции одинаковы, но аргументы разные. В данном случае функции разные, но аргументы одинаковы. Здесь ваш пример изменен так, чтобы on
был подходящим ответом:
(f x) && (f y)
который можно записать:
on (&&) f x y
PS: скобки не нужны.
Ответ 5
Это также можно сделать, используя Arrows:
import Control.Arrow ((&&&), (>>>), Arrow(..))
split_combine :: Arrow cat => cat (b, c) d -> cat a b -> cat a c -> cat a d
split_combine h f g = (f &&& g) >>> h
letter_or_digit = split_combine (uncurry (||)) isLetter isDigit
&&&
(не относящийся к &&
) разбивает вход; >>>
- состав стрелок/категорий.
Вот пример:
> map letter_or_digit "aQ_%8"
[True,True,False,False,True]
Это работает, потому что функции - ->
- являются экземплярами категории и стрелки. Сравнение типов сигнатур с примерами Don liftA2
и liftM2
показывает сходство:
> :t split_combine
split_combine :: Arrow cat => cat (b, c) d -> cat a b -> cat a c -> cat a d
> :t liftA2
liftA2 :: Applicative f => (b -> c -> d) -> f b -> f c -> f d
Помимо currying, обратите внимание, что вы можете почти преобразовать первый тип во второй, заменив cat a ---> f
и Arrow ---> Applicative
(другое отличие состоит в том, что split_combine
не ограничивается чистыми функциями в первом аргументе; вероятно, не важно, хотя).
Ответ 6
Это было упомянуто, но более сложным способом. Вы можете использовать аппликативные материалы.
Для функций, в основном, это то, что он передает один и тот же аргумент нескольким функциям, которые вы можете комбинировать в конце.
Итак, вы можете реализовать его следующим образом:
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
Для каждого <*>
в цепочке вы получаете другую функцию, которая применяется к a, а затем вы суммируете весь вывод вместе, используя fmap
, попеременно записываемую как <$>
. Причина, по которой это работает с &&
, состоит в том, что она принимает два аргумента, и у нас есть две функции вместе. Если там была дополнительная звезда, и еще одна проверка, вам нужно написать что-то вроде:
(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
проверьте это для получения дополнительных примеров
Ответ 7
Если f1 и f2 совпадают, вы можете использовать 'on':
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
в базе Data.Function
fand1 f = (&&) `on` f
for1 f = (||) `on` f
Типичное использование:
Data.List.sortBy (compare `on` fst)
(из Hoogle)