summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libraries/base/Data/Tuple.hs4
-rw-r--r--libraries/base/GHC/Read.hs20
-rw-r--r--libraries/base/GHC/Show.hs4
-rw-r--r--libraries/base/tests/all.T1
-rw-r--r--libraries/base/tests/read-show-solo.hs9
-rw-r--r--libraries/base/tests/read-show-solo.stdout5
-rw-r--r--libraries/ghc-prim/GHC/Tuple.hs50
7 files changed, 89 insertions, 4 deletions
diff --git a/libraries/base/Data/Tuple.hs b/libraries/base/Data/Tuple.hs
index 569dd14da0..5b64f17601 100644
--- a/libraries/base/Data/Tuple.hs
+++ b/libraries/base/Data/Tuple.hs
@@ -16,7 +16,8 @@
-----------------------------------------------------------------------------
module Data.Tuple
- ( fst
+ ( Solo (..)
+ , fst
, snd
, curry
, uncurry
@@ -24,6 +25,7 @@ module Data.Tuple
) where
import GHC.Base () -- Note [Depend on GHC.Tuple]
+import GHC.Tuple (Solo (..))
default () -- Double isn't available yet
diff --git a/libraries/base/GHC/Read.hs b/libraries/base/GHC/Read.hs
index 7f698ec498..43e5ee5b32 100644
--- a/libraries/base/GHC/Read.hs
+++ b/libraries/base/GHC/Read.hs
@@ -669,7 +669,25 @@ instance Read () where
readList = readListDefault
-- | @since 4.15
-deriving instance Read a => Read (Solo a)
+instance Read a => Read (Solo a) where
+ -- Since our `show` doesn't show record syntax, we want to accept non-record
+ -- syntax. Since Solo is actually a record, it only seems fair to accept
+ -- record syntax as well.
+ readPrec = parens $
+ (prec appPrec $
+ do expectP (L.Ident "Solo")
+ x <- step readPrec
+ return (Solo x))
+ +++
+ (prec appPrec1
+ (do expectP (L.Ident "Solo")
+ expectP (L.Punc "{")
+ x <- readField
+ "getSolo" (reset readPrec)
+ expectP (L.Punc "}")
+ return (Solo x)))
+
+ readListPrec = readListPrecDefault
-- | @since 2.01
instance (Read a, Read b) => Read (a,b) where
diff --git a/libraries/base/GHC/Show.hs b/libraries/base/GHC/Show.hs
index 04fbcb6112..ecfb7dbe0f 100644
--- a/libraries/base/GHC/Show.hs
+++ b/libraries/base/GHC/Show.hs
@@ -169,7 +169,9 @@ appPrec1 = I# 11# -- appPrec + 1
deriving instance Show ()
-- | @since 4.15
-deriving instance Show a => Show (Solo a)
+instance Show a => Show (Solo a) where
+ showsPrec d (Solo x) = showParen (d > 10) $
+ showString "Solo " . showsPrec 11 x
-- | @since 2.01
instance Show a => Show [a] where
diff --git a/libraries/base/tests/all.T b/libraries/base/tests/all.T
index ebbf81ec52..371ed56327 100644
--- a/libraries/base/tests/all.T
+++ b/libraries/base/tests/all.T
@@ -45,6 +45,7 @@ test('inits', normal, compile_and_run, [''])
test('genericNegative001', extra_run_opts('-1'), compile_and_run, [''])
test('ix001', normal, compile_and_run, [''])
test('isValidNatural', normal, compile_and_run, [''])
+test('read-show-solo', normal, compile_and_run, [''])
# need to add -K64m to the compiler opts, so that GHCi gets it too
test('ioref001',
diff --git a/libraries/base/tests/read-show-solo.hs b/libraries/base/tests/read-show-solo.hs
new file mode 100644
index 0000000000..553e837836
--- /dev/null
+++ b/libraries/base/tests/read-show-solo.hs
@@ -0,0 +1,9 @@
+module Main (main) where
+import Data.Tuple (Solo (..))
+
+main = do
+ print $ Solo (3 :: Int)
+ print $ Solo (Just "")
+ print $ Just (Solo "")
+ print (read (show (Solo (3 :: Int))) :: Solo Int)
+ print (read "Just Solo { getSolo = 5 }" :: Maybe (Solo Int))
diff --git a/libraries/base/tests/read-show-solo.stdout b/libraries/base/tests/read-show-solo.stdout
new file mode 100644
index 0000000000..9478a3df92
--- /dev/null
+++ b/libraries/base/tests/read-show-solo.stdout
@@ -0,0 +1,5 @@
+Solo 3
+Solo (Just "")
+Just (Solo "")
+Solo 3
+Just (Solo 5)
diff --git a/libraries/ghc-prim/GHC/Tuple.hs b/libraries/ghc-prim/GHC/Tuple.hs
index b45ae023bf..9254ab72ae 100644
--- a/libraries/ghc-prim/GHC/Tuple.hs
+++ b/libraries/ghc-prim/GHC/Tuple.hs
@@ -29,7 +29,55 @@ data () = ()
-- The desugarer uses 1-tuples,
-- but "()" is already used up for 0-tuples
-- See Note [One-tuples] in GHC.Builtin.Types
-data Solo a = Solo a
+
+-- | @Solo@ is the canonical lifted 1-tuple, just like '(,)' is the canonical
+-- lifted 2-tuple (pair) and '(,,)' 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.
+data Solo a = Solo { getSolo :: a }
data (a,b) = (a,b)
data (a,b,c) = (a,b,c)