lets-lens/src/Lets/Lens.hs

780 lines
16 KiB
Haskell
Raw Normal View History

2015-05-21 03:44:22 +02:00
{-# LANGUAGE RankNTypes #-}
2015-04-22 05:14:38 +02:00
module Lets.Lens (
2015-05-21 03:44:22 +02:00
fmapT
, over
, fmapTAgain
, Set
, sets
, mapped
, set
, foldMapT
, foldMapOf
, foldMapTAgain
, Fold
, folds
, folded
, Get
, get
, Traversal
, both
, traverseLeft
, traverseRight
, Traversal'
, Lens
, Prism
, _Left
, _Right
, prism
, _Just
, _Nothing
, setP
, getP
, Prism'
, modify
, (%~)
, (.~)
, fmodify
, (|=)
, fstL
, sndL
, mapL
, setL
, compose
, (|.)
, identity
, product
, (***)
, choice
, (|||)
, Lens'
, cityL
, stateL
, countryL
, streetL
, suburbL
, localityL
, ageL
, nameL
, addressL
, intAndIntL
, intAndL
, getSuburb
, setStreet
, getAgeAndCountry
, setCityAndLocality
, getSuburbOrCity
, setStreetOrState
, modifyCityUppercase
, modifyIntAndLengthEven
, traverseLocality
, intOrIntP
, intOrP
, intOrLengthEven
2015-04-22 05:14:38 +02:00
) where
2015-05-21 03:44:22 +02:00
import Control.Applicative(Applicative((<*>), pure))
import Data.Char(toUpper)
import Data.Foldable(Foldable(foldMap))
import Data.Functor((<$>))
import Data.Map(Map)
import qualified Data.Map as Map(insert, delete, lookup, alterF)
2015-05-21 03:44:22 +02:00
import Data.Monoid(Monoid)
import qualified Data.Set as Set(Set, insert, delete, member)
import Data.Traversable(Traversable(traverse))
import Lets.Data(AlongsideLeft(AlongsideLeft, getAlongsideLeft), AlongsideRight(AlongsideRight, getAlongsideRight), Identity(Identity, getIdentity), Const(Const, getConst), Tagged(Tagged, getTagged), IntOr(IntOrIs, IntOrIsNot), IntAnd(IntAnd), Person(Person), Locality(Locality), Address(Address))
2015-05-21 03:44:22 +02:00
import Lets.Choice(Choice(left, right))
import Lets.Profunctor(Profunctor(dimap))
import Prelude hiding (product)
-- $setup
-- >>> import qualified Data.Map as Map(fromList)
-- >>> import qualified Data.Set as Set(fromList)
2019-02-01 06:46:27 +01:00
-- >>> import Data.Bool(bool)
2015-05-21 03:44:22 +02:00
-- >>> import Data.Char(ord)
2015-05-21 03:52:57 +02:00
-- >>> import Lets.Data
2015-05-21 03:44:22 +02:00
-- Let's remind ourselves of Traversable, noting Foldable and Functor.
--
-- class (Foldable t, Functor t) => Traversable t where
-- traverse ::
-- Applicative f =>
2015-05-21 03:44:22 +02:00
-- (a -> f b)
-- -> t a
-- -> f (t b)
-- | Observe that @fmap@ can be recovered from @traverse@ using @Identity@.
--
-- /Reminder:/ fmap :: Functor t => (a -> b) -> t a -> t b
fmapT ::
Traversable t =>
(a -> b)
-> t a
-> t b
fmapT f = getIdentity . traverse (Identity . f)
2015-05-21 03:44:22 +02:00
-- | Let's refactor out the call to @traverse@ as an argument to @fmapT@.
over ::
2015-05-21 03:44:22 +02:00
((a -> Identity b) -> s -> Identity t)
-> (a -> b)
-> s
-> t
over t f = getIdentity . t (Identity . f)
2015-05-21 03:44:22 +02:00
-- | Here is @fmapT@ again, passing @traverse@ to @over@.
fmapTAgain ::
Traversable t =>
(a -> b)
-> t a
-> t b
fmapTAgain = over traverse
2015-05-21 03:44:22 +02:00
-- | Let's create a type-alias for this type of function.
type Set s t a b =
(a -> Identity b)
-> s
-> Identity t
-- | Let's write an inverse to @over@ that does the @Identity@ wrapping &
-- unwrapping.
sets ::
((a -> b) -> s -> t)
-> Set s t a b
sets f g = Identity . f (getIdentity . g)
2015-05-21 03:44:22 +02:00
mapped ::
Functor f =>
Set (f a) (f b) a b
mapped f g = Identity (getIdentity . f <$> g)
2015-05-21 03:44:22 +02:00
set ::
Set s t a b
-> s
-> b
-> t
set f s b = over f (const b) s
2015-05-21 03:44:22 +02:00
----
2015-06-09 05:30:02 +02:00
-- | Observe that @foldMap@ can be recovered from @traverse@ using @Const@.
2015-05-21 03:44:22 +02:00
--
-- /Reminder:/ foldMap :: (Foldable t, Monoid b) => (a -> b) -> t a -> b
foldMapT ::
(Traversable t, Monoid b) =>
(a -> b)
-> t a
-> b
foldMapT f = getConst . traverse (Const . f)
2015-05-21 03:44:22 +02:00
-- | Let's refactor out the call to @traverse@ as an argument to @foldMapT@.
foldMapOf ::
((a -> Const r b) -> s -> Const r t)
-> (a -> r)
-> s
-> r
foldMapOf t f = getConst . t (Const . f)
2015-05-21 03:44:22 +02:00
-- | Here is @foldMapT@ again, passing @traverse@ to @foldMapOf@.
foldMapTAgain ::
(Traversable t, Monoid b) =>
(a -> b)
-> t a
-> b
foldMapTAgain = foldMapOf traverse
2015-05-21 03:44:22 +02:00
-- | Let's create a type-alias for this type of function.
type Fold s t a b =
forall r.
Monoid r =>
(a -> Const r b)
-> s
-> Const r t
-- | Let's write an inverse to @foldMapOf@ that does the @Const@ wrapping &
-- unwrapping.
folds ::
((a -> b) -> s -> t)
-> (a -> Const b a)
-> s
-> Const t s
folds f g s = let t = (f (getConst . g) s) in Const t
2015-05-21 03:44:22 +02:00
folded ::
Foldable f =>
Fold (f a) (f a) a a
folded = folds foldMap
2015-05-21 03:44:22 +02:00
----
-- | @Get@ is like @Fold@, but without the @Monoid@ constraint.
type Get r s a =
(a -> Const r a)
-> s
-> Const r s
get ::
Get a s a
-> s
-> a
get f = getConst . f Const
2015-05-21 03:44:22 +02:00
----
-- | Let's generalise @Identity@ and @Const r@ to any @Applicative@ instance.
type Traversal s t a b =
forall f.
Applicative f =>
(a -> f b)
-> s
-> f t
-- | Traverse both sides of a pair.
both ::
Traversal (a, a) (b, b) a b
-- both :: (a -> f b) -> (a , a) -> f (b , b)
both f (a, b) = (,) <$> f a <*> f b
2015-05-21 03:44:22 +02:00
-- | Traverse the left side of @Either@.
traverseLeft ::
Traversal (Either a x) (Either b x) a b
traverseLeft f (Left a) = Left <$> f a
traverseLeft _ (Right x) = pure $ Right x
2015-05-21 03:44:22 +02:00
-- | Traverse the right side of @Either@.
traverseRight ::
Traversal (Either x a) (Either x b) a b
traverseRight _ (Left x) = pure $ Left x
traverseRight f (Right b) = Right <$> f b
2015-05-21 03:44:22 +02:00
type Traversal' a b =
Traversal a a b b
----
-- | @Const r@ is @Applicative@, if @Monoid r@, however, without the @Monoid@
-- constraint (as in @Get@), the only shared abstraction between @Identity@ and
-- @Const r@ is @Functor@.
--
-- Consequently, we arrive at our lens derivation:
type Lens s t a b =
forall f.
Functor f =>
(a -> f b)
-> s
-> f t
----
-- | A prism is a less specific type of traversal.
type Prism s t a b =
forall p f.
(Choice p, Applicative f) =>
p a (f b)
-> p s (f t)
_Left ::
Prism (Either a x) (Either b x) a b
-- _Left h = let rh = right h in dimap (either Right (Left . Right)) (either pure (fmap Left)) rh
_Left h = let lh = left h in dimap (either Left (Right . Right)) (either (fmap Left) pure) lh
2015-05-21 03:44:22 +02:00
_Right ::
Prism (Either x a) (Either x b) a b
_Right h = let rh = right h in dimap (either (Left . Left) Right) (either pure (fmap Right)) rh
2015-05-21 03:44:22 +02:00
prism ::
(b -> t)
-> (s -> Either t a)
-> Prism s t a b
prism bToT sToEta pafb = let rh = right pafb in dimap sToEta (either pure (fmap bToT)) rh
2015-05-21 03:44:22 +02:00
_Just ::
Prism (Maybe a) (Maybe b) a b
_Just = prism Just (maybe (Left Nothing) Right)
2015-05-21 03:44:22 +02:00
_Nothing ::
Prism (Maybe a) (Maybe a) () ()
_Nothing = prism (\_ -> Nothing) (maybe (Right ()) (Left . Just))
2015-05-21 03:44:22 +02:00
setP ::
Prism s t a b
-> s
-> Either t a
setP p = let pl = p Left in either Right Left . pl
2015-05-21 03:44:22 +02:00
getP ::
Prism s t a b
-> b
-> t
-- Copied :(
getP p = let pti = p . Tagged . Identity in getIdentity . getTagged . pti
2015-05-21 03:44:22 +02:00
type Prism' a b =
Prism a a b b
----
-- |
--
-- >>> modify fstL (+1) (0 :: Int, "abc")
-- (1,"abc")
--
-- >>> modify sndL (+1) ("abc", 0 :: Int)
-- ("abc",1)
--
-- prop> let types = (x :: Int, y :: String) in modify fstL id (x, y) == (x, y)
--
-- prop> let types = (x :: Int, y :: String) in modify sndL id (x, y) == (x, y)
modify ::
Lens s t a b
-> (a -> b)
-> s
-> t
modify l f = getIdentity . l (Identity . f)
2015-05-21 03:44:22 +02:00
-- | An alias for @modify@.
(%~) ::
Lens s t a b
-> (a -> b)
-> s
-> t
(%~) =
modify
infixr 4 %~
-- |
--
-- >>> fstL .~ 1 $ (0 :: Int, "abc")
-- (1,"abc")
--
-- >>> sndL .~ 1 $ ("abc", 0 :: Int)
-- ("abc",1)
--
-- prop> let types = (x :: Int, y :: String) in set fstL (x, y) z == (fstL .~ z $ (x, y))
--
-- prop> let types = (x :: Int, y :: String) in set sndL (x, y) z == (sndL .~ z $ (x, y))
(.~) ::
Lens s t a b
-> b
-> s
-> t
(.~) l = let ml = modify l in ml . const
2015-05-21 03:44:22 +02:00
infixl 5 .~
-- |
--
-- >>> fmodify fstL (+) (5 :: Int, "abc") 8
-- (13,"abc")
--
-- >>> fmodify fstL (\n -> bool Nothing (Just (n * 2)) (even n)) (10, "abc")
-- Just (20,"abc")
--
-- >>> fmodify fstL (\n -> bool Nothing (Just (n * 2)) (even n)) (11, "abc")
-- Nothing
fmodify ::
Functor f =>
Lens s t a b
-> (a -> f b)
-> s
-> f t
fmodify l = l
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> fstL |= Just 3 $ (7, "abc")
-- Just (3,"abc")
--
-- >>> (fstL |= (+1) $ (3, "abc")) 17
-- (18,"abc")
(|=) ::
Functor f =>
Lens s t a b
-> f b
-> s
-> f t
(|=) l = let fl = fmodify l in fl . const
2015-05-21 03:44:22 +02:00
infixl 5 |=
-- |
--
-- >>> modify fstL (*10) (3, "abc")
-- (30,"abc")
fstL ::
Lens (a, x) (b, x) a b
fstL f (x, y) = let fx = f x in fmap (\x' -> (x', y)) fx
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> modify sndL (++ "def") (13, "abc")
-- (13,"abcdef")
sndL ::
Lens (x, a) (x, b) a b
sndL f (x, y) = let fy = f y in fmap (\y' -> (x, y')) fy
2015-05-21 03:44:22 +02:00
-- |
--
-- To work on `Map k a`:
-- Map.lookup :: Ord k => k -> Map k a -> Maybe a
-- Map.insert :: Ord k => k -> a -> Map k a -> Map k a
-- Map.delete :: Ord k => k -> Map k a -> Map k a
--
2015-05-21 03:44:22 +02:00
-- >>> get (mapL 3) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d']))
-- Just 'c'
--
-- >>> get (mapL 33) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d']))
-- Nothing
--
-- >>> set (mapL 3) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d'])) (Just 'X')
-- fromList [(1,'a'),(2,'b'),(3,'X'),(4,'d')]
--
-- >>> set (mapL 33) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d'])) (Just 'X')
-- fromList [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(33,'X')]
--
-- >>> set (mapL 3) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d'])) Nothing
-- fromList [(1,'a'),(2,'b'),(4,'d')]
--
-- >>> set (mapL 33) (Map.fromList (map (\c -> (ord c - 96, c)) ['a'..'d'])) Nothing
-- fromList [(1,'a'),(2,'b'),(3,'c'),(4,'d')]
mapL ::
Ord k =>
k
-> Lens (Map k v) (Map k v) (Maybe v) (Maybe v)
mapL =
error "todo: mapL"
-- |
--
-- To work on `Set a`:
-- Set.insert :: Ord a => a -> Set a -> Set a
-- Set.member :: Ord a => a -> Set a -> Bool
-- Set.delete :: Ord a => a -> Set a -> Set a
--
2015-05-21 03:44:22 +02:00
-- >>> get (setL 3) (Set.fromList [1..5])
-- True
--
-- >>> get (setL 33) (Set.fromList [1..5])
-- False
--
-- >>> set (setL 3) (Set.fromList [1..5]) True
-- fromList [1,2,3,4,5]
--
-- >>> set (setL 3) (Set.fromList [1..5]) False
-- fromList [1,2,4,5]
--
-- >>> set (setL 33) (Set.fromList [1..5]) True
-- fromList [1,2,3,4,5,33]
--
-- >>> set (setL 33) (Set.fromList [1..5]) False
-- fromList [1,2,3,4,5]
setL ::
Ord k =>
k
-> Lens (Set.Set k) (Set.Set k) Bool Bool
setL =
error "todo: setL"
-- |
--
-- >>> get (compose fstL sndL) ("abc", (7, "def"))
-- 7
--
-- >>> set (compose fstL sndL) ("abc", (7, "def")) 8
-- ("abc",(8,"def"))
compose ::
Lens s t a b
-> Lens q r s t
-> Lens q r a b
compose l1 l2 = l2 . l1
2015-05-21 03:44:22 +02:00
-- | An alias for @compose@.
(|.) ::
Lens s t a b
-> Lens q r s t
-> Lens q r a b
(|.) =
compose
infixr 9 |.
-- |
--
-- >>> get identity 3
-- 3
--
-- >>> set identity 3 4
-- 4
identity ::
Lens a b a b
identity = id
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> get (product fstL sndL) (("abc", 3), (4, "def"))
-- ("abc","def")
--
-- >>> set (product fstL sndL) (("abc", 3), (4, "def")) ("ghi", "jkl")
-- (("ghi",3),(4,"jkl"))
product ::
Lens s t a b
-> Lens q r c d
-> Lens (s, q) (t, r) (a, c) (b, d)
product _ _ =
2015-05-21 03:44:22 +02:00
error "todo: product"
-- | An alias for @product@.
(***) ::
Lens s t a b
-> Lens q r c d
-> Lens (s, q) (t, r) (a, c) (b, d)
(***) =
product
infixr 3 ***
-- |
--
-- >>> get (choice fstL sndL) (Left ("abc", 7))
-- "abc"
--
-- >>> get (choice fstL sndL) (Right ("abc", 7))
-- 7
--
-- >>> set (choice fstL sndL) (Left ("abc", 7)) "def"
-- Left ("def",7)
--
-- >>> set (choice fstL sndL) (Right ("abc", 7)) 8
-- Right ("abc",8)
choice ::
Lens s t a b
-> Lens q r a b
-> Lens (Either s q) (Either t r) a b
choice l1 l2 = (\f esq -> case esq of
Left s -> Left <$> (l1 f s)
Right q -> Right <$> (l2 f q))
2015-05-21 03:44:22 +02:00
-- | An alias for @choice@.
(|||) ::
Lens s t a b
-> Lens q r a b
-> Lens (Either s q) (Either t r) a b
(|||) =
choice
infixr 2 |||
----
type Lens' a b =
Lens a a b b
cityL ::
Lens' Locality String
cityL p (Locality c t y) =
fmap (\c' -> Locality c' t y) (p c)
stateL ::
Lens' Locality String
stateL p (Locality c t y) =
fmap (\t' -> Locality c t' y) (p t)
countryL ::
Lens' Locality String
countryL p (Locality c t y) =
fmap (\y' -> Locality c t y') (p y)
streetL ::
Lens' Address String
streetL p (Address t s l) =
fmap (\t' -> Address t' s l) (p t)
suburbL ::
Lens' Address String
suburbL p (Address t s l) =
fmap (\s' -> Address t s' l) (p s)
localityL ::
Lens' Address Locality
localityL p (Address t s l) =
fmap (\l' -> Address t s l') (p l)
ageL ::
Lens' Person Int
ageL p (Person a n d) =
fmap (\a' -> Person a' n d) (p a)
nameL ::
Lens' Person String
nameL p (Person a n d) =
fmap (\n' -> Person a n' d) (p n)
addressL ::
Lens' Person Address
addressL p (Person a n d) =
fmap (\d' -> Person a n d') (p d)
intAndIntL ::
Lens' (IntAnd a) Int
intAndIntL p (IntAnd n a) =
fmap (\n' -> IntAnd n' a) (p n)
-- lens for polymorphic update
intAndL ::
Lens (IntAnd a) (IntAnd b) a b
intAndL p (IntAnd n a) =
fmap (\a' -> IntAnd n a') (p a)
-- |
--
2015-07-28 13:32:38 +02:00
-- >>> getSuburb fred
2015-05-21 03:44:22 +02:00
-- "Fredville"
--
2015-07-28 13:32:38 +02:00
-- >>> getSuburb mary
2015-05-21 03:44:22 +02:00
-- "Maryland"
getSuburb ::
Person
-> String
getSuburb = get $ (compose suburbL addressL)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> setStreet fred "Some Other St"
-- Person 24 "Fred" (Address "Some Other St" "Fredville" (Locality "Fredmania" "New South Fred" "Fredalia"))
--
-- >>> setStreet mary "Some Other St"
-- Person 28 "Mary" (Address "Some Other St" "Maryland" (Locality "Mary Mary" "Western Mary" "Maristan"))
setStreet ::
Person
-> String
-> Person
setStreet = set $ (compose streetL addressL)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> getAgeAndCountry (fred, maryLocality)
-- (24,"Maristan")
--
-- >>> getAgeAndCountry (mary, fredLocality)
-- (28,"Fredalia")
getAgeAndCountry ::
(Person, Locality)
-> (Int, String)
getAgeAndCountry (person, locality) = (get ageL person,
get countryL locality)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> setCityAndLocality (fred, maryAddress) ("Some Other City", fredLocality)
-- (Person 24 "Fred" (Address "15 Fred St" "Fredville" (Locality "Some Other City" "New South Fred" "Fredalia")),Address "83 Mary Ln" "Maryland" (Locality "Fredmania" "New South Fred" "Fredalia"))
--
-- >>> setCityAndLocality (mary, fredAddress) ("Some Other City", maryLocality)
-- (Person 28 "Mary" (Address "83 Mary Ln" "Maryland" (Locality "Some Other City" "Western Mary" "Maristan")),Address "15 Fred St" "Fredville" (Locality "Mary Mary" "Western Mary" "Maristan"))
setCityAndLocality ::
(Person, Address) -> (String, Locality) -> (Person, Address)
setCityAndLocality (person, address) (city, locality) =
(set (cityL |. localityL |. addressL) person city,
set localityL address locality)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> getSuburbOrCity (Left maryAddress)
-- "Maryland"
--
-- >>> getSuburbOrCity (Right fredLocality)
-- "Fredmania"
getSuburbOrCity ::
Either Address Locality
-> String
getSuburbOrCity =
get (suburbL ||| cityL)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> setStreetOrState (Right maryLocality) "Some Other State"
-- Right (Locality "Mary Mary" "Some Other State" "Maristan")
--
-- >>> setStreetOrState (Left fred) "Some Other St"
-- Left (Person 24 "Fred" (Address "Some Other St" "Fredville" (Locality "Fredmania" "New South Fred" "Fredalia")))
setStreetOrState ::
Either Person Locality
-> String
-> Either Person Locality
setStreetOrState =
set (streetL |. addressL ||| stateL)
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> modifyCityUppercase fred
-- Person 24 "Fred" (Address "15 Fred St" "Fredville" (Locality "FREDMANIA" "New South Fred" "Fredalia"))
--
-- >>> modifyCityUppercase mary
-- Person 28 "Mary" (Address "83 Mary Ln" "Maryland" (Locality "MARY MARY" "Western Mary" "Maristan"))
modifyCityUppercase ::
Person
-> Person
modifyCityUppercase =
cityL |. localityL |. addressL %~ map toUpper
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> modify intAndL (even . length) (IntAnd 10 "abc")
2015-05-21 03:44:22 +02:00
-- IntAnd 10 False
--
-- >>> modify intAndL (even . length) (IntAnd 10 "abcd")
2015-05-21 03:44:22 +02:00
-- IntAnd 10 True
modifyIntAndLengthEven ::
IntAnd [a]
-> IntAnd Bool
modifyIntAndLengthEven =
intAndL %~ even . length
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> over traverseLocality (map toUpper) (Locality "abc" "def" "ghi")
-- Locality "ABC" "DEF" "GHI"
traverseLocality ::
Traversal' Locality String
traverseLocality f (Locality c t y) =
Locality <$> f c <*> f t <*> f y
2015-05-21 03:44:22 +02:00
-- |
--
-- >>> over intOrIntP (*10) (IntOrIs 3)
-- IntOrIs 30
--
-- >>> over intOrIntP (*10) (IntOrIsNot "abc")
-- IntOrIsNot "abc"
intOrIntP ::
Prism' (IntOr a) Int
intOrIntP =
prism
IntOrIs
(\i -> case i of
IntOrIs n -> Right n
IntOrIsNot a -> Left (IntOrIsNot a))
2015-05-21 03:44:22 +02:00
intOrP ::
Prism (IntOr a) (IntOr b) a b
intOrP =
prism
IntOrIsNot
(\i -> case i of
IntOrIs n -> Left (IntOrIs n)
IntOrIsNot a -> Right a)
2015-05-21 03:44:22 +02:00
-- |
--
-- >> over intOrP (even . length) (IntOrIsNot "abc")
2015-05-21 03:44:22 +02:00
-- IntOrIsNot False
--
-- >>> over intOrP (even . length) (IntOrIsNot "abcd")
2015-05-21 03:44:22 +02:00
-- IntOrIsNot True
--
-- >>> over intOrP (even . length) (IntOrIs 10)
2015-05-21 03:44:22 +02:00
-- IntOrIs 10
intOrLengthEven ::
IntOr [a]
-> IntOr Bool
intOrLengthEven = over intOrP (even . length)