Получение Bool из [Возможно, Bool], который гарантированно содержит хотя бы один только

#haskell #functional-programming #monads #maybe #monadplus

Вопрос:

У меня есть входные list данные типа [Maybe SomeType] и предикат p типа SomeType -> Bool , и я хочу ответить на вопрос «Сохраняется ли предикат p для всех SomeType s, которые оказались во входных данных?».

Первая часть проста: (map . fmap) p list имеет тип [Maybe Bool] .

Одна важная информация заключается в том, что я это знаю, length list >= 1 и all isNothing list == False обе они верны, так что должен быть хотя бы Just True вход (map . fmap) p list .

Но как мне вытащить один Bool из этого списка?

Я подумал, что мог бы воспользоваться преимуществами сворачивания (например, через foldl ) и Maybe , MonadPlus например, сделать что-то вроде следующего:

 allTrueOrNothing :: [Maybe Bool] -> Bool
allTrueOrNothing = fromJust . foldl mplus mzero
 

но это не совсем верно, потому mplus что возвращает левый операнд, если он Just something независимо от того, что something есть, поэтому allTrueOrNothing вернется True , даже если его входные [Just True, Just False] данные .

Каков самый чистый/наиболее идиоматичный способ, которым я могу выполнить эту задачу?

Я вижу, что я мог бы просто filter вывести Nothing s, а затем and вместе Just s, что-то вроде этого:

 allTrueOrNothing' :: [Maybe Bool] -> Bool
allTrueOrNothing' = all fromJust . filter (fmap not isNothing)
 

Но мне было более любопытно узнать, есть ли способ заставить этих Maybe Bool людей вести себя так, как будто Monoid они знают о его Bool содержании.

Комментарии:

1. Можете ли вы получить представление о Nothing значениях перед нанесением на карту p ? all p . catMaybes . Поскольку all p [] == True не имеет значения , есть ли хотя бы одно не — Nothing значение в исходном вводе.

Ответ №1:

Я бы просто использовал all напрямую:

 all . all :: (a -> Bool) -> [Maybe a] -> Bool
 

Если по какой-то причине вам необходимо различать фазы, которые вы описываете, вы можете использовать специализацию and = all id :

 all and :: [Maybe Bool] -> Bool
 

Комментарии:

1. Мне потребовалась секунда, чтобы понять, что это работает, потому [Maybe a] что имеет форму f (g a) , в которой f и то, и g другое складывается. Умно (возможно, немного чересчур; -), но мне это нравится).

2. Foldable Сочиняете ли вы вообще?

3. @chepner Да, через foldMap = foldMap . foldMap .

4. @DanielWagner, конечно, проверяет тип, но я не был уверен, что существует какой-то закон, который может быть нарушен в зависимости от моноида.

5. @chepner Foldable Законы не очень интересны: если вы определяете только foldMap , все законы удовлетворяются реализациями других методов по умолчанию, потому что законы связывают Foldable методы только друг с другом.

Ответ №2:

Кажется, это работает:

 > and . catMaybes $ [Just False, Nothing, Just False]
False
> and . catMaybes $ [Just False, Nothing, Just True]
False
> and . catMaybes $ [Just True, Nothing, Just True]
True
 

Вы можете использовать catMaybes для преобразования списка в [Bool] и and для завершения.

(Обратите внимание, что это вернется True в список всех Nothing , что, согласно вашим предположениям, является «невозможным» случаем.)

Если вы абсолютно хотите использовать моноид, я думаю, вы можете это сделать, но это немного громоздко. Это включало бы в себя обертывание каждого элемента списка в некоторые newtype And = And (Maybe Bool) , затем определение соответствующего экземпляра моноида, затем mconcat повторение и, наконец, разворачивание.

Непроверенный код:

 newtype And = And (Maybe Bool)

instance Semigroup And where
   And Nothing  <> x            = x
   x            <> And Nothing  = x
   And (Just a) <> And (Just b) = And (Just (a amp;amp; b))

instance Monoid And where
   mempty = Nothing

allTrueOrNothing :: [Maybe Bool] -> Bool
allTrueOrNothing = fromMaybe False . coerce . mconcat @And . coerce
 

Комментарии:

1. newtype And = And (Maybe Bool) ?

2. @AlexeyRomanov Я отредактировал с соответствующими экземплярами. Надеюсь, теперь это более ясно.

3. newtype And = And (Maybe Bool) deriving (Semigroup, Monoid) via Maybe All

Ответ №3:

Самый чистый способ-это and . catMaybes .

Но вы хотели использовать Моноид, который знает о его Bool содержании, в amp;amp; некотором роде. Это All :

 > foldMap (fmap All) [Just True,Nothing,Just False]
Just (All {getAll = False})

> foldMap (fmap All) [Just True,Nothing,Just True]
Just (All {getAll = True})
 

Комментарии:

1. Принимая это, поскольку в нем более подробно рассматривается вопрос, имхо, несмотря на то, что ответ Foldable , основанный на примере Maybe , тоже велик.

2. @Enlico вы имели в виду «помимо», возможно, вместо «несмотря»? но в любом случае это не так. он использует instance Monoid a => Monoid (Maybe a) и instance Foldable [] : foo :: (a -> Maybe All) -> [a] -> Maybe All ; foo = foldMap также работает. (ср. foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m ).:)

3. Нет, я имел в виду, несмотря ни на что. Проблема в том, что я по ошибке написал вместо другого прямо перед ответом . Я имел в виду ответ Даниэля Вагнера.