patrickt / fastsum

A fast open-union type, suitable for 100+ contained alternatives.
Other
97 stars 8 forks source link

Some lensy bits for your review #24

Open jwiegley opened 2 years ago

jwiegley commented 2 years ago

I've found these useful as a UI for fastsum in my lens-oriented code:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}

module Journal.SumLens where

import Control.Lens
import Data.Constraint
import Data.Sum

projected :: e :< r => Prism' (Sum r v) (e v)
projected = prism' inject project

projectedC :: forall r v e. Const e :< r => Prism' (Sum r v) e
projectedC = prism' (inject . Const) (fmap getConst . project)

weakened :: Prism' (Sum (e ': r) v) (Sum r v)
weakened = prism' weaken $ \s -> case decompose s of
  Left es -> Just es
  Right _ -> Nothing

_shead :: Prism' (Sum (e ': r) v) (e v)
_shead = projected

_stail :: Prism' (Sum (e ': r) v) (Sum r v)
_stail = weakened

underneath :: e :< r => Prism' (Sum (s ': r) v) (e v)
underneath = weakened . projected

underneathC :: Const e :< r => Prism' (Sum (s ': r) v) e
underneathC = weakened . projectedC

decomposed :: Iso' (Sum (e ': r) v) (Either (Sum r v) (e v))
decomposed = iso decompose (either weaken inject)

-- | @applied@ is the optic version of apply, to make it easy to compose
--   applications with other optics:
--   @@
--   s ^. applied @Printable printItem
--     === apply @Printable printItem s
--   @@
applied ::
  forall c r v a.
  Apply c r =>
  (forall f. c f => f v -> a) ->
  Fold (Sum r v) a
applied k f s = s <$ f (apply @c k s)

-- | @HasTraversal'@ serves the same role as Apply, but for traversals across
--   sums that support a given optic. For example, and with direct analogy to
--   'Apply':
--   @@
--   class HasLot f where
--     _Lot :: Traversal' (f v) Lot
--
--   instance HasTraversal' HasLot fs => HasLot (Sum fs) where
--     _Lot = traversing @HasLot _Lot
--   @@
class HasTraversal' (c :: (* -> *) -> Constraint) (fs :: [* -> *]) where
  traversing :: (forall g. c g => Traversal' (g a) b) -> Traversal' (Sum fs a) b

instance c t => HasTraversal' c '[t] where
  traversing k f s = fmap inject (k f (decomposeLast s))

instance
  {-# OVERLAPPING #-}
  (HasTraversal' c (u ': r), c t) =>
  HasTraversal' c (t ': u ': r)
  where
  traversing k f s = case decompose s of
    Right e -> inject <$> k f e
    Left es -> weaken <$> traversing @c k f es
jwiegley commented 2 years ago

This all seems to be working rather nicely, you can see it in action here: https://github.com/jwiegley/trade-journal

patrickt commented 2 years ago

I really like this, too. I think there is or was something in semantic that behaved similarly. Unfortunately, microlens doesn't provide a Prism definition, and I'd hate to hard-depend on the 800-lb gorilla that is lens. We could apply an optics interface, which has a more friendly dependency footprint (and that I prefer overall), but might be less useful, given its less-popular stature. Hmm.

patrickt commented 2 years ago

Maybe fastsum-lens is indicated?