packages feed

edit (empty) → 0.0.1.0

raw patch · 10 files changed

+1067/−0 lines, 10 filesdep +QuickCheckdep +basedep +comonadsetup-changed

Dependencies added: QuickCheck, base, comonad, containers, deepseq, doctest, edit, tasty, tasty-discover, tasty-quickcheck, transformers, uniplate

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for edit++## 0.1.0.0  -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Varun Gandhi++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Varun Gandhi nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ edit.cabal view
@@ -0,0 +1,78 @@+name:                edit+version:             0.0.1.0+synopsis:            A monad for rewriting things.+homepage:            https://github.com/theindigamer/edit+license:             BSD3+license-file:        LICENSE+author:              Varun Gandhi <theindigamer15@gmail.com>+maintainer:          Varun Gandhi <theindigamer15@gmail.com>+copyright:           Varun Gandhi 2018+category:            Data+build-type:          Simple+extra-source-files:  ChangeLog.md+cabal-version:       >=1.10+description:+   Edit is a monad for rewriting things.++source-repository head+ type:     git+ location: https://github.com/theindigamer/edit.git++flag arbitrary_instance+  description: Adds QuickCheck as a dependency to provide an Arbitrary instance.+  default: False+  manual:  True++flag comonad_instance+  description: Adds comonad as a dependency to provide a Comonad instance.+  default: False+  manual:  True++flag tutorial+  description: Build the tutorial. Adds dependencies on uniplate and containers.+  default: False+  manual:  True++library+  exposed-modules:     Data.Edit+                       Data.Edit.Tutorial+                       Control.Monad.Trans.Edit+  build-depends:       base    >= 4.9 && < 4.12+                     , deepseq >= 1.1 && < 1.5+                     , transformers >= 0.5 && < 0.6+  ghc-options:         -Wall+                       -Wincomplete-uni-patterns+                       -Wnoncanonical-monad-instances+                       -Wnoncanonical-monoid-instances+                       -Wcompat+  hs-source-dirs:      src+  default-language:    Haskell2010+  if flag(arbitrary_instance)+    CPP-options:       -DWITH_ARBITRARY_INSTANCE+    build-depends:     QuickCheck >= 2.10 && < 2.12+  if flag(comonad_instance)+    CPP-options:       -DWITH_COMONAD_INSTANCE+    build-depends:     comonad+  if flag(tutorial)+    CPP-options:       -DTUTORIAL+    build-depends:     uniplate >= 1.6 && < 1.7+                     , containers >= 0.5.8.1 && < 0.5.12++test-suite test-edit+  type:              exitcode-stdio-1.0+  hs-source-dirs:    tests+  main-is:           Driver.hs+  other-modules:     Data.EditSpec+                     Control.Monad.Trans.EditSpec+  build-Depends:     base+                   , edit+                   , doctest    >= 0.13 && < 0.15+                   , QuickCheck >= 2.10 && < 2.12+                   , uniplate   >= 1.6 && < 1.7+                   , comonad    >= 5.0  && < 5.1+                   , tasty      >= 1.0 && < 1.1+                   , tasty-discover   >= 4.2 && < 4.3+                   , tasty-quickcheck >= 0.9 && < 0.10+  ghc-options:       -Wall -threaded+  default-language:  Haskell2010+
+ src/Control/Monad/Trans/Edit.hs view
@@ -0,0 +1,100 @@+-- |+-- Module      :  Data.Edit+-- Copyright   :  (c) Varun Gandhi 2018+-- License     :  BSD-style (see the file LICENSE)+--+-- Maintainer  :  theindigamer15@gmail.com+-- Stability   :  experimental+-- Portability :  portable+--+-- A monad/comonad transformer for the 'Edit' monad.+--+-- I'm not entirely sure what this might be useful for, but it is provided for+-- the sake of completeness. If you find a concrete use case for it, please+-- submit a PR on Github to fix this section!++{-# LANGUAGE CPP #-}++module Control.Monad.Trans.Edit where++import Control.Applicative (liftA2)+import Control.Monad.IO.Class+import Control.Monad.Trans.Class+import Control.Monad.Zip+import Data.Edit+import Data.Functor.Classes++#if defined(WITH_COMONAD_INSTANCE)+import Control.Comonad+import Control.Comonad.Trans.Class+#endif++newtype EditT m a = EditT { runEditT :: m (Edit a) }++instance Eq1 m => Eq1 (EditT m) where+  liftEq eq (EditT x) (EditT y) = liftEq (liftEq eq) x y++instance Show1 m => Show1 (EditT m) where+  liftShowsPrec sp sl d (EditT m) =+      showsUnaryWith (liftShowsPrec sp' sl') "EditT" d m+    where+      sp' = liftShowsPrec sp sl+      sl' = liftShowList sp sl++instance Read1 m => Read1 (EditT m) where+  liftReadsPrec rp rl = readsData $+      readsUnaryWith (liftReadsPrec rp' rl') "EditT" EditT+    where+      rp' = liftReadsPrec rp rl+      rl' = liftReadList rp rl++instance (Eq1 m, Eq a) => Eq (EditT m a) where (==) = eq1+instance (Read1 m, Read a) => Read (EditT m a) where readsPrec = readsPrec1+instance (Show1 m, Show a) => Show (EditT m a) where showsPrec = showsPrec1++mapEditT :: (m (Edit a) -> n (Edit b)) -> EditT m a -> EditT n b+mapEditT f = EditT . f . runEditT++instance Functor m => Functor (EditT m) where+    fmap f = mapEditT (fmap (fmap f))++instance Applicative m => Applicative (EditT m) where+    pure = EditT . pure . Clean+    EditT mf <*> EditT mx = EditT $ liftA2 (<*>) mf mx++instance Monad m => Monad (EditT m) where+  return = pure+  EditT x >>= f = EditT $ do+    v <- x+    case v of+      Dirty y -> dirty <$> runEditT (f y)+      Clean y -> runEditT (f y)++instance MonadTrans EditT where+  lift = EditT . fmap Clean++instance MonadIO m => MonadIO (EditT m) where+  liftIO = lift . liftIO++instance MonadZip m => MonadZip (EditT m) where+  mzip (EditT x) (EditT y) = EditT (liftA2 mzip x y)++instance Foldable f => Foldable (EditT f) where+    foldMap f (EditT a) = foldMap (foldMap f) a++instance Traversable f => Traversable (EditT f) where+    traverse f (EditT a) = EditT <$> traverse (traverse f) a++#if defined(WITH_COMONAD_INSTANCE)+instance Comonad c => Comonad (EditT c) where+  extract = extract . extract . runEditT+  duplicate (EditT cex) = EditT ceEcex+    where+      ef = case extract cex of+        Dirty _ -> Dirty+        Clean _ -> Clean+      ceEcex = fmap (ef . EditT) (duplicate cex)++instance ComonadTrans EditT where+  lower = fmap extract . runEditT+#endif
+ src/Data/Edit.hs view
@@ -0,0 +1,345 @@+-- |+-- Module      :  Data.Edit+-- Copyright   :  (c) Varun Gandhi 2018+-- License     :  BSD-style (see the file LICENSE)+--+-- Maintainer  :  theindigamer15@gmail.com+-- Stability   :  experimental+-- Portability :  portable+--+-- The 'Edit' type for working with rewriting systems, with associated+-- operations.+--+-- To see a high-level overview of some use cases and a detailed example,+-- check the "Data.Edit.Tutorial" module.+--+-- __Usage notes:__+--+--   1. You probably want to import this module qualified to avoid a name+--      collision with "Data.Maybe"'s 'Data.Maybe.fromMaybe'.+--   2. We re-export the composition operators from "Control.Monad" for+--      convenience.++{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveAnyClass #-}++module Data.Edit+  (+  -- * Edit type and basic operations+    Edit (..)+  , fromEdit+  , isClean+  , isDirty+  , extract+  , duplicate+  , extend+  -- * Conversions to and from base types+  , toMaybe+  , fromMaybe+  , edits+  , toEither+  , fromEither+  -- * Finding a fixed point+  , polish+  , iterations+  -- * Operations with lists+  , partitionEdits+  -- * Forceful conversions+  , clean+  , dirty+  -- * Re-exports from "Control.Monad"+  , (>=>)+  , (<=<)+  )+  where++#define MONOID_SUPERCLASS_OF_SEMIGROUP    MIN_VERSION_base(4,11,0)+#define SEMIGROUP_EXPORTED_FROM_PRELUDE   MIN_VERSION_base(4,11,0)+#define LIFTREADPREC_IN_READ1             MIN_VERSION_base(4,10,0)++import Control.Applicative+import Control.DeepSeq (NFData)+import Control.Monad ((>=>), (<=<), ap)+import Control.Monad.Zip (MonadZip (..))+import Data.Data (Typeable, Data)+import Data.Either (partitionEithers)+import Data.List (unfoldr)+import Data.Functor.Classes+import GHC.Generics (Generic)++#ifdef WITH_COMONAD_INSTANCE+import Control.Comonad+#endif+#if !SEMIGROUP_EXPORTED_FROM_PRELUDE+import Data.Semigroup (Semigroup (..))+#endif+#ifdef WITH_ARBITRARY_INSTANCE+import Test.QuickCheck (Arbitrary (..), Arbitrary1 (..)+                       , frequency, arbitrary1, shrink1)+#endif++-- | The 'Edit' type encapsulates rewriting.+--+-- Since 'Edit' is also a monad, it allows you to easily "bubble up" information+-- on whether changes were made when working with nested data structures. This+-- is helpful when you want to save the fact that you've reaching a fixed point+-- while rewriting, instead of, say re-computing it after the fact using an 'Eq'+-- instance on the underlying data-type.+--+-- For example,+--+-- >>> halveEvens x = if x `mod` 2 == 0 then (Dirty $ x `div` 2) else (Clean x)+-- >>> traverse halveEvens [1, 2, 3]+-- Dirty [1,1,3]+-- >>> traverse halveEvens [1, 3, 5]+-- Clean [1,3,5]+--+-- To support this behaviour, the 'Applicative' and 'Monad' instances have+-- "polluting" semantics:+--+-- 1. 'pure' = 'Clean' = 'return'.+-- 2. The result of '<*>' is 'Clean' if and only if both the arguments are+--    'Clean'.+-- 3. If you bind a 'Clean' value, you may get anything depending on the+--    function involved. However, if you bind a 'Dirty' value, you will+--    definitely get a 'Dirty' value back.+--+-- If you're familiar with the Writer monad, 'Edit' is equivalent to+-- a Writer monad where @w@ is isomorphic to 'Bool' with @(<>) = (||)@.+--+-- If you like comonads, you can use the @comonad_instance@ package flag to,+-- erm, get a legit+-- <https://hackage.haskell.org/package/comonad-5.0.3/docs/Control-Comonad.html#t:Comonad Comonad>+-- instance, instead of just having the 'extract', 'duplicate' and 'extend'+-- functions.++data Edit a+  = Dirty a -- ^ A value that has been modified.+  | Clean a -- ^ A value that has not been modified.+  deriving+    ( Eq, Show, Read+    , Functor, Foldable, Traversable+    , Generic, NFData, Typeable, Data+    )++instance Applicative Edit where+  pure = Clean+  (<*>) = ap++instance Monad Edit where+  return = pure+  Clean x >>= f = f x+  Dirty x >>= f = dirty (f x)++instance Semigroup a => Semigroup (Edit a) where+  (<>) = liftA2 (<>)++#if MONOID_SUPERCLASS_OF_SEMIGROUP+instance Monoid a => Monoid (Edit a) where+#else+instance (Semigroup a, Monoid a) => Monoid (Edit a) where+#endif+  mempty = Clean mempty+  mappend = (<>)++instance MonadZip Edit where+  mzip = liftA2 (,)++-- These instances have been adapted from Maybe's instances.+instance Eq1 Edit where+  liftEq eq ex ey = eq (extract ex) (extract ey)++instance Show1 Edit where+  liftShowsPrec sp _ d (Clean x) = showsUnaryWith sp "Clean" d x+  liftShowsPrec sp _ d (Dirty x) = showsUnaryWith sp "Dirty" d x++-- Mimicking Maybe's Read1 instance.+#if LIFTREADPREC_IN_READ1+instance Read1 Edit where+  liftReadPrec rp _ =+    readData (readUnaryWith rp "Clean" Clean)+    <|> readData (readUnaryWith rp "Dirty" Dirty)+#else+instance Read1 Edit where+  liftReadsPrec rp _ d =+    readsData (readsUnaryWith rp "Clean" Clean) d+    `mappend` readsData (readsUnaryWith rp "Dirty" Dirty) d+#endif++#if defined(WITH_ARBITRARY_INSTANCE)+instance Arbitrary1 Edit where+  liftArbitrary arb = frequency [(1, Clean <$> arb), (4, Dirty <$> arb)]++  liftShrink shr (Dirty x) = Clean x : liftShrink shr (Clean x) ++ [Dirty x' | x' <- shr x]+  liftShrink shr (Clean x) = [Clean x' | x' <- shr x]++-- | 'arbitrary' is biased towards producing more 'Dirty' values. 'shrink'+-- shrinks the generator towards 'Clean' values.+instance Arbitrary a => Arbitrary (Edit a) where+  arbitrary = arbitrary1+  shrink = shrink1+#endif++-- | Forcibly make the value 'Clean'.+-- You probably do not want to use this function unless you're implementing+-- some class instance for 'Edit'.+clean :: Edit a -> Edit a+clean = Clean . extract++-- | Forcibly make the value 'Dirty'.+-- You probably do not want to use this function unless you're implementing+-- some class instance for 'Edit'.+dirty :: Edit a -> Edit a+dirty = Dirty . extract++-- | Extract the final value after having done some edits.+--+-- Unlike 'Data.Maybe.Maybe''s 'Data.Maybe.fromMaybe', this function doesn't+-- require a default value for totality as both constructors have a value in+-- them.+fromEdit :: Edit a -> a+fromEdit = \case+  Clean x -> x+  Dirty x -> x++-- | Was an edit made (is the value 'Dirty')? If yes, returns 'Just' otherwise+-- 'Nothing'.+--+-- >>> toMaybe (Clean "Good morning.")+-- Nothing+-- >>> toMaybe (Dirty "Wink, wink.")+-- Just "Wink, wink."+toMaybe :: Edit a -> Maybe a+toMaybe = \case+  Clean _ -> Nothing+  Dirty x -> Just x++-- | Takes a clean value and a possibly dirty value and makes an 'Edit'.+--+-- >>> fromMaybe "Hi" Nothing+-- Clean "Hi"+-- >>> defaultValue = 1000+-- >>> correctedValue = Just 1024+-- >>> fromMaybe defaultValue correctedValue+-- Dirty 1024+fromMaybe :: a -> Maybe a -> Edit a+fromMaybe x = \case+  Just y  -> Dirty y+  Nothing -> Clean x++-- | Takes a function that may dirty a value, and returns another which+-- saves the default value if no modification is done.+--+-- @f \`edits\` x == fromMaybe x (f x)@+edits :: (a -> Maybe a) -> a -> Edit a+edits f x = case f x of+  Just y  -> Dirty y+  Nothing -> Clean x++-- | A 'Dirty' value becomes a 'Left' and a 'Clean' value becomes a 'Right'.+--+-- Mnemonic: having things clean is usually the right situation to be in.+toEither :: Edit a -> Either a a+toEither = \case+  Dirty x -> Left x+  Clean x -> Right x++-- | A 'Left' value becomes a 'Dirty' and a 'Right' value becomes a 'Clean'.+--+-- Mnemonic: having things clean is usually the right situation to be in.+fromEither :: Either a a -> Edit a+fromEither = \case+  Left x -> Dirty x+  Right x -> Clean x++-- | Return 'True' iff the argument has the form @Clean _@.+isClean :: Edit a -> Bool+isClean = \case+  Clean _ -> True+  Dirty _ -> False++-- | Returns 'True' iff the argument has the form @Dirty _@.+isDirty :: Edit a -> Bool+isDirty = \case+  Clean _ -> False+  Dirty _ -> True++#if defined(WITH_COMONAD_INSTANCE)+instance Comonad Edit where+  extract = fromEdit+  duplicate = dup++instance ComonadApply Edit where+  (<@>) = (<*>)+  ( @>) = ( *>)+  (<@ ) = (<* )++#elif 1+-- | @extract = fromEdit@. Provided purely for aesthetic reasons.+extract :: Edit a -> a+extract = fromEdit++-- | Wraps the value according to its current status. Like father, like son.+duplicate :: Edit a -> Edit (Edit a)+duplicate = dup++-- | Keep track of changes while utilizing an extraction map.+--+-- > extend f = fmap f . duplicate+extend :: (Edit a -> b) -> Edit a -> Edit b+extend f = fmap f . duplicate+#endif++dup :: Edit a -> Edit (Edit a)+dup = \case+  Clean x -> Clean (Clean x)+  Dirty x -> Dirty (Dirty x)++-- | 'Dirty' values are put on the left and 'Clean' values are put on the right.+--+-- > partitionEdits = partitionEithers . map toEither+partitionEdits :: [Edit a] -> ([a], [a])+partitionEdits = partitionEithers . map toEither++-- | Keep editing till the result is 'Clean' (find the fixed point).+--+-- >>> g x = if x >= 10 then Clean x else Dirty (x + 2)+-- >>> polish g 3+-- 11+--+-- Conceptually,+--+-- > polish f x = last $ iterations f x+polish :: (a -> Edit a) -> a -> a+polish f x = case f x of+  Clean y -> y+  Dirty y -> polish f y++-- | Keep editing till the result is 'Clean', recording iterations.+--+-- Similar to 'polish' but gets the entire list of arguments tested instead of+-- just the final result. The result is guaranteed to be non-empty because+-- the first element will always be included. If the list is finite, the last+-- element gives a 'Clean' result.+--+-- >>> g x = if x >= 10 then Clean x else Dirty (x + 2)+-- >>> iterations g 3+-- [3,5,7,9,11]+--+-- This can be helpful in debugging your transformation function. For example,+--+-- @+-- [ (before, after)+-- | let xs = iterations f start+-- , (before, after) <- zip xs (tail xs)+-- , sanityCheck before && not (sanityCheck after))+-- ]+-- @+iterations :: (a -> Edit a) -> a -> [a]+iterations f = unfoldr (fmap g') . Just+  where g' y = (y, toMaybe (f y))
+ src/Data/Edit/Tutorial.hs view
@@ -0,0 +1,486 @@+-- |+-- Module      :  Data.Edit+-- Copyright   :  (c) Varun Gandhi 2018+-- License     :  BSD-style (see the file LICENSE)+--+-- Maintainer  :  theindigamer15@gmail.com+-- Stability   :  experimental+-- Portability :  portable+--+-- This is a short (?) tutorial describing how you can use the 'Data.Edit' module+-- to help you with writing dataflow analysis code for a compiler. The example+-- is a bit artificial for the sake of relative conciseness -- if you have a+-- better suggestion, or find any mistakes, please let me know on the Github+-- <https://github.com/theindigamer/edit/issues issue tracker>.+--+{-# LANGUAGE CPP #-}++#ifdef TUTORIAL+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE TupleSections #-}+#endif++#ifdef TUTORIAL+module Data.Edit.Tutorial+  ( -- * TL;DR+    -- $tldr++    -- * Setup+    -- $setup++    -- * Tutorial+    --+    -- $identexpr+    --+    -- $ConstFold+    --+    -- $ConstFoldTests+    --+    -- $Substitute+    --+    -- $SubstituteTests+    --+    -- $StmtConstProp+    --+    -- $ConstFoldPass+    --+    -- $ConstPropPass+    --+    -- $CombinedPass+    --+    -- $CombinedTest+    Ident (..)+    , Expr (..)+    , constFold+    , substitute+    , Stmt+    , constProp+    , constFold'+    , constFoldPass+    , substitute'+    , constProp'+    , constPropPass+    , constFoldAndPropPass+  )+  where+#else+module Data.Edit.Tutorial+  ( -- * TL;DR+    -- $tldr++    -- * Setup+    -- $setup++    -- * Tutorial+    --+    -- $identexpr+    --+    -- $ConstFold+    --+    -- $ConstFoldTests+    --+    -- $Substitute+    --+    -- $SubstituteTests+    --+    -- $CombinedPass+    --+    -- $CombinedTest+  )+  where+#endif++#ifdef TUTORIAL+import Data.Data+import Data.Edit+import Data.Generics.Uniplate.Data+import Data.List (unfoldr)+import Data.Map (Map)+import qualified Data.Map as Map++#endif++{- $tldr+Get a fixed point from applying a sequence of transformations.++> import Data.Edit (Edit, edits, polish, (>=>))+>+> mkAwesome1 :: Foo -> Maybe Foo+> ...+> mkAwesomeN :: Foo -> Maybe Foo+>+> mkAwesomeAny :: Foo -> Edit Foo+> mkAwesomeAny+>   = foldr (\f acc -> acc >=> (f `edits`)) pure+>     [mkAwesome1, ..., mkAwesomeN]+>+> mkAsAwesomeAsPossible :: Foo -> Foo+> mkAsAwesomeAsPossible = polish mkAwesomeAny++Transform a recursive data structure, keeping track of whether it was changed+or not, and feed the result to some high-level dataflow analysis function.++> import DataFlowLibrary+> import PlatedLibrary+> import Data.Edit (Edit, edits, toMaybe)+>+> instance FancyPlate Foo where ...+>+> mkAwesome :: Foo -> Maybe Foo+> mkAwesome = ...+>+> mkTotallyAwesome :: Foo -> Edit Foo+> mkTotallyAwesome = transformM (mkAwesome `edits`)+>+> dataFlowAnalysis = dataFlowLibFn (toMaybe . mkTotallyAwesome)+-}++-- $setup+-- The examples here use the+-- <https://github.com/ndmitchell/uniplate Uniplate> and+-- <https://github.com/haskell/containers Containers> libraries.+-- If you want to+-- follow along as we proceed, you will want to supply the package flag+-- @tutorial@ and maybe read the docs in your browser.+--+-- If you're testing inside a @cabal@ sandbox, this can be done using+--+-- > cabal configure --flags="tutorial"+-- > cabal build+-- > cabal haddock+--+-- If you're using @stack@, the same can be done using:+--+-- > stack build --flag=edit:tutorial+-- > stack haddock --flag=edit:tutorial --open edit++-- $identexpr+--+-- Let's define a toy language @L@ with 'Int's and addition.+--+-- > newtype Ident = Ident String+-- >   deriving (Show, Eq)+-- >+-- > data Expr+-- >   = Val Int+-- >   | Var Ident+-- >   | Add Expr Expr+-- >   deriving (Show, Eq)++#ifdef TUTORIAL++newtype Ident = Ident String+  deriving (Eq, Ord, Show, Typeable, Data)++data Expr+  = Val Int+  | Add Expr Expr+  | Var Ident+  deriving (Show, Eq, Typeable, Data)++#endif+-- $ConstFold+-- Q. How would you implement constant folding for the 'Expr' type?+--+-- (1) Write the recursion by hand. While this is easy enough to do since+-- 'Expr' only has a few constructors, this isn't very practical when you have+-- lots of constructors. The exact point where you recognize that this is a+-- recursive descent into unmaintainability depends on your personal boilerplate+-- threshold.+--+-- (2) Use recursion schemes and get lost in the unfathomable type errors+-- (I'm half-joking). While this is a reasonable approach, we're not going to+-- follow this here.+--+-- (3) Use a generics library. For simplicity, we'll be using Uniplate here.+-- The particular functions that are relevant at the moment are 'rewrite' and+-- 'transform'. Let's use 'rewrite'.+--+-- @+-- __\{-\# LANGUAGE DeriveDataTypeable \#-\}__+--+-- __import Data.Data__+-- __import Data.Generics.Uniplate.Data__+--+-- newtype Ident = Ident String+--   deriving (Show, Eq__, Typeable, Data__)+--+-- data Expr+--   = Val Int+--   | Var Ident+--   | Add Expr Expr+--   deriving (Show, Eq__, Typeable, Data__)+--+-- __constFold :: Expr -> Expr__+-- __constFold e = rewrite go e__+--   __where__+--     __go (Add (Val i) (Val j)) = Just (Val (i + j))__+--     __go _ = Nothing__+-- @+#ifdef TUTORIAL++constFold :: Expr -> Expr+constFold = rewrite go+  where+    go (Add (Val i) (Val j)) = Just (Val (i + j))+    go _ = Nothing++#endif+-- $ConstFoldTests+-- Test that the implementation works as expected.+--+-- >>> two = Add (Val 1) (Val 1)+-- >>> four = Add (Val 2) (Val 2)+-- >>> constFold (Add two four)+-- Val 6+-- >>> constFold (Add (Var "x") two)+-- Add (Var "x") (Val 2)++-- $Substitute+-- Let's say we add assignment statements to the language and write a function+-- to do constant propagation. First we add a @substitute@ function.+--+-- @+-- __import Data.Map (Map)__+-- __import qualified Data.Map as Map__+--+-- newtype Ident = Ident String+--   deriving (Eq, __Ord,__ Show, Typeable, Data)+--+-- __substitute :: Map Ident Int -> Expr -> Expr__+-- __substitute m e = rewrite go e__+--   __where__+--     __go (Var x) = Val \<$\> Map.lookup x m__+--     __go _ = Nothing__+-- @+#ifdef TUTORIAL++substitute :: Map Ident Int -> Expr -> Expr+substitute m = rewrite go+  where+    go (Var x) = Val <$> Map.lookup x m+    go _ = Nothing++#endif+-- $SubstituteTests+-- Let's test this out.+--+-- >>> x = Var (Ident "x")+-- >>> quadrupleX = Add x (Add x (Add x x))+-- >>> m1 = Map.fromList [(Ident "x", 5)]+-- >>> substitute m1 quadrupleX+-- Add (Val 5) (Add (Val 5) (Add (Val 5) (Val 5)))++-- $StmtConstProp+-- Finally add in statements and a constant propagation function.+--+-- @+-- __infix 9 :=__+-- __data Stmt = Ident := Expr__+--     __deriving (Show)__+--+-- __constProp :: Map Ident Int -> Stmt -> (Map Ident Int, Stmt)__+-- __constProp map_ (var := expr) = (f map_, var := expr')__+--   __where__+--     __expr' = substitute map_ expr__+--     __f = case expr' of__+--       __Val x -> Map.insert var x__+--       ___ -> Map.delete var__ -- delete old entry if var is re-defined+-- @+--+-- >>> x = Var (Ident "x")+-- >>> m1 = Map.fromList [(Ident "x", 5)]+-- >>> constProp m1 (Ident "y" := Var (Ident "x"))+-- (fromList [(Ident "x",5),(Ident "y",5)],Ident "y":=Val 5)+#ifdef TUTORIAL+infix 9 :=+data Stmt = Ident := Expr+  deriving (Show)++constProp :: Map Ident Int -> Stmt -> (Map Ident Int, Stmt)+constProp map_ (var := expr) = (f map_, var := expr')+  where+    expr' = substitute map_ expr+    f = case expr' of+      Val x -> Map.insert var x+      _ -> id+#endif++-- $ConstFoldPass+-- Now let's say we want to write two passes -- one for constant folding, one+-- for constant propagation, and then iterate until no more optimization can be+-- done (yes, this isn't an optimal strategy, but then this tutorial would be+-- even longer :O).+--+-- However, the 'constFold' function, as it stands, doesn't save the+-- "information" whether it changed something or not. Consequently, we won't+-- be able to tell if we hit the fixed point or not unless we do an equality+-- check (which could be expensive if the expression trees are big). Time to+-- finally use the 'Edit' monad!+--+-- We can use the 'edits' function, which converts a function @f: a -> Maybe a@+-- to a function @f' : a -> Edit a@.+--+-- @+-- __import Data.Edit__+--+-- -- We don't have to alter the core logic here, neat!+-- constFold__'__ :: Expr -> __Edit__ Expr+-- constFold__'__ = __transformM__ (go __\`edits\`__)+--  where+--   go (Add (Val i) (Val j)) = Just (Val (i + j))+--   go _ = Nothing+--+-- __constFoldPass :: [Stmt] -> Edit [Stmt]__+-- __constFoldPass ss = traverse (\\(v := e) -> (v :=) \<$\> constFold' e) ss__+-- @+--+#ifdef TUTORIAL++constFold' :: Expr -> Edit Expr+constFold' = transformM (go `edits`)+  where+    go (Add (Val i) (Val j)) = Just (Val (i + j))+    go _ = Nothing++constFoldPass :: [Stmt] -> Edit [Stmt]+constFoldPass = traverse (\(v := e) -> (v :=) <$> constFold' e)++#endif+-- $ConstPropPass+-- We also need slightly different versions of 'substitute' and 'constProp'.+-- Here we use the 'extract' function; it has the signature @Edit a -> a@.+-- It is fine to throw away the 'Clean'/'Dirty' information when we are updating+-- the map, because we are only interested in changes to the @Stmt@ and don't+-- care if the @Map@ gets changed or not.+--+-- @+-- substitute__'__ :: Map Ident Int -> Expr -> __Edit__ Expr+-- substitute__'__ m e = __transformM__ (go __\`edits\`__) e+--  where+--   go (Var x) = Val \<$\> Map.lookup x m+--   go _ = Nothing+--+-- constProp__'__ :: Map Ident Int -> Stmt -> (Map Ident Int, __Edit__ Stmt)+-- constProp__'__ map_ (var := expr) = (f map_, (var :=) __\<$\>__ expr')+--  where+--   expr' = substitute__'__ map_ expr+--   f = case __extract__ expr' of+--     Val x -> Map.insert var x+--     _ -> id+-- @+--+-- Let's add a top-level function similar to 'constFoldPass'.+--+-- Note: If you're unfamiliar with 'unfoldr', you can think of it as the+-- opposite of 'foldr'. 'foldr' takes a list and a starting value and+-- collapses it to a single value; 'unfoldr' takes a starting value (often+-- called a seed) and generates a list out of it.+--+-- @+-- __import Data.List (unfoldr)__+--+-- __constPropPass :: [Stmt] -> Edit [Stmt]__+-- __constPropPass ss = sequence $ unfoldr go (Map.empty, ss)__+--  __where__+--   __go (_, []) = Nothing__+--   __go (m, x:xs) = let (m', ex) = constProp' m x in Just (ex, (m', xs))__+-- @+#ifdef TUTORIAL++substitute' :: Map Ident Int -> Expr -> Edit Expr+substitute' m e = transformM (go `edits`) e+ where+  go (Var x) = Val <$> Map.lookup x m+  go _ = Nothing++constProp' :: Map Ident Int -> Stmt -> (Map Ident Int, Edit Stmt)+constProp' map_ (var := expr) = (f map_, (var :=) <$> expr')+ where+  expr' = substitute' map_ expr+  f = case extract expr' of+    Val x -> Map.insert var x+    _ -> Map.delete var++constPropPass :: [Stmt] -> Edit [Stmt]+constPropPass ss = sequence $ unfoldr go (Map.empty, ss)+  where+    go (_, []) = Nothing+    go (m, x:xs) = let (m', ex) = constProp' m x in Just (ex, (m', xs))++#endif+-- $CombinedPass+-- Finally putting all the pieces together. We can use the 'polish' function+-- to find the fixed point, which (in this case) is a fancy way of saying that+-- we keep iterating until we have a 'Clean' (unchanged) value.+--+-- @+-- __constFoldAndPropPass :: [Stmt] -> [Stmt]__+-- __constFoldAndPropPass = polish (constFoldPass >=> constPropPass)__+-- @+#ifdef TUTORIAL++constFoldAndPropPass :: [Stmt] -> [Stmt]+constFoldAndPropPass = polish (constFoldPass >=> constPropPass)++#endif+-- $CombinedTest+-- We're not done yet though! We still need to check that this works :P.+--+-- >>> [w, x, y] = map Ident ["w", "x", "y"]+-- >>> s1 = w := Add (Val 1) (Val 2)+-- >>> s2 = x := Add (Var w) (Var w)+-- >>> s3 = y := Add (Var w) (Add (Val 1) (Var x))+-- >>> s4 = x := Add (Var y) (Var y)+-- >>> s5 = y := Add (Var w) (Var x)+-- >>> constFoldAndPropPass [s1, s2, s3, s4, s5]+-- [Ident "w" := Val 3,Ident "x" := Val 6,Ident "y" := Val 10,Ident "x" := Val 20,Ident "y" := Val 23]+--+-- Yup, it works! For fun, let's see the transformation process in action.+-- We can do this using the 'iterations' function.+--+-- >>> pprint = putStr . unlines . map (unlines . map show)+-- >>> pprint $ iterations (constFoldPass >=> constPropPass) [s1, s2, s3, s4, s5]+--+-- The output shows the full history, with the final result that we obtained+-- earlier at the end.+--+-- @+-- Ident "w" := Add (Val 1) (Val 2)+-- Ident "x" := Add (Var (Ident "w")) (Var (Ident "w"))+-- Ident "y" := Add (Var (Ident "w")) (Add (Val 1) (Var (Ident "x")))+-- Ident "x" := Add (Var (Ident "y")) (Var (Ident "y"))+-- Ident "y" := Add (Var (Ident "w")) (Var (Ident "x"))+--+-- Ident "w" := Val 3+-- Ident "x" := Add (Val 3) (Val 3)+-- Ident "y" := Add (Val 3) (Add (Val 1) (Var (Ident "x")))+-- Ident "x" := Add (Var (Ident "y")) (Var (Ident "y"))+-- Ident "y" := Add (Val 3) (Var (Ident "x"))+--+-- Ident "w" := Val 3+-- Ident "x" := Val 6+-- Ident "y" := Add (Val 3) (Add (Val 1) (Val 6))+-- Ident "x" := Add (Var (Ident "y")) (Var (Ident "y"))+-- Ident "y" := Add (Val 3) (Var (Ident "x"))+--+-- Ident "w" := Val 3+-- Ident "x" := Val 6+-- Ident "y" := Val 10+-- Ident "x" := Add (Val 10) (Val 10)+-- Ident "y" := Add (Val 3) (Var (Ident "x"))+--+-- Ident "w" := Val 3+-- Ident "x" := Val 6+-- Ident "y" := Val 10+-- Ident "x" := Val 20+-- Ident "y" := Add (Val 3) (Val 20)+--+-- Ident "w" := Val 3+-- Ident "x" := Val 6+-- Ident "y" := Val 10+-- Ident "x" := Val 20+-- Ident "y" := Val 23+-- @+--+-- Fin.
+ tests/Control/Monad/Trans/EditSpec.hs view
@@ -0,0 +1,6 @@+module Control.Monad.Trans.EditSpec where++runTests :: IO ()+runTests = do+  print "Hello"+  pure ()
+ tests/Data/EditSpec.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE ViewPatterns #-}++module Data.EditSpec where++import Data.Edit+import Test.QuickCheck.Function++prop_EditApplicativeIdentity :: Edit Int -> Bool+prop_EditApplicativeIdentity v = (pure id <*> v) == v++prop_EditApplicativeComposition+  :: Edit (Fun Int Int) -> Edit (Fun Int Int) -> Edit Int -> Bool+prop_EditApplicativeComposition (fmap apply -> u) (fmap apply -> v) w+  = (pure (.) <*> u <*> v <*> w) == (u <*> (v <*> w))
+ tests/Driver.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}