From 7e7c89da98c803290d595309eedd7d10cdcf8a3b Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Fri, 22 Dec 2017 18:17:54 +0100 Subject: [PATCH 1/4] tmp work --- package.json | 13 ++- src/Control/Applicative/Free.purs | 128 ++++++++++++++++++++---- test/Test/Control/Applicative/Free.purs | 40 +++++++- 3 files changed, 147 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 331664e..b7768e8 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,13 @@ { - "name": "purescript-freeap", - "version": "3.0.1", "private": true, - "license": "MIT", "scripts": { - "psc": "PATH=$PURESCRIPT_0_11_PATH:$PATH psc 'src/**/*.purs' 'test/**/*.purs' 'bower_components/purescript-*/src/**/*.purs' 2>&1", - "pscid": "PATH=$PURESCRIPT_0_11_PATH:$PATH pscid", - "test": "node -e 'require(\"./output/Test.Main\").main()'" + "clean": "rimraf output && rimraf .pulp-cache && rimraf bench/output && rimraf bench/.pulp-cache", + "test": "pulp test", + "build": "pulp build -- --censor-lib --strict" }, "devDependencies": { - "pscid": "^1.12.1" + "pulp": "^11.0.0", + "purescript-psa": "^0.5.1", + "rimraf": "^2.6.1" } } diff --git a/src/Control/Applicative/Free.purs b/src/Control/Applicative/Free.purs index cb92d69..a76ec4a 100644 --- a/src/Control/Applicative/Free.purs +++ b/src/Control/Applicative/Free.purs @@ -7,55 +7,139 @@ module Control.Applicative.Free , analyzeFreeAp ) where -import Prelude hiding (ap) - import Data.Const (Const(..)) -import Data.Exists (Exists, mkExists, runExists) +import Data.Either (Either(..)) +import Data.List (List(..)) +import Data.List.NonEmpty as NEL import Data.Monoid (class Monoid) import Data.Newtype (unwrap) +import Data.NonEmpty ((:|)) +import Data.Tuple (Tuple(..)) +import Debug.Trace (spy) +import Prelude hiding (ap) +import Unsafe.Coerce (unsafeCoerce) -- | The free applicative functor for a type constructor `f`. -data FreeAp f a = Pure a | Ap (Exists (ApF f a)) +data FreeAp f a + = Pure a + | Lift (f a) + | Ap (FreeAp f (Val -> a)) (FreeAp f Val) -data ApF f a i = ApF (Unit -> f i) (Unit -> FreeAp f (i -> a)) - -ap :: forall f a i. (Unit -> f i) -> (Unit -> FreeAp f (i -> a)) -> FreeAp f a -ap v k = Ap (mkExists (ApF v k)) +data Val -- | Lift a value described by the type constructor `f` into -- | the free applicative functor. liftFreeAp :: forall f a. f a -> FreeAp f a -liftFreeAp a = ap (\_ -> a) (\_ -> Pure id) +liftFreeAp = Lift --- | Run a free applicative functor using the applicative instance for --- | the type constructor `f`. -retractFreeAp :: forall f a. Applicative f => FreeAp f a -> f a -retractFreeAp (Pure a) = pure a -retractFreeAp (Ap x) = runExists (\(ApF v k') -> apply (retractFreeAp (k' unit)) (v unit)) x +type ApFunc g = { func :: g (Val -> Val), count :: Int } +type FuncStack g = List (ApFunc g) +type ValStack f = NEL.NonEmptyList (FreeAp f Val) +type Stack f g = Tuple (FuncStack g) (ValStack f) -- | Run a free applicative functor with a natural transformation from -- | the type constructor `f` to the applicative functor `g`. foldFreeAp :: forall f g a. Applicative g => (f ~> g) -> FreeAp f a -> g a -foldFreeAp k (Pure a) = pure a -foldFreeAp k (Ap x) = runExists (\(ApF v k') -> apply (map (flip id) (k (v unit))) (foldFreeAp k (k' unit))) x +foldFreeAp nat z = + unsafeToG $ go $ Tuple Nil (NEL.singleton $ unsafeToFVal z) + where + unsafeToG :: g Val -> g a + unsafeToG = unsafeCoerce + + unsafeToFVal :: forall f' a'. FreeAp f' a' -> FreeAp f' Val + unsafeToFVal = unsafeCoerce + + go :: Stack f g -> g Val + go stck@(Tuple fStack (NEL.NonEmptyList (val :| vals))) = + let zzz = stck + in case val of + Pure a -> case goApply fStack vals (pure a) of + Left x -> x + Right s -> go s + Lift a -> case goApply fStack vals (nat a) of + Left x -> x + Right s -> go s + Ap l r -> + let nextVals = NEL.NonEmptyList (r :| vals) + in go $ goLeft fStack nextVals nat l 1 + +goApply + :: forall f g + . Applicative g + => FuncStack g + -> List (FreeAp f Val) + -> g Val + -> Either (g Val) (Stack f g) +goApply fStack vals gVal = + case fStack of + Nil -> Left gVal + Cons f fs -> + let gRes = f.func <*> gVal + in case vals of + Nil -> Left gRes + Cons val vals' -> + if f.count == 1 then + case fs of + Nil -> Left gRes + _ -> goApply fs vals gRes + else Right $ Tuple + (Cons { func: unsafeToGFunc gRes, count: f.count - 1 } fs) + (NEL.NonEmptyList (val :| vals')) + where + unsafeToGFunc :: g Val -> g (Val -> Val) + unsafeToGFunc = unsafeCoerce + +goLeft + :: forall f g + . Applicative g + => FuncStack g + -> ValStack f + -> (f ~> g) + -> FreeAp f (Val -> Val) + -> Int + -> Stack f g +goLeft fStack valStack nat func count = case func of + Pure a -> Tuple (Cons { func: pure a, count } fStack) valStack + Lift a -> Tuple (Cons { func: nat a, count } fStack) valStack + Ap l r -> goLeft fStack (NEL.cons r valStack) nat (unsafeToFunc l) (count + 1) + where + unsafeToFunc :: FreeAp f (Val -> Val -> Val) -> FreeAp f (Val -> Val) + unsafeToFunc = unsafeCoerce + +-- | Run a free applicative functor using the applicative instance for +-- | the type constructor `f`. +retractFreeAp :: forall f a. Applicative f => FreeAp f a -> f a +retractFreeAp = foldFreeAp id -- | Natural transformation from `FreeAp f a` to `FreeAp g a` given a -- | natural transformation from `f` to `g`. hoistFreeAp :: forall f g a. (f ~> g) -> FreeAp f a -> FreeAp g a -hoistFreeAp k (Pure a) = Pure a -hoistFreeAp k (Ap x) = runExists (\(ApF v k') -> ap (\_ -> k (v unit)) (\_ -> hoistFreeAp k (k' unit))) x +hoistFreeAp f = foldFreeAp (f >>> liftFreeAp) -- | Perform monoidal analysis over the free applicative functor `f`. analyzeFreeAp :: forall f m a. Monoid m => (forall b. f b -> m) -> FreeAp f a -> m analyzeFreeAp k = unwrap <<< foldFreeAp (Const <<< k) +mkAp :: forall f a b. FreeAp f (b -> a) -> FreeAp f b -> FreeAp f a +mkAp fba fb = Ap (coerceFunc fba) (coerceValue fb) + where + coerceFunc :: FreeAp f (b -> a) -> FreeAp f (Val -> a) + coerceFunc = unsafeCoerce + + coerceValue :: FreeAp f b -> FreeAp f Val + coerceValue = unsafeCoerce + instance functorFreeAp :: Functor (FreeAp f) where - map k (Pure a) = Pure (k a) - map k (Ap x) = runExists (\(ApF v k') -> ap v (\_ -> map ((<<<) k) (k' unit))) x + map f x = mkAp (Pure f) x instance applyFreeAp :: Apply (FreeAp f) where - apply (Pure k) f = map k f - apply (Ap x) f = runExists (\(ApF v k') -> ap v (\_ -> apply (map flip (k' unit)) f)) x + apply fba fb = mkAp fba fb instance applicativeFreeAp :: Applicative (FreeAp f) where pure = Pure + +instance showFreeAp :: Show (FreeAp f a) where + show = case _ of + Pure a -> "(Pure Val)" + Lift f -> "(Lift F)" + Ap l r -> "(Ap " <> show l <> " " <> show r <> ")" diff --git a/test/Test/Control/Applicative/Free.purs b/test/Test/Control/Applicative/Free.purs index b083445..8f42c73 100644 --- a/test/Test/Control/Applicative/Free.purs +++ b/test/Test/Control/Applicative/Free.purs @@ -2,12 +2,30 @@ module Test.Control.Applicative.Free ( checkAnalyze ) where -import Prelude (Unit, (==), (<>), unit, apply, map) -import Control.Applicative.Free (FreeAp, liftFreeAp, analyzeFreeAp) +-- import Prelude (Unit, (==), (<>), unit, (-), ($), (>>>)) +import Prelude + +import Control.Applicative ((*>)) +import Control.Applicative.Free (FreeAp, liftFreeAp, analyzeFreeAp, retractFreeAp) import Data.Either (Either(..)) +import Data.Identity (Identity(..)) +import Data.Show (class Show) +import Debug.Trace (spy) data M r = A r | B r +instance showFreeAp :: Show (M a) where + show (A _) = "A" + show (B _) = "B" + +fInc = liftFreeAp (Identity (_ + 1)) +fOne = liftFreeAp (Identity 1) +fMult = liftFreeAp (Identity (+)) + +x = fMult <*> (fInc <*> fOne) <*> (fInc <*> fOne) + +zzz = spy { res1: retractFreeAp x } + ma :: FreeAp M Unit ma = liftFreeAp (A unit) @@ -15,15 +33,27 @@ mb :: FreeAp M Unit mb = liftFreeAp (B unit) printM :: forall a. FreeAp M a -> String -printM = analyzeFreeAp go where +printM fr = + analyzeFreeAp (go >>> spy) fr + where go (A _) = "A" go (B _) = "B" +build :: Int -> FreeAp M Unit -> FreeAp M Unit -> FreeAp M Unit +build 0 _ acc = acc +build n x acc = build (n - 1) x (acc *> x) + result :: String -result = printM (apply (map (\l r -> r) ma) mb) +result = printM (build 10 (ma *> mb) mb) +-- result = printM ((mb *> mb) *> ma) +-- result = printM (mb *> (mb *> ma)) + +buildExpected :: Int -> String -> String -> String +buildExpected 0 _ acc = acc +buildExpected n x acc = buildExpected (n - 1) x (acc <> x) expected :: String -expected = "AB" +expected = buildExpected 10 "AB" "B" checkAnalyze :: Either String String checkAnalyze = if result == expected From 309cdded5a2cc5d993f5083bc3e61a5f1a826a87 Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Tue, 2 Jan 2018 01:43:11 +0100 Subject: [PATCH 2/4] fix goApply --- src/Control/Applicative/Free.purs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Control/Applicative/Free.purs b/src/Control/Applicative/Free.purs index a76ec4a..c11ee5c 100644 --- a/src/Control/Applicative/Free.purs +++ b/src/Control/Applicative/Free.purs @@ -75,16 +75,19 @@ goApply fStack vals gVal = Nil -> Left gVal Cons f fs -> let gRes = f.func <*> gVal - in case vals of - Nil -> Left gRes - Cons val vals' -> - if f.count == 1 then - case fs of - Nil -> Left gRes - _ -> goApply fs vals gRes - else Right $ Tuple - (Cons { func: unsafeToGFunc gRes, count: f.count - 1 } fs) - (NEL.NonEmptyList (val :| vals')) + in if f.count == 1 then + case fs of + Nil -> + -- here vals must be empty + Left gRes + _ -> goApply fs vals gRes + else + case vals of + Nil -> Left gRes + Cons val vals' -> + Right $ Tuple + (Cons { func: unsafeToGFunc gRes, count: f.count - 1 } fs) + (NEL.NonEmptyList (val :| vals')) where unsafeToGFunc :: g Val -> g (Val -> Val) unsafeToGFunc = unsafeCoerce From 36fc150bab63b0f0d6518a989d5fcfebe871a4db Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 10 Jan 2018 23:05:05 +0100 Subject: [PATCH 3/4] add tests --- .gitignore | 1 + bower.json | 3 +- src/Control/Applicative/Free.purs | 3 +- src/Control/Applicative/Free/Gen.purs | 40 +++++++++++ test/Test/Control/Applicative/Free.purs | 91 +++++++++++++++++-------- test/Test/Main.purs | 16 +++-- 6 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 src/Control/Applicative/Free/Gen.purs diff --git a/.gitignore b/.gitignore index e2f8c00..0a2bec4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules/ bower_components/ tmp/ output/ +.psc-ide-port diff --git a/bower.json b/bower.json index 3674de7..7b2d73d 100644 --- a/bower.json +++ b/bower.json @@ -18,6 +18,7 @@ "purescript-integers": "^3.0.0", "purescript-generics": "^4.0.0", "purescript-console": "^3.0.0", - "purescript-exceptions": "^3.0.0" + "purescript-exceptions": "^3.0.0", + "purescript-quickcheck-laws": "^3.0.1" } } diff --git a/src/Control/Applicative/Free.purs b/src/Control/Applicative/Free.purs index c11ee5c..5f74bab 100644 --- a/src/Control/Applicative/Free.purs +++ b/src/Control/Applicative/Free.purs @@ -7,6 +7,7 @@ module Control.Applicative.Free , analyzeFreeAp ) where +import Prelude import Data.Const (Const(..)) import Data.Either (Either(..)) import Data.List (List(..)) @@ -15,8 +16,6 @@ import Data.Monoid (class Monoid) import Data.Newtype (unwrap) import Data.NonEmpty ((:|)) import Data.Tuple (Tuple(..)) -import Debug.Trace (spy) -import Prelude hiding (ap) import Unsafe.Coerce (unsafeCoerce) -- | The free applicative functor for a type constructor `f`. diff --git a/src/Control/Applicative/Free/Gen.purs b/src/Control/Applicative/Free/Gen.purs new file mode 100644 index 0000000..65a605c --- /dev/null +++ b/src/Control/Applicative/Free/Gen.purs @@ -0,0 +1,40 @@ +module Control.Applicative.Free.Gen where + +import Prelude + +import Control.Applicative.Free as F +import Control.Monad.Gen (class MonadGen, oneOf) +import Control.Monad.Rec.Class (class MonadRec) +import Data.NonEmpty (NonEmpty(..)) + +genFree :: forall m f a + . MonadGen m + => MonadRec m + => m (f Unit) + -> m a + -> m (a -> a) + -> m (F.FreeAp f a) +genFree genF genA genA2A = oneOf $ NonEmpty + ( genA <#> pure) + [ do + fUnit <- genF + a <- genA + pure $ + pure (const a) <*> F.liftFreeAp fUnit + , do + fUnit <- genF + a <- genA + a2a <- genA2A + pure $ + (pure (const a) <*> F.liftFreeAp fUnit) <#> a2a + , do + fUnit <- genF + a <- genA + a2a <- genA2A + pure $ + F.liftFreeAp fUnit <#> const a <#> a2a + , do + a <- genA + a2a <- genA2A + pure $ pure a <#> a2a + ] \ No newline at end of file diff --git a/test/Test/Control/Applicative/Free.purs b/test/Test/Control/Applicative/Free.purs index 8f42c73..1fb6503 100644 --- a/test/Test/Control/Applicative/Free.purs +++ b/test/Test/Control/Applicative/Free.purs @@ -1,30 +1,24 @@ module Test.Control.Applicative.Free ( checkAnalyze + , checkStack + , check ) where --- import Prelude (Unit, (==), (<>), unit, (-), ($), (>>>)) import Prelude -import Control.Applicative ((*>)) import Control.Applicative.Free (FreeAp, liftFreeAp, analyzeFreeAp, retractFreeAp) +import Control.Applicative.Free.Gen as GenF import Data.Either (Either(..)) -import Data.Identity (Identity(..)) -import Data.Show (class Show) -import Debug.Trace (spy) +import Data.Tuple (Tuple) +import Test.QuickCheck (class Arbitrary, class Coarbitrary, arbitrary) +import Test.QuickCheck.Gen (Gen) +import Test.QuickCheck.Laws (QC, A, checkLaws) +import Test.QuickCheck.Laws.Control as Control +import Test.QuickCheck.Laws.Data as Data +import Type.Proxy (Proxy(..), Proxy2(..)) -data M r = A r | B r - -instance showFreeAp :: Show (M a) where - show (A _) = "A" - show (B _) = "B" - -fInc = liftFreeAp (Identity (_ + 1)) -fOne = liftFreeAp (Identity 1) -fMult = liftFreeAp (Identity (+)) -x = fMult <*> (fInc <*> fOne) <*> (fInc <*> fOne) - -zzz = spy { res1: retractFreeAp x } +data M r = A r | B r ma :: FreeAp M Unit ma = liftFreeAp (A unit) @@ -34,7 +28,7 @@ mb = liftFreeAp (B unit) printM :: forall a. FreeAp M a -> String printM fr = - analyzeFreeAp (go >>> spy) fr + analyzeFreeAp go fr where go (A _) = "A" go (B _) = "B" @@ -43,19 +37,58 @@ build :: Int -> FreeAp M Unit -> FreeAp M Unit -> FreeAp M Unit build 0 _ acc = acc build n x acc = build (n - 1) x (acc *> x) -result :: String -result = printM (build 10 (ma *> mb) mb) --- result = printM ((mb *> mb) *> ma) --- result = printM (mb *> (mb *> ma)) - buildExpected :: Int -> String -> String -> String buildExpected 0 _ acc = acc buildExpected n x acc = buildExpected (n - 1) x (acc <> x) -expected :: String -expected = buildExpected 10 "AB" "B" - checkAnalyze :: Either String String -checkAnalyze = if result == expected - then Right result - else Left (result <> " is not " <> expected) +checkAnalyze = + if result == expected + then Right result + else Left (result <> " is not " <> expected) + where + result :: String + result = printM (build 10 (ma *> mb) mb) + + expected :: String + expected = buildExpected 10 "AB" "B" + + +checkStack :: Either String String +checkStack = + if result == expected + then Right "safe for 100000 node" + else Left (result <> " is not " <> expected) + where + result :: String + result = printM (build 100000 (ma *> mb) mb) + + expected :: String + expected = buildExpected 100000 "AB" "B" + + +newtype ArbFreeAp a = ArbFreeAp (FreeAp (Tuple (Array String)) a) + +instance arbitraryArbFreeAp :: (Coarbitrary a, Arbitrary a) => Arbitrary (ArbFreeAp a) where + arbitrary = ArbFreeAp <$> + GenF.genFree + arbitrary + (arbitrary :: Gen a) + (arbitrary :: Gen (a -> a)) + +instance eqArbFreeAp :: Eq a => Eq (ArbFreeAp a) where + eq (ArbFreeAp a) (ArbFreeAp b) = retractFreeAp a == retractFreeAp b + +derive newtype instance functorArbFreeAp :: Functor ArbFreeAp +derive newtype instance applyArbFreeAp :: Apply ArbFreeAp +derive newtype instance applicativeArbFreeAp :: Applicative ArbFreeAp + +check ∷ ∀ eff. QC eff Unit +check = checkLaws "FreeAp" do + Data.checkEq prxFree + Data.checkFunctor prx2Free + Control.checkApply prx2Free + Control.checkApplicative prx2Free + where + prxFree = Proxy ∷ Proxy (ArbFreeAp A) + prx2Free = Proxy2 ∷ Proxy2 ArbFreeAp diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 584a1da..f0b2284 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -1,14 +1,14 @@ module Test.Main where -import Prelude (Unit, bind, discard) - +import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log, logShow) - -import Test.Control.Applicative.Free.Validation as Validation +import Control.Monad.Eff.Exception (EXCEPTION) +import Control.Monad.Eff.Random (RANDOM) import Test.Control.Applicative.Free as FreeTest +import Test.Control.Applicative.Free.Validation as Validation -main :: Eff (console :: CONSOLE) Unit +main :: Eff ( console ∷ CONSOLE , random ∷ RANDOM , exception ∷ EXCEPTION ) Unit main = do log "\nvalid case:" logShow (Validation.runForm "Joe" "Smith" "28") @@ -21,3 +21,9 @@ main = do log "\nanalyze:" logShow FreeTest.checkAnalyze + + log "\nstack safety:" + logShow FreeTest.checkStack + + log "\nlaws:" + FreeTest.check From 9a11a5f17289383740c89256194038e7dc99723d Mon Sep 17 00:00:00 2001 From: Irakli Safareli Date: Wed, 17 Jan 2018 19:15:43 +0100 Subject: [PATCH 4/4] remove debug leftover, code formating --- src/Control/Applicative/Free.purs | 11 ++--------- src/Control/Applicative/Free/Gen.purs | 2 +- test/Test/Main.purs | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Control/Applicative/Free.purs b/src/Control/Applicative/Free.purs index 5f74bab..2b6c0cd 100644 --- a/src/Control/Applicative/Free.purs +++ b/src/Control/Applicative/Free.purs @@ -49,9 +49,8 @@ foldFreeAp nat z = unsafeToFVal = unsafeCoerce go :: Stack f g -> g Val - go stck@(Tuple fStack (NEL.NonEmptyList (val :| vals))) = - let zzz = stck - in case val of + go (Tuple fStack (NEL.NonEmptyList (val :| vals))) = + case val of Pure a -> case goApply fStack vals (pure a) of Left x -> x Right s -> go s @@ -139,9 +138,3 @@ instance applyFreeAp :: Apply (FreeAp f) where instance applicativeFreeAp :: Applicative (FreeAp f) where pure = Pure - -instance showFreeAp :: Show (FreeAp f a) where - show = case _ of - Pure a -> "(Pure Val)" - Lift f -> "(Lift F)" - Ap l r -> "(Ap " <> show l <> " " <> show r <> ")" diff --git a/src/Control/Applicative/Free/Gen.purs b/src/Control/Applicative/Free/Gen.purs index 65a605c..3e9dcf6 100644 --- a/src/Control/Applicative/Free/Gen.purs +++ b/src/Control/Applicative/Free/Gen.purs @@ -37,4 +37,4 @@ genFree genF genA genA2A = oneOf $ NonEmpty a <- genA a2a <- genA2A pure $ pure a <#> a2a - ] \ No newline at end of file + ] diff --git a/test/Test/Main.purs b/test/Test/Main.purs index f0b2284..03db937 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -8,7 +8,7 @@ import Control.Monad.Eff.Random (RANDOM) import Test.Control.Applicative.Free as FreeTest import Test.Control.Applicative.Free.Validation as Validation -main :: Eff ( console ∷ CONSOLE , random ∷ RANDOM , exception ∷ EXCEPTION ) Unit +main :: Eff (console ∷ CONSOLE , random ∷ RANDOM , exception ∷ EXCEPTION) Unit main = do log "\nvalid case:" logShow (Validation.runForm "Joe" "Smith" "28")