{- SubZero, A haskell library to provide a useful data structure Copyright (C) 2017 Tristan Wibberley This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {- | Module : Control.Applicative.SubZero Description : SubZero type and related functions Copyright : (c) Tristan Wibberley, 2017 License : GPL-2 Maintainer : tristan.wibberley@gmail.com Stability : experimental Here is a longer description of this module, containing some commentary with @some markup@. -} module Control.Applicative.SubZero ( SubZero -- * Constructors and Translators -- -- $constructors , points , foldlA1 , letStand , fromSubZero , collapse ) where import Control.Applicative import Data.Monoid import Data.Maybe import Data.Function import Data.Functor.Compose {- | Converts a functor so that each point at the source has alternatives. It's just like Compose but the applicative instance appends new alternative values in the rightmost (inner/minor) functor instead of in the leftmost (outer/major) functor. The result is that two ZipLists of alternatives zip together, providing alternatives to each point and two Given the immediate utility of this, I do wonder if the Alternative instance of Compose is the wrong one. f = major functor g = superposition policy - how do they collapse? a = transformed value type -} newtype SubZero f g a = SubZero { getSubZero :: Compose f g a } deriving (Show, Read, Eq, Ord, Functor, Applicative) instance (Applicative f, Alternative g) => Alternative (SubZero f g) where empty = SubZero $ Compose $ pure empty (SubZero (Compose a)) <|> (SubZero (Compose b)) = SubZero $ Compose $ (<|>) <$> a <*> b -- A foldl for foldable alternatives, empty stays empty, nonempty becomes one alternative -- The applicative you get back can be a different type than the input foldlA1 :: (Foldable f, Alternative g) => (a -> a -> a) -> f a -> g a foldlA1 f a | null a = empty | otherwise = pure $ foldl1 f a -- Turns a value to Just value or Nothing based on a predicate assuming you use it in a -- context that wants Maybe instead of some other representation of Alternatives letStand :: (Alternative f) => (a -> Bool) -> a -> f a letStand f x | f x = pure x | otherwise = empty {- $constructors Constructors are good -} {- | Turns a container of values to a container of either retained or destroyed values based on a predicate The type constraint allows us to model possible outcomes so destroyed values are just "no possible outcomes" while retained values represent "the only possible outcome". To represent that "no value" is a possible outcome, @a@ should be some type like ('Maybe' a) or ('Either' 'String' a). [@f@]: This functor defines the broad scale behaviours but its Alternative instance is overridden. This in particular might change during upcoming design validation. [@g@]: This functor supplies the supercedent Alternative instance and thus the finer behaviours. -} points :: (Functor f, Alternative g) => (a -> Bool) -- ^ A predicate that indicates whether a point -- is occupied by its original value or vacant. -> f a -- ^ The seed points with their values. -> SubZero f g a -- ^ The constructed @SubZero@ value. points f = SubZero . Compose . ((letStand f) <$>) -- If the type of the possibilities concept is Maybe then you can -- use fromSubZero with a default for impossible points fromSubZero :: (Applicative f) => f a -> SubZero f Maybe a -> f a fromSubZero a (SubZero (Compose b)) = fromMaybe <$> a <*> b -- Take the alternatives embedded in the SubZero and collapse them -- with a collapse function to a single alternative or empty (no possible outcomes) -- This is quite free in the type of the possibilities concept of the result -- It's compatible with Maybe for further uses with fromSubZero, but you can -- retain behaviours of more sophisticated types. A consequence of this -- is that you will probably require to state a type collapse :: (Functor f, Foldable g, Alternative h) => (a -> a -> a) -> SubZero f g a -> SubZero f h a collapse f (SubZero (Compose a)) = SubZero $ Compose $ (foldlA1 f) <$> a