В Haskell, выполняющем `и " и " или " для булевых функций
Я только что написал следующие две функции:
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)
каков был "правильный" способ сделать это?
7 ответов
одно упрощение,
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 (&&)
монады версии
или 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 (&&)
это уродливее, если вы всегда хотите две функции, но я думаю, что я бы обобщил его:
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
полностью отрывая Томмда, я видел 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-странный выбор имени. Конечно, хороший, чтобы бросить этих императивных программистов для цикл. =)
в довершение всего, что сказал Дон,liftA2/liftM2
версии могут быть недостаточно ленивыми:
>let a .&&. b = liftA2 (&&) a b in pure False .&&. undefined
*** Exception: Prelude.undefined
ой!
поэтому вместо этого Вам может понадобиться немного другая функция. Обратите внимание, что для этой новой функции требуется Monad
ограничение -- Applicative
недостаточно.
>let a *&&* b = a >>= \a' -> if a' then b else return a' in pure False *&&* undefined
False
что лучше.
что касается ответа, который предполагает
это также можно сделать с помощью стрелки:
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]
это работает, потому что функции -- ->
-- являются экземплярами категории и Стрелки. Сравнивая тип подписи к Дону 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
помимо выделки, обратите внимание, что вы можете почти преобразовать первый тип во второй, заменив cat a ---> f
и Arrow ---> Applicative
(другая разница в том, что split_combine
не ограничивается принятием чистых функций в своем 1-м аргументе; вероятно, это не важно).
это было упомянуто, но более сложным образом. Вы можете использовать аппликатор.
для функций в основном он передает один и тот же аргумент ряду функций, которые вы можете объединить в конце.
таким образом, вы можете реализовать его следующим образом:
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
для каждого <*>
в цепочке затем вы получаете другую функцию, которая применяется к a, а затем вы munge все выходные данные вместе с помощью fmap
поочередно записывается как <$>
. Этот причина этого работает с &&
потому что он принимает два аргумента, и у нас есть 2 функции, снятые вместе. Если бы там была лишняя звезда и еще один чек, вам пришлось бы написать что-то вроде:
(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
зацените дополнительные примеры
Если f1 и f2 одинаковы, то вы можете использовать "on":
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
в базу данных.Функция
fand1 f = (&&) `on` f
for1 f = (||) `on` f
типичное использование:
Data.List.sortBy (compare `on` fst)
(от Hoogle)