В Haskell, выполняющем `and` и` or` для логических функций - PullRequest
40 голосов
/ 19 апреля 2011

Я только что написал следующие две функции:

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 ]

46 голосов
/ 19 апреля 2011

Одно упрощение,

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 (&&)
8 голосов
/ 19 апреля 2011

Абсолютно срывая 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 - выбор нечетного имени.Конечно, это хороший способ бросить этих императивных программистов в цикл .=)

7 голосов
/ 19 апреля 2011

Ужаснее, если вы всегда хотите две функции, но я думаю, я бы обобщил это:

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
2 голосов
/ 19 апреля 2011

Вдобавок к тому, что сказал Дон, версии 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: скобки не нужны.

1 голос
/ 25 февраля 2015

Это уже упоминалось, но более сложным способом.Вы можете использовать аппликативные вещи.

Для функций в основном то, что он делает, это передает один и тот же аргумент ряду функций, которые вы можете объединить в конце.

Таким образом, вы можете реализовать это следующим образом:

(&&) <$> aCheckOnA <*> anotherCheckOnA $ a

Для каждого <*> в цепочке вы получаете другую функцию, которая применяется к a, а затем вы комбинируете все выходные вместе, используя fmap, поочередно записываемый как <$>.Причина, по которой это работает с &&, заключается в том, что он принимает два аргумента, и у нас есть две функции, помеченные вместе.Если бы там была лишняя звезда и еще одна проверка, вам нужно было бы написать что-то вроде:

(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a

проверьте это , чтобы найти больше примеров

1 голос
/ 28 ноября 2012

Это также можно сделать, используя Стрелки :

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]

Это работает, потому что функции - -> - являются экземплярами Category и Arrow.Сравнение сигнатур типов с примерами Дона 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-м аргументе; хотя, вероятно, это и не важно).

0 голосов
/ 19 апреля 2011

Если 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 )

...