Extract the value from a Solo. Very often, values should be extracted
directly using pattern matching, to control just what gets evaluated when.
getSolo is for convenience in situations where that is not the case:
When the result is passed to a strict function, it makes no difference
whether the pattern matching is done on the "outside" or on the
"inside":
Data.Set.insert (getSolo sol) set === case sol of Solo v -> Data.Set.insert v set
A traversal may be performed in Solo in order to control evaluation
internally, while using getSolo to extract the final result. A strict
mapping function, for example, could be defined
map' :: Traversable t => (a -> b) -> t a -> t b
map' f = getSolo . traverse ((Solo $!) . f)
Solo is the canonical lifted 1-tuple, just like Tuple2 is the canonical
lifted 2-tuple (pair) and Tuple3 is the canonical lifted 3-tuple (triple).
The most important feature of Solo is that it is possible to force its
"outside" (usually by pattern matching) without forcing its "inside",
because it is defined as a datatype rather than a newtype. One situation
where this can be useful is when writing a function to extract a value from
a data structure. Suppose you write an implementation of arrays and offer
only this function to index into them:
index :: Array a -> Int -> a
Now imagine that someone wants to extract a value from an array and store it
in a lazy-valued finite map/dictionary:
insert "hello" (arr index 12) m
This can actually lead to a space leak. The value is not actually extracted
from the array until that value (now buried in a map) is forced. That means
the entire array may be kept live by just that value! Often, the solution
is to use a strict map, or to force the value before storing it, but for
some purposes that's undesirable.
One common solution is to include an indexing function that can produce its
result in an arbitrary Applicative context:
indexA :: Applicative f => Array a -> Int -> f a
When using indexA in a pure context, Solo serves as a handy
Applicative functor to hold the result. You could write a non-leaky
version of the above example thus:
case arr indexA 12 of
Solo a -> insert "hello" a m
While such simple extraction functions are the most common uses for
unary tuples, they can also be useful for fine-grained control of
strict-spined data structure traversals, and for unifying the
implementations of lazy and strict mapping functions.
Class laws for tuples hold only up to laziness. The
Bifoldable methods are lazier than their Foldable counterparts.
For example the law bifoldr (flipconst) ≡ foldr does
not hold for tuples if laziness is exploited:
Class laws for tuples hold only up to laziness. The
Bitraversable methods are lazier than their Traversable counterparts.
For example the law bitraversepure ≡ traverse does
not hold for tuples if laziness is exploited:
Example2 expressions
>>> (bitraverse pure pure undefined :: IO (Int, Word)) `seq` ()()>>> (traverse pure (errorWithoutStackTrace "error!") :: IO (Int, Word)) `seq` ()*** Exception: error!
Eq2Tuple2Defined in base-4.23.0.0 · Data.Functor.Classes
Ord2Tuple2Defined in base-4.23.0.0 · Data.Functor.Classes
Read2Tuple2Defined in base-4.23.0.0 · Data.Functor.Classes
Show2Tuple2Defined in base-4.23.0.0 · Data.Functor.Classes
Generic1 (Tuple2a)Defined in ghc-internal-10.0.0 · GHC.Internal.Generics
(Lifta, Liftb) => Lift (a, b)Defined in ghc-internal-10.0.0 · GHC.Internal.TH.Lift
Monoida => Monad (Tuple2a)Defined in ghc-internal-10.0.0 · GHC.Internal.Base
Functor (Tuple2a)Defined in ghc-internal-10.0.0 · GHC.Internal.Base
Monoida => MonadFix (Tuple2a)Defined in ghc-internal-10.0.0 · GHC.Internal.Control.Monad.Fix
Solo is the canonical lifted 1-tuple, just like Tuple2 is the canonical
lifted 2-tuple (pair) and Tuple3 is the canonical lifted 3-tuple (triple).
The most important feature of Solo is that it is possible to force its
"outside" (usually by pattern matching) without forcing its "inside",
because it is defined as a datatype rather than a newtype. One situation
where this can be useful is when writing a function to extract a value from
a data structure. Suppose you write an implementation of arrays and offer
only this function to index into them:
index :: Array a -> Int -> a
Now imagine that someone wants to extract a value from an array and store it
in a lazy-valued finite map/dictionary:
insert "hello" (arr index 12) m
This can actually lead to a space leak. The value is not actually extracted
from the array until that value (now buried in a map) is forced. That means
the entire array may be kept live by just that value! Often, the solution
is to use a strict map, or to force the value before storing it, but for
some purposes that's undesirable.
One common solution is to include an indexing function that can produce its
result in an arbitrary Applicative context:
indexA :: Applicative f => Array a -> Int -> f a
When using indexA in a pure context, Solo serves as a handy
Applicative functor to hold the result. You could write a non-leaky
version of the above example thus:
case arr indexA 12 of
Solo a -> insert "hello" a m
While such simple extraction functions are the most common uses for
unary tuples, they can also be useful for fine-grained control of
strict-spined data structure traversals, and for unifying the
implementations of lazy and strict mapping functions.