{-# OPTIONS_GHC -Wno-redundant-constraints #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {- | Copyright: (c) 2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik The @prolens@ package is a Haskell library with a minimal and lightweight implementation of optics based on 'Profunctor's. __'Optic'__ is a high-level concept for values that provide composable access to different parts of structures. "Prolens" implements the following optics: * 'Lens' — composable getters and setters * 'Prism' — composable constructors and deconstructors * 'Traversal' — composable data structures visitors == Usage To use lenses or prisms in your project, you need to add @prolens@ package as the dependency in the @build-depends@ field of your @.cabal@ file. E.g.: @ build-depends: prolens ^>= 0.0.0.0 @ You should add the import of this module in the place of lenses usage: @ __import__ "Prolens" @ == Creating your own optics We show in each section of this module how to create values of each kind of optics. ⚠️ __The general crucial rule__ for achieving maximum performance: always add @\{\-\# INLINE ... \#\-\}@ pragmas to your optics. == Typeclasses table The below table shows required constraints for each 'Optic': +-------------+------------------------------+ | Optic | Constraints | +=============+==============================+ | 'Lens' | @'Strong' p@ | +-------------+------------------------------+ | 'Prism' | @'Choice' p@ | +-------------+------------------------------+ | 'Traversal' | @('Choice' p, 'Monoidal' p)@ | +-------------+------------------------------+ == Usage table: get, set, modify Here is a go-to table on how to use getter, setters and modifiers with different 'Optic's. +-------------+------------------+--------------+------------------+------------------+-----------------+-----------------+ | | get | get operator | set | set operator | modify | modify operator | +=============+==================+==============+==================+==================+=================+=================+ | 'Lens' | @'view' l x@ | @x '^.' l@ | @'set' l new x@ | @x & l '.~' new@ | @'over' l f x@ | @x & l '%~' f@ | +-------------+------------------+--------------+------------------+------------------+-----------------+-----------------+ | 'Prism' | @'preview' _L x@ | - | @'set' _L new x@ | - | @'over' _L f x@ | - | +-------------+------------------+--------------+------------------+------------------+-----------------+-----------------+ | 'Traversal' | @'view' l x@ | - | @'set' l new x@ | - | @'over' l f x@ | - | +-------------+------------------+--------------+------------------+------------------+-----------------+-----------------+ @since 0.0.0.0 -} module Prolens ( -- * Profunctor typeclass Profunctor (..) -- * Optics , Optic -- * Lenses -- $lenses -- ** Lenses types , Lens , Lens' -- ** Strong typeclass , Strong (..) -- ** Lenses functions , set , over , view , lens -- ** Lenses operators , (^.) , (.~) , (%~) -- ** Standard lenses , fstL , sndL -- * Prisms -- $prisms -- ** Prism types , Prism , Prism' -- ** Choice typeclass , Choice (..) -- ** Prism functions , prism , prism' , preview -- ** Standard Prisms , _Just , _Left , _Right -- * Traversals -- ** Traversal types , Traversal -- ** Monoidal typeclass , Monoidal (..) -- ** Traversal functions , traverseOf -- ** Standard traversals , eachPair , eachMaybe , eachList -- * Internal data types , Forget (..) , Fun (..) ) where import Control.Applicative (Const (..), liftA2) import Data.Coerce (coerce) import Data.Monoid (First (..)) -- $setup -- >>> import Data.Function ((&)) {- | The type @p@ is called 'Profunctor' and it means, that a value of type @p in out@ takes a value of type @in@ as an argument (input) and outputs a value of type @out@. 'Profunctor' allows mapping of inputs and outputs. @ +----> Result input | | +--> Original profunctor | +-> Original input | + + + dimap :: (in2 -> in1) -> (out1 -> out2) -> p in1 out1 -> p in2 out2 + + | +-> Result output | +-> Original output @ Speaking in terms of other abstractions, 'Profunctor' is 'Data.Functor.Contravariant.Contravariant' in the first type argument (type variable @in@) and 'Functor' in the second type argument (type variable @out@). Moreover, @p in@ must have 'Functor' instance first to implement the 'Profunctor' instance. This required using @QuantifiedConstraints@. @ Contravariant <---+ | +-+-+ + + (forall a . Functor (p a)) => Profunctor p a b + + + | | | +--> Quantified constraint +++ | Functor <--+ @ Instances of 'Profunctor' should satisfy the following laws: * __Identity:__ @'dimap' 'id' 'id' ≡ 'id'@ * __Composition:__ @'dimap' (inAB . inBC) (outYZ . outXY) ≡ 'dimap' outBC outYZ . 'dimap' outAB outXY@ @since 0.0.0.0 -} -- type Profunctor :: (Type -> Type -> Type) -> Constraint class (forall a . Functor (p a)) => Profunctor p where dimap :: (in2 -> in1) -- ^ Map input -> (out1 -> out2) -- ^ Map output -> p in1 out1 -- ^ Take @in1@ as input and return @out1@ -> p in2 out2 -- ^ Take @in2@ as input and return @out2@ -- | @since 0.0.0.0 instance Profunctor (->) where dimap :: (in2 -> in1) -> (out1 -> out2) -> (in1 -> out1) -> (in2 -> out2) dimap in21 out12 f = out12 . f . in21 {-# INLINE dimap #-} {- | @'Fun' m a b@ is a wrapper for function @a -> m b@. @since 0.0.0.0 -} newtype Fun m a b = Fun { unFun :: a -> m b } -- | @since 0.0.0.0 instance Functor m => Functor (Fun m x) where fmap :: (a -> b) -> Fun m x a -> Fun m x b fmap f (Fun xma) = Fun (fmap f . xma) {-# INLINE fmap #-} -- | @since 0.0.0.0 instance Functor m => Profunctor (Fun m) where dimap :: (in2 -> in1) -> (out1 -> out2) -> Fun m in1 out1 -> Fun m in2 out2 dimap in21 out12 (Fun f) = Fun (fmap out12 . f . in21) {-# INLINE dimap #-} {- | 'Strong' is a 'Profunctor' that can be lifted to take a pair as an input and return a pair. The second element of a pair (variable of type @c@) can be of any type, and you can decide what type it should be. This is convenient for implementing various functions. E.g. 'lens' uses this fact. @since 0.0.0.0 -} class Profunctor p => Strong p where first :: p a b -> p (a, c) (b, c) second :: p a b -> p (c, a) (c, b) -- | @since 0.0.0.0 instance Strong (->) where first :: (a -> b) -> (a, c) -> (b, c) first ab (a, c) = (ab a, c) {-# INLINE first #-} second :: (a -> b) -> (c, a) -> (c, b) second ab (c, a) = (c, ab a) {-# INLINE second #-} -- | @since 0.0.0.0 instance (Functor m) => Strong (Fun m) where first :: Fun m a b -> Fun m (a, c) (b, c) first (Fun amb) = Fun (\(a, c) -> fmap (, c) (amb a)) {-# INLINE first #-} second :: Fun m a b -> Fun m (c, a) (c, b) second (Fun amb) = Fun (\(c, a) -> fmap (c,) (amb a)) {-# INLINE second #-} {- | 'Choice' is a 'Profunctor' that can be lifted to work with 'Either' given input or some other value. The other element of 'Either' (variable of type @c@) can be of any type, and you can decide what type it should be. This is convenient for implementing various functions. E.g. 'prism' uses this fact. @since 0.0.0.0 -} class Profunctor p => Choice p where left :: p a b -> p (Either a c) (Either b c) right :: p a b -> p (Either c a) (Either c b) -- | @since 0.0.0.0 instance Choice (->) where left :: (a -> b) -> Either a c -> Either b c left ab = \case Left a -> Left $ ab a Right c -> Right c {-# INLINE left #-} right :: (a -> b) -> Either c a -> Either c b right ab = \case Right a -> Right $ ab a Left c -> Left c {-# INLINE right #-} -- | @since 0.0.0.0 instance (Applicative m) => Choice (Fun m) where left :: Fun m a b -> Fun m (Either a c) (Either b c) left (Fun amb)= Fun $ \eitherAc -> case eitherAc of Left a -> Left <$> amb a Right c -> pure $ Right c {-# INLINE left #-} right :: Fun m a b -> Fun m (Either c a) (Either c b) right (Fun amb)= Fun $ \eitherCa -> case eitherCa of Right a -> Right <$> amb a Left c -> pure $ Left c {-# INLINE right #-} {- | 'Monoidal' is 'Strong' 'Profunctor' that can be appended. It is similar to 'Monoid's but for higher-kinded types. @since 0.0.0.0 -} class Strong p => Monoidal p where pappend :: p a b -> p c d -> p (a, c) (b, d) pempty :: p a a -- | @since 0.0.0.0 instance Monoidal (->) where pappend :: (a -> b) -> (c -> d) -> (a, c) -> (b, d) pappend ab cd (a, c) = (ab a, cd c) {-# INLINE pappend #-} pempty :: a -> a pempty = id {-# INLINE pempty #-} -- | @since 0.0.0.0 instance (Applicative m) => Monoidal (Fun m) where pappend :: Fun m a b -> Fun m c d -> Fun m (a, c) (b, d) pappend (Fun amb) (Fun cmd) = Fun (\(a, c) -> liftA2 (,) (amb a) (cmd c)) {-# INLINE pappend #-} pempty :: Fun m a a pempty = Fun (pure . id) {-# INLINE pempty #-} {- | 'Optic' takes a connection from @a@ to @b@ (represented as a value of type @p a b@) and returns a connection from @source@ to @target@ (represented as a value of type @p source target@). @ +---> Profunctor | | +----> Final input | | | | +-> Final output | | | + + + type Optic p source target a b + + | | Given input <--+ | | Given output <-------+ @ @since 0.0.0.0 -} type Optic p source target a b = p a b -> p source target {- $lenses == Example To understand better how to use this library lets look at some simple example. Let's say we have the user and address data types in our system: >>> :{ data Address = Address { addressCountry :: String , addressCity :: String , addressIndex :: String } deriving (Show) :} >>> :{ data User = User { userName :: String , userAge :: Int , userAddress :: Address } deriving (Show) :} We can easily get fields of the @User@ and @Address@ types, but setting values is inconvenient (especially for nested records). To solve this problem, we can use lenses — composable getters and setters. 'Lens' is a value, so we need to define lenses for fields of our data types first. To create the lens for the @userName@ field we can use 'lens' function and manually writing getter and setter function: >>> :{ nameL :: Lens' User String nameL = lens getter setter where getter :: User -> String getter = userName setter :: User -> String -> User setter user newName = user {userName = newName} :} In this manner, we can create other lenses for our @User@ data type. Usually, lenses are one-liners, and we can define them easily using lambda-functions. >>> :{ ageL :: Lens' User Int ageL = lens userAge (\u new -> u {userAge = new}) :} >>> :{ addressL :: Lens' User Address addressL = lens userAddress (\u new -> u {userAddress = new}) :} We want to have lenses for accessing @Adress@ fields inside @User@, so we want to have the following values: @ countryL :: 'Lens'' User 'String' cityL :: 'Lens'' User 'String' indexL :: 'Lens'' User 'String' @ /Note:/ for lenses as @countryL@, @cityL@ etc., we are using composition of the lenses for the @userAddress@ field. If we have >>> :{ addressCityL :: Lens' Address String addressCityL = lens addressCity (\a new -> a {addressCity = new}) :} then >>> cityL = addressL . addressCityL Let's say we have some sample user >>> :{ address = Address { addressCountry = "UK" , addressCity = "London" , addressIndex = "XXX" } user :: User user = User { userName = "John" , userAge = 42 , userAddress = address } :} To view the fields of the User data type we can use 'view' or '^.' >>> view ageL user 42 >>> user ^. cityL "London" If we want to change any of the user's data, we should use 'set' or '.~' >>> set nameL "Johnny" user User {userName = "Johnny", userAge = 42, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}} >>> user & cityL .~ "Bristol" User {userName = "John", userAge = 42, userAddress = Address {addressCountry = "UK", addressCity = "Bristol", addressIndex = "XXX"}} 'over' or '%~' operator could be useful when, for example, you want to increase the age by one on the user's birthday: >>> over ageL succ user User {userName = "John", userAge = 43, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}} >>> user & ageL %~ succ User {userName = "John", userAge = 43, userAddress = Address {addressCountry = "UK", addressCity = "London", addressIndex = "XXX"}} -} {- | 'Lens' represents composable getters and setters. 'Lens' is an @'Optic' p@ with the 'Strong' constraint on the @p@ type variable. @ +---> Current object | | +-> Final object | | + + type Lens source target a b + + | | Current field <--+ | | Final field <-------+ @ @since 0.0.0.0 -} type Lens source target a b = forall p . Strong p => Optic p source target a b {- | The monomorphic lenses which don't change the type of the container (or of the value inside). It has a 'Strong' constraint, and it can be used whenever a getter or a setter is needed. * @a@ is the type of the value inside of structure * @source@ is the type of the whole structure For most use-cases it's enought to use this 'Lens'' instead of more general 'Lens'. @since 0.0.0.0 -} type Lens' source a = Lens source source a a {- | Sets the given value to the structure using a setter. @since 0.0.0.0 -} set :: (p ~ (->)) => Optic p source target a b -- ^ 'Optic' that can be lens -> b -- ^ Value to set -> source -- ^ Object where we want to set value -> target -- ^ Resulting object with @b@ set set abst = abst . const {-# INLINE set #-} {- | Applies the given function to the target. @since 0.0.0.0 -} over :: (p ~ (->)) => Optic p source target a b -- ^ 'Optic' that can be lens -> (a -> b) -- ^ Field modification function -> source -- ^ Object where we want to set value -> target -- ^ Resulting object with the modified field over = id {-# INLINE over #-} {- | Gets a value out of a structure using a getter. @since 0.0.0.0 -} view :: (p ~ Fun (Const a)) => Optic p source target a b -- ^ 'Optic' that can be lens -> source -- ^ Object from which we want to get value -> a -- ^ Field of @source@ view opt = coerce (opt (Fun Const)) {-# INLINE view #-} -- view opt = getConst . unFun (opt (Fun Const)) -- opt :: Fun (Const a) a b -> Fun (Const a) s t -- opt :: (a -> Const a b) -> ( s -> Const a t) {- | Creates 'Lens' from the getter and setter. @since 0.0.0.0 -} lens :: (source -> a) -- ^ Getter -> (source -> b -> target) -- ^ Setter -> Lens source target a b lens getter setter = dimap (\s -> (s, getter s)) (uncurry setter) . second {-# INLINE lens #-} {- | The operator form of 'view' with the arguments flipped. @since 0.0.0.0 -} infixl 8 ^. (^.) :: source -> Lens' source a -> a s ^. l = view l s {-# INLINE (^.) #-} {- | The operator form of 'set'. @since 0.0.0.0 -} infixr 4 .~ (.~) :: Lens' source a -> a -> source -> source (.~) = set {-# INLINE (.~) #-} {- | The operator form of 'over'. @since 0.0.0.0 -} infixr 4 %~ (%~) :: Lens' source a -> (a -> a) -> source -> source (%~) = over {-# INLINE (%~) #-} {- | 'Lens'' for a tuple on the first argument. >>> view fstL (42, "str") 42 @since 0.0.0.0 -} fstL :: Lens (a, c) (b, c) a b fstL = lens fst $ \(_, b) new -> (new, b) {-# INLINE fstL #-} {- | 'Lens'' for a tuple on the second argument. >>> view sndL (42, "Hello") "Hello" @since 0.0.0.0 -} sndL :: Lens (x, a) (x, b) a b sndL = lens snd $ \(a, _) new -> (a, new) {-# INLINE sndL #-} {- $prisms Prisms work with sum types. == Example Let's say we have the user data type in our system: >>> :{ data Address = Address { addressCountry :: String , addressCity :: String } deriving (Show) :} >>> :{ data Payload = NamePayload String | IdPayload Int | AddressPayload Address deriving (Show) :} To create the prism for each constructor we can use 'prism'' function and manually writing getter and setter function: /NOTE:/ The naming convention for prisms is the following: @ _ConstructorName @ >>> :{ _NamePayload :: Prism' Payload String _NamePayload = prism' construct match where match :: Payload -> Maybe String match p = case p of NamePayload name -> Just name _otherPayload -> Nothing construct :: String -> Payload construct = NamePayload :} In this manner, we can create other prisms for our @Payload@ data type. >>> :{ _IdPayload :: Prism' Payload Int _IdPayload = prism' IdPayload $ \p -> case p of IdPayload i -> Just i _otherPayload -> Nothing :} >>> :{ _AddressPayload :: Prism' Payload Address _AddressPayload = prism' AddressPayload $ \p -> case p of AddressPayload a -> Just a _otherPayload -> Nothing :} Let's say we have some sample payload >>> :{ payloadName :: Payload payloadName = NamePayload "Some name" :} To view the fields of the @Payload@ data type we can use 'preview' >>> preview _NamePayload payloadName Just "Some name" >>> preview _IdPayload payloadName Nothing If we want to change any of the data, we should use 'set' or '.~' (just like in lenses) >>> set _NamePayload "Johnny" payloadName NamePayload "Johnny" >>> set _IdPayload 3 payloadName NamePayload "Some name" Note, that you can easily compose lenses and prisms together: >>> :{ address = Address { addressCountry = "UK" , addressCity = "London" } :} >>> :{ addressCityL :: Lens' Address String addressCityL = lens addressCity (\a new -> a {addressCity = new}) :} >>> :{ payloadAddress :: Payload payloadAddress = AddressPayload address :} >>> set _AddressPayload (address & addressCityL .~ "Bristol") payloadAddress AddressPayload (Address {addressCountry = "UK", addressCity = "Bristol"}) -} {- | 'Prism' represents composable constructors and deconstructors. 'Prism' is an @'Optic' p@ with 'Choice' constraint on the @p@ type variable. @ +---> Current object | | +-> Final object | | + + type Prism source target a b + + | | Field in current constructor <--+ | | Field in final constructor <-------+ @ @since 0.0.0.0 -} type Prism source target a b = forall p . Choice p => Optic p source target a b {- | The monomorphic prisms which don't change the type of the container (or of the value inside). * @a@ is the value inside the particular constructor * @source@ is some sum type @since 0.0.0.0 -} type Prism' source a = Prism source source a a {- | Newtype around function @a -> r@. It's called /forget/ because it forgets about its last type variable. @since 0.0.0.0 -} newtype Forget r a b = Forget { unForget :: a -> r } -- | @since 0.0.0.0 instance Functor (Forget r x) where fmap :: (a -> b) -> Forget r x a -> Forget r x b fmap _ = coerce -- | @since 0.0.0.0 instance Profunctor (Forget r) where dimap :: (a -> b) -> (c -> d) -> Forget r b c -> Forget r a d dimap ab _cd (Forget br) = Forget (br . ab) {-# INLINE dimap #-} -- | @since 0.0.0.0 instance Strong (Forget r) where first :: Forget r a b -> Forget r (a, c) (b, c) first (Forget ar) = Forget (ar . fst) {-# INLINE first #-} second :: Forget r a b -> Forget r (c, a) (c, b) second (Forget ar) = Forget (ar . snd) {-# INLINE second #-} -- | @since 0.0.0.0 instance Monoid r => Choice (Forget r) where left :: Forget r a b -> Forget r (Either a c) (Either b c) left (Forget ar) = Forget (either ar (const mempty)) {-# INLINE left #-} right :: Forget r a b -> Forget r (Either c a) (Either c b) right (Forget ar) = Forget (either (const mempty) ar) {-# INLINE right #-} -- | @since 0.0.0.0 instance (Monoid r) => Monoidal (Forget r) where pappend :: Forget r a b -> Forget r c d -> Forget r (a, c) (b, d) pappend (Forget ar) (Forget cr) = Forget (\(a, c) -> ar a <> cr c) {-# INLINE pappend #-} pempty :: Forget r a a pempty = Forget (const mempty) {-# INLINE pempty #-} {- | Match a value from @source@ type. @since 0.0.0.0 -} preview :: forall a source p . (p ~ Forget (First a)) => Optic p source source a a -- ^ 'Optic' that can be prism -> source -- ^ Object (possible sum type) -> Maybe a -- ^ Value of type @a@ from a specific constructor preview paapss = coerce (paapss wrap) where wrap :: Forget (First a) a a wrap = coerce @(a -> Maybe a) @(Forget (First a) a a) Just {-# INLINE wrap #-} {-# INLINE preview #-} -- preview paapss = getFirst . unForget (paapss (Forget (First . Just))) -- paapss :: Forget (First a) a a -> Forget (First a) source source -- paapss :: (a -> First a) -> source -> First a -- paapss :: (a -> Maybe a) -> source -> Maybe a {- | Create 'Prism' from constructor and matching function. @since 0.0.0.0 -} prism :: (b -> target) -- ^ Constructor -> (source -> Either target a) -- ^ Matching function -> Prism source target a b -- prism :: (b -> target) -> (source -> Either target a) -> p a b -> p source target prism ctor match = dimap match (either id ctor) . right {-# INLINE prism #-} {- | Create monomorphic 'Prism'' from constructor and matching function. @since 0.0.0.0 -} prism' :: (a -> source) -- ^ Constructor -> (source -> Maybe a) -- ^ Matching function -> Prism' source a prism' ctor match = prism ctor (\s -> maybe (Left s) Right $ match s) {-# INLINE prism' #-} {- | 'Prism' for a 'Just' of 'Maybe'. >>> preview _Just (Just 42) Just 42 >>> preview _Just Nothing Nothing @since 0.0.0.0 -} _Just :: Prism (Maybe a) (Maybe b) a b _Just = prism Just $ \case Just a -> Right a Nothing -> Left Nothing {-# INLINE _Just #-} {- | 'Prism' for a 'Left' of 'Either'. >>> preview _Left (Left 42) Just 42 >>> preview _Left (Right "Hello") Nothing @since 0.0.0.0 -} _Left :: Prism (Either a x) (Either b x) a b _Left = prism Left $ \case Left l -> Right l Right r -> Left $ Right r {-# INLINE _Left #-} {- | 'Prism' for a 'Left' of 'Either'. >>> preview _Right (Left 42) Nothing >>> preview _Right (Right "Hello") Just "Hello" @since 0.0.0.0 -} _Right :: Prism (Either x a) (Either x b) a b _Right = prism Right $ \case Right a -> Right a Left x -> Left $ Left x {-# INLINE _Right #-} {- | 'Traversal' provides composable ways to visit different parts of a data structure. 'Traversal' is an @'Optic' p@ with the 'Choice' and 'Monoidal' constraints on the @p@ type variable. @ +---> Current collection | | +-> Final collection | | + + type Traversal source target a b + + | | Current element <--+ | | Final element <-------+ @ @since 0.0.0.0 -} type Traversal source target a b = forall p . (Choice p, Monoidal p) => Optic p source target a b {- | Traverse a data structure using given 'Traversal'. >>> traverseOf eachPair putStrLn ("Hello", "World!") Hello World! ((),()) @since 0.0.0.0 -} traverseOf :: (Applicative f, p ~ Fun f) => Optic p source target a b -- ^ 'Optic' that can be a traversal -> (a -> f b) -- ^ Traversing function -> source -- ^ Data structure to traverse -> f target -- ^ Traversing result traverseOf pabPst aFb = unFun (pabPst (Fun aFb)) -- pabPst :: Fun f a b -> Fun f source target -- pabPst :: (a -> f b) -> Fun f source target {- | 'Traversal' for a pair of same type elements. >>> over eachPair (+ 1) (3, 7) (4,8) @since 0.0.0.0 -} eachPair :: Traversal (a, a) (b, b) a b eachPair pab = pappend pab pab {- | 'Traversal' for a 'Maybe'. >>> over eachMaybe (+ 1) (Just 3) Just 4 >>> over eachMaybe (+ 1) Nothing Nothing @since 0.0.0.0 -} eachMaybe :: Traversal (Maybe a) (Maybe b) a b eachMaybe pab = dimap maybeToEither eitherToMaybe (left pab) where maybeToEither :: Maybe a -> Either a () maybeToEither = \case Just a -> Left a Nothing -> Right () eitherToMaybe :: Either a () -> Maybe a eitherToMaybe = \case Left a -> Just a Right () -> Nothing {- | 'Traversal' for lists. >>> over eachList (+ 1) [1..5] [2,3,4,5,6] >>> over eachList (+ 1) [] [] @since 0.0.0.0 -} eachList :: Traversal [a] [b] a b eachList pab = dimap listToEither eitherToList $ left $ pappend pab (eachList pab) where listToEither :: [a] -> Either (a, [a]) () listToEither = \case [] -> Right () x:xs -> Left (x, xs) eitherToList :: Either (a, [a]) () -> [a] eitherToList = \case Right () -> [] Left (x, xs) -> x:xs