]> Git — Sourcephile - haskell/symantic-base.git/commitdiff
iface: add interpreter `LetInserter` main
authorJulien Moutinho <julm@sourcephile.fr>
Fri, 3 May 2024 10:41:24 +0000 (12:41 +0200)
committerJulien Moutinho <julm@sourcephile.fr>
Fri, 24 May 2024 23:57:16 +0000 (01:57 +0200)
15 files changed:
.gitignore
.hlint.yaml
.reuse/dep5
Makefile
cabal.project [deleted file]
flake.lock
flake.nix
src/Symantic/Semantics/Identity.hs
src/Symantic/Semantics/LetInserter.hs [new file with mode: 0644]
src/Symantic/Semantics/Viewer.hs
src/Symantic/Syntaxes/Classes.hs
symantic-base.cabal
tests/Spec.hs [new file with mode: 0644]
tests/Symantic/Semantics/LetInserterSpec.hs [new file with mode: 0644]
tests/Symantic/Syntaxes/Extras.hs [new file with mode: 0644]

index 619db9e8287200be4c9beba20c59fa9f47c2bafb..bb3d4b9e8dba93520d58bca272e832760a5ef174 100644 (file)
@@ -11,6 +11,7 @@
 .direnv/
 .ghc.environment.*
 .stack-work/
+dist/
 dist-newstyle/
 dump-core/
 hlint.html
index f8d02d88176a72fde8467f1018eac4664224feb4..077a4aec5b67aa5bf5624e8650b74e2149b9665c 100644 (file)
@@ -4,6 +4,7 @@
   - name: TypeApplications
 
 - ignore: {name: Avoid lambda}
+- ignore: {name: Collapse lambdas}
 - ignore: {name: Move brackets to avoid $}
 - ignore: {name: Reduce duplication}
 - ignore: {name: Redundant $}
@@ -13,6 +14,7 @@
 - ignore: {name: Use camelCase}
 - ignore: {name: Use const}
 - ignore: {name: Use fmap}
+- ignore: {name: Use guards}
 - ignore: {name: Use id}
 - ignore: {name: Use if}
 - ignore: {name: Use import/export shortcut}
index e9dd7693d4067b9d7623b494fbfd7dd79346aa8e..98442262d1188609f6c8608bd22ff202d0314d97 100644 (file)
@@ -7,6 +7,6 @@ Files: *.nix *.lock cabal.project *.cabal *.md .chglog/* .envrc fourmolu.yaml .g
 Copyright: Julien Moutinho <julm+symantic-base@sourcephile.fr>
 License: CC0-1.0
 
-Files: src/*
+Files: src/* tests/*
 Copyright: Julien Moutinho <julm+symantic-base@sourcephile.fr>
 License: AGPL-3.0-or-later
index 71184c315f23067dfd1760d4cefbbd7f8264b88e..2ac569ef49464563026b862f18ae19f9071766aa 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,17 +5,38 @@ project := $(patsubst %.cabal,%,$(cabal))
 cabal_builddir ?= dist-newstyle
 
 override REPL_OPTIONS += -ignore-dot-ghci
+override TEST_OPTIONS +=
 override GHCID_OPTIONS += --no-height-limit --reverse-errors --color=always --warnings --restart $(cabal)
 
+# Note that cabal's v1-* commands are required to use sydtest-discover from $PATH
+# instead of requiring a cabal update to build it.
+
 all: build
 build:
-       cabal build $(CABAL_BUILD_FLAGS)
+       cabal v1-build $(CABAL_BUILD_FLAGS)
 clean c:
        cabal clean
 repl:
-       cabal repl $(CABAL_REPL_FLAGS) $(project)
+       cabal v1-repl $(CABAL_REPL_FLAGS) $(project)
 ghcid:
-       ghcid $(GHCID_OPTIONS) --command 'cabal repl -fno-code $(CABAL_REPL_FLAGS) $(project) $(addprefix --repl-options ,$(REPL_OPTIONS))'
+       ghcid $(GHCID_OPTIONS) --command 'cabal v1-repl $(CABAL_REPL_FLAGS) $(project) $(addprefix --repl-options ,$(REPL_OPTIONS))'
+
+.PHONY: tests
+t tests:
+       cabal v1-test $(CABAL_TEST_FLAGS) \
+        --show-details always --test-options "$(TEST_OPTIONS)"
+t/repl tests/repl:
+       cabal v1-repl $(CABAL_REPL_FLAGS) $(CABAL_TEST_FLAGS) $(project)-tests
+t/ghcid tests/ghcid:
+       ghcid $(GHCID_OPTIONS) --command 'cabal v1-repl $(CABAL_REPL_FLAGS) $(project) $(addprefix --repl-options ,$(REPL_OPTIONS))' \
+         --run=':! ghcid $(GHCID_OPTIONS) --command "cabal v1-repl $(CABAL_REPL_FLAGS) $(CABAL_TEST_FLAGS) $(project)-tests" --test ":main $(TEST_OPTIONS)"'
+
+%/accept: TEST_OPTIONS += --accept
+%/accept: %
+       
+%/cover: CABAL_TEST_FLAGS += --enable-coverage
+%/cover: %
+       
 
 doc:
        cabal haddock --haddock-css ocean --haddock-hyperlink-source
diff --git a/cabal.project b/cabal.project
deleted file mode 100644 (file)
index dc68224..0000000
+++ /dev/null
@@ -1 +0,0 @@
-packages:.
index e64f0b219fc672742c500692b31c58453f8bb643..e8ac6d1025f335a6467588a9978869516ea84949 100644 (file)
@@ -1,59 +1,98 @@
 {
   "nodes": {
-    "flake-utils": {
+    "flake-compat": {
+      "flake": false,
       "locked": {
-        "lastModified": 1667077288,
-        "narHash": "sha256-bdC8sFNDpT0HK74u9fUkpbf1MEzVYJ+ka7NXCdgBoaA=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "6ee9ebb6b1ee695d2cacc4faa053a7b9baa76817",
+        "lastModified": 1696426674,
+        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
         "type": "github"
       },
       "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
+        "owner": "edolstra",
+        "repo": "flake-compat",
         "type": "github"
       }
     },
-    "nixpkgs": {
+    "git-hooks": {
+      "inputs": {
+        "flake-compat": "flake-compat",
+        "gitignore": "gitignore",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "nixpkgs-stable": "nixpkgs-stable"
+      },
       "locked": {
-        "lastModified": 1691328192,
-        "narHash": "sha256-w59N1zyDQ7SupfMJLFvtms/SIVbdryqlw5AS4+DiH+Y=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "61676e4dcfeeb058f255294bcb08ea7f3bc3ce56",
+        "lastModified": 1715870890,
+        "narHash": "sha256-nacSOeXtUEM77Gn0G4bTdEOeFIrkCBXiyyFZtdGwuH0=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "fa606cccd7b0ccebe2880051208e4a0f61bfc8c1",
         "type": "github"
       },
       "original": {
-        "id": "nixpkgs",
-        "type": "indirect"
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
       }
     },
-    "pre-commit-hooks": {
+    "gitignore": {
       "inputs": {
-        "flake-utils": "flake-utils",
         "nixpkgs": [
+          "git-hooks",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1667992213,
-        "narHash": "sha256-8Ens8ozllvlaFMCZBxg6S7oUyynYx2v7yleC5M0jJsE=",
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
-        "rev": "ebcbfe09d2bd6d15f68de3a0ebb1e4dcb5cd324b",
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
         "type": "github"
       },
       "original": {
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1712310679,
+        "narHash": "sha256-XgC/a/giEeNkhme/AV1ToipoZ/IVm1MV2ntiK4Tm+pw=",
+        "path": "/nix/store/8mk40j3mfrvndrnh89azngcgdgr1fidi-source",
+        "rev": "72da83d9515b43550436891f538ff41d68eecc7f",
+        "type": "path"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs-stable": {
+      "locked": {
+        "lastModified": 1710695816,
+        "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "614b4613980a522ba49f0d194531beddbb7220d3",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-23.11",
+        "repo": "nixpkgs",
         "type": "github"
       }
     },
     "root": {
       "inputs": {
-        "nixpkgs": "nixpkgs",
-        "pre-commit-hooks": "pre-commit-hooks"
+        "git-hooks": "git-hooks",
+        "nixpkgs": "nixpkgs"
       }
     }
   },
index 72724fac03a2df967b19a0aebd6808aa5d1bd31c..908b2217b0c898c2e14850a07020548cc780f62c 100644 (file)
--- a/flake.nix
+++ b/flake.nix
@@ -1,53 +1,67 @@
 {
   inputs = {
     nixpkgs.url = "flake:nixpkgs";
-    pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
-    pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
+    git-hooks.url = "github:cachix/git-hooks.nix";
+    git-hooks.inputs.nixpkgs.follows = "nixpkgs";
   };
   outputs = inputs:
     let
       pkg = "symantic-base";
       lib = inputs.nixpkgs.lib;
+      fileInputs = with lib.fileset; toSource {
+        root = ./.;
+        fileset = unions [
+          ./symantic-base.cabal
+          ./LICENSES
+          (fileFilter (file: lib.any file.hasExt [ ".hs" ]) ./src)
+          (fileFilter (file: lib.any file.hasExt [ ".hs" ]) ./tests)
+        ];
+      };
       forAllSystems = f: lib.genAttrs lib.systems.flakeExposed (system: f rec {
         inherit system;
         pkgs = inputs.nixpkgs.legacyPackages.${system};
         haskellPackages = pkgs.haskellPackages.extend (with pkgs.haskell.lib; hfinal: hsuper: {
-          ${pkg} = buildFromSdist (hfinal.callCabal2nix pkg ./. { });
+          ${pkg} = buildFromSdist (hfinal.callCabal2nix pkg fileInputs { });
         });
       });
     in
     rec {
       # nix -L build
-      defaultPackage = forAllSystems ({ haskellPackages, ... }: haskellPackages.${pkg});
+      packages = forAllSystems ({ haskellPackages, ... }: {
+        default = haskellPackages.${pkg};
+      });
       # nix -L develop  or  direnv allow
-      devShell = forAllSystems ({ pkgs, haskellPackages, system, ... }:
-        haskellPackages.shellFor {
-          packages = ps: [ ps.${pkg} ];
-          nativeBuildInputs = [
-            haskellPackages.cabal-install
-            haskellPackages.ghcid
-            haskellPackages.haskell-language-server
-            haskellPackages.hlint
-            pkgs.git-chglog
-            pkgs.reuse
-          ];
-          withHoogle = false;
-          inherit (checks.${system}.pre-commit-check) shellHook;
-        });
+      devShells = forAllSystems ({ pkgs, haskellPackages, system, ... }: {
+        default =
+          haskellPackages.shellFor {
+            packages = ps: [ ps.${pkg} ];
+            nativeBuildInputs = [
+              haskellPackages.cabal-install
+              haskellPackages.ghcid
+              haskellPackages.haskell-language-server
+              haskellPackages.hlint
+              pkgs.git-chglog
+              pkgs.reuse
+            ];
+            withHoogle = false;
+            inherit (checks.${system}.git-hooks-check) shellHook;
+          };
+      });
       # nix flake check
       checks = forAllSystems (args: with args; {
-        pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
+        git-hooks-check = inputs.git-hooks.lib.${system}.run {
           src = ./.;
-          #settings.ormolu.cabalDefaultExtensions = true;
-          settings.ormolu.defaultExtensions = [
-            "ImportQualifiedPost"
-            "TypeApplications"
-          ];
           hooks = {
+            ormolu.settings.cabalDefaultExtensions = true;
+            cabal-fmt.enable = true;
+            fourmolu.enable = true;
             hlint.enable = true;
             nixpkgs-fmt.enable = true;
-            fourmolu.enable = true;
-            cabal-fmt.enable = true;
+            reuse = {
+              enable = true;
+              entry = "${pkgs.reuse}/bin/reuse lint";
+              pass_filenames = false;
+            };
           };
         };
       });
index 0d730cd37854b923cad5677be16b9c764808a56d..6cdf04278f5d5109c30b4c6b542fc9762cc042c0 100644 (file)
@@ -1,21 +1,57 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
--- | This module provides the 'Identity' semantic
+
+-- | This module provides the 'Identity' and 'IdentityT' semantics
 -- which interprets the combinators as a Haskell value.
-module Symantic.Semantics.Identity
-  ( Identity(..)
-  ) where
+module Symantic.Semantics.Identity (
+  Identity (..),
+  IdentityT (..),
+) where
 
 import Control.Applicative qualified as App
+import Control.Monad.Trans.Identity (IdentityT (..))
 import Data.Either qualified as Either
 import Data.Eq qualified as Eq
 import Data.Function qualified as Fun
-import Data.Functor.Identity (Identity(..))
+import Data.Functor.Identity (Identity (..))
 import Data.Maybe qualified as Maybe
 
+import Debug.Trace (traceShow)
+import Text.Show (Show)
+
 import Symantic.Syntaxes.Classes
+import Symantic.Syntaxes.Derive
 
--- * Type 'Identity'
+-- * Type 'IdentityT'
 
+type instance Derived (IdentityT sem) = sem
+instance Derivable (IdentityT sem) where
+  derive = runIdentityT
+instance LiftDerived (IdentityT sem) where
+  liftDerived = IdentityT
+instance LiftDerived1 (IdentityT sem) where
+  liftDerived1 f = IdentityT Fun.. f Fun.. runIdentityT
+instance LiftDerived2 (IdentityT sem) where
+  liftDerived2 f x y = IdentityT (f (runIdentityT x) (runIdentityT y))
+instance LiftDerived3 (IdentityT sem) where
+  liftDerived3 f x y z = IdentityT (f (runIdentityT x) (runIdentityT y) (runIdentityT z))
+instance LiftDerived4 (IdentityT sem) where
+  liftDerived4 f w x y z = IdentityT (f (runIdentityT w) (runIdentityT x) (runIdentityT y) (runIdentityT z))
+
+instance Abstractable sem => Abstractable (IdentityT sem)
+instance Abstractable1 sem => Abstractable1 (IdentityT sem)
+instance Anythingable sem => Anythingable (IdentityT sem)
+instance Constantable c sem => Constantable c (IdentityT sem)
+instance Eitherable sem => Eitherable (IdentityT sem)
+instance Equalable sem => Equalable (IdentityT sem)
+instance IfThenElseable sem => IfThenElseable (IdentityT sem)
+instance Instantiable sem => Instantiable (IdentityT sem)
+instance LetRecable idx sem => LetRecable idx (IdentityT sem)
+instance Listable sem => Listable (IdentityT sem)
+instance Maybeable sem => Maybeable (IdentityT sem)
+instance Unabstractable sem => Unabstractable (IdentityT sem)
+instance Varable sem => Varable (IdentityT sem)
+
+-- * Type 'Identity'
 instance Abstractable Identity where
   lam f = Identity (runIdentity Fun.. f Fun.. Identity)
 instance Abstractable1 Identity where
@@ -30,10 +66,12 @@ instance Eitherable Identity where
 instance Equalable Identity where
   equal = Identity (Eq.==)
 instance IfThenElseable Identity where
-  ifThenElse test ok ko = Identity
-    (if runIdentity test
-    then runIdentity ok
-    else runIdentity ko)
+  ifThenElse test ok ko =
+    Identity
+      ( if runIdentity test
+          then runIdentity ok
+          else runIdentity ko
+      )
 instance Instantiable Identity where
   Identity f .@ Identity x = Identity (f x)
 instance Listable Identity where
@@ -51,3 +89,13 @@ instance Unabstractable Identity where
   ($) = Identity (Fun.$)
 instance Varable Identity where
   var = Fun.id
+instance Letable Identity where
+  let_ e body = body e
+instance Show idx => LetRecable idx Identity where
+  letRec _idx f body =
+    Identity Fun.$
+      -- traceShow ["Identity", "letRec"] Fun.$
+      let self idx =
+            -- traceShow (["Identity", "letRec", "self"], ("idx", idx)) Fun.$
+            runIdentity (f (Identity Fun.. self) idx)
+      in runIdentity (body (Identity Fun.. self))
diff --git a/src/Symantic/Semantics/LetInserter.hs b/src/Symantic/Semantics/LetInserter.hs
new file mode 100644 (file)
index 0000000..a61a8f8
--- /dev/null
@@ -0,0 +1,831 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+-- For Syn.LetRecable
+{-# LANGUAGE UndecidableInstances #-}
+
+module Symantic.Semantics.LetInserter where
+
+import Control.Applicative (liftA2, liftA3)
+import Control.Monad (Monad (..))
+import Control.Monad.Trans.State.Strict qualified as MT
+import Data.Bool (Bool (..), not, otherwise, (&&))
+import Data.Either (Either (..))
+import Data.Eq (Eq (..))
+import Data.Foldable (foldMap)
+import Data.Function (const, id, on, ($), (.))
+import Data.Functor ((<$>))
+import Data.Int (Int)
+import Data.Kind (Type)
+import Data.List qualified as List
+import Data.Maybe (Maybe (..), isNothing, maybe)
+import Data.Ord (Ord (..))
+import Data.Semigroup (Semigroup (..))
+import Data.Set qualified as Set
+import Debug.Trace qualified as Trace
+import Numeric.Natural (Natural)
+import Symantic.Syntaxes.Classes qualified as Syn
+import Symantic.Syntaxes.Derive qualified as Syn
+import Text.Show (Show (..), showListWith, showString, shows)
+import Unsafe.Coerce (unsafeCoerce)
+import Prelude (error, pred)
+import Prelude qualified
+
+-- import Control.Monad.Trans.Class qualified as MT
+-- import Data.Functor.Identity (Identity(..))
+-- import Data.Monoid (Monoid(..))
+-- import Data.String (String)
+
+traceShow :: Show a => a -> b -> b
+-- traceShow = Trace.traceShow
+traceShow _x a = a
+
+newtype Locus = Locus Natural
+  deriving (Eq, Ord)
+  deriving newtype (Show)
+
+{-
+type Locus = [Pigit]
+data Pigit = POne | PTwo | PThree | PLoc Locus | PNote String
+  deriving (Eq, Ord)
+instance Show Pigit where
+  showsPrec _p x =
+    case x of
+      POne -> shows (1::Int)
+      PTwo -> shows (2::Int)
+      PThree -> shows (3::Int)
+      PLoc loc -> shows loc
+      PNote msg -> showString msg
+-}
+
+-- * Type 'OpenCode'
+newtype OpenCode code a = OpenCode {unOpenCode :: Env code -> code a}
+type instance Syn.Derived (OpenCode code) = code
+
+-- instance Syn.Derivable (OpenCode code) where
+--  derive oc = unOpenCode oc mempty
+instance Syn.LiftDerived (OpenCode code) where
+  liftDerived x = OpenCode (const x)
+instance Syn.LiftDerived1 (OpenCode code) where
+  liftDerived1 f x = OpenCode (\env -> f (unOpenCode x env))
+instance Syn.LiftDerived2 (OpenCode code) where
+  liftDerived2 f x y = OpenCode (\env -> f (unOpenCode x env) (unOpenCode y env))
+instance Syn.LiftDerived3 (OpenCode code) where
+  liftDerived3 f x y z = OpenCode (\env -> f (unOpenCode x env) (unOpenCode y env) (unOpenCode z env))
+
+-- instance Syn.Abstractable code => Syn.Abstractable (OpenCode code) where
+--  lam f = OpenCode (\env -> Syn.lam (\x -> unOpenCode f env (unOpenCode x env)))
+-- instance Syn.Instantiable code => Syn.Instantiable (OpenCode code)
+-- instance Syn.Constantable c code => Syn.Constantable c (OpenCode code)
+instance Syn.IfThenElseable code => Syn.IfThenElseable (OpenCode code) where
+  ifThenElse x y z = OpenCode (\env -> Syn.ifThenElse (unOpenCode x env) (unOpenCode y env) (unOpenCode z env))
+instance Syn.Equalable code => Syn.Equalable (OpenCode code) where
+  equal = OpenCode (const Syn.equal)
+instance Syn.Constantable c code => Syn.Constantable c (OpenCode code)
+
+-- ** Type Env'
+type Env code = [EnvBind code]
+
+-- *** Type 'Var'
+newtype Var a = Var {unVar :: VarName}
+  deriving (Eq)
+  deriving newtype (Show)
+type VarName = Locus
+
+-- *** Type 'EnvBind'
+data EnvBind code where
+  EnvBind :: !(Var a, code a) -> EnvBind code
+instance Show (EnvBind code) where
+  showsPrec p (EnvBind (n, _e)) = showsPrec p n
+
+lookupVar :: Var a -> OpenCode code a
+lookupVar (Var v) =
+  traceShow (["lookupVar", "OpenCode"], v) $
+    OpenCode $ \env ->
+      traceShow (["lookupVar", "OpenCode", "find"], v, ("env", env)) $
+        case List.find (\(EnvBind (Var v1, _)) -> v1 == v) env of
+          Just (EnvBind (_, x)) -> unsafeCoerce x
+          Nothing -> error ("lookupVar failure for var=(" <> show v <> ")  env=" <> show ((\(EnvBind (Var n, _v)) -> n) <$> env))
+
+lookupVarLetInserter :: Var a -> LetInserter code a
+lookupVarLetInserter v =
+  LetInserter $
+    return
+      AnnotatedCode
+        { annotatedCodeOpenCode = lookupVar v
+        , annotatedCodeFloatingLets = FloatingLets []
+        , annotatedCodeFreeVars = Set.singleton (unVar v)
+        }
+
+insertVar :: Var a -> code a -> Env code -> Env code
+insertVar v e env =
+  traceShow ("insertVar", v) $
+    EnvBind (v, e) : env
+
+-- * Type 'Memo'
+type Memo = Natural
+
+-- ** Type 'MaybeMemo'
+
+-- | Like a @(Maybe Memo)@ except 'Nothing' is never equal to any other value
+-- to implement unique memoization key.
+newtype MaybeMemo = MaybeMemo (Maybe Memo)
+  deriving (Show)
+
+instance Eq MaybeMemo where
+  MaybeMemo (Just x) == MaybeMemo (Just y) = x == y
+  _ == _ = False
+
+-- * Type 'FloatingLet'
+data FloatingLet code where
+  FloatingLet ::
+    { floatingLetLocus :: !(Maybe Locus)
+    -- ^ 'FloatingLet' are either inserted at the place of an explicit 'Locus',
+    -- or at the binding ('lam' or 'let_') that dominates all free variables of the bound expression,
+    -- whichever has the narrowest scope.
+    , floatingLetMemo :: !MaybeMemo
+    -- ^ Memoization key, either internal (unique) or user-specified.
+    --
+    -- The memo key defines the equivalence classes:
+    -- expressions with the same memo key are to be shared.
+    -- Therefore, if @('genLetMemo' k l e)@ finds that there is already a let-binding
+    -- produced by an earlier 'genLetMemo' with the same locus @(l)@ and the same memo key @(k)@,
+    -- @('genLetMemo' k l e) returns the code of the earlier bound variable.
+    , floatingLetVarName :: !(Var a)
+    , floatingLetBoundCode :: !(BoundCode code a)
+    , floatingLetFreeVars :: !(Set.Set VarName)
+    } ->
+    FloatingLet code
+instance Show (FloatingLet code) where
+  showsPrec _p FloatingLet{..} =
+    showString "FloatingLet{"
+      . showString "locus="
+      . shows floatingLetLocus
+      . showString ", "
+      . showString "memo="
+      . shows floatingLetMemo
+      . showString ", "
+      . showString "var="
+      . shows floatingLetVarName
+      . showString ", "
+      . showString "exp="
+      . showString (case floatingLetBoundCode of BoundCodeOpenCode{} -> "BoundCodeOpenCode"; BoundCodeLetInserter{} -> "BoundCodeLetInserter")
+      . showString ", "
+      . showString "fvs="
+      . showListWith (shows) (Set.toList floatingLetFreeVars)
+      . showString ", "
+      . showString "}"
+
+-- * Type 'VLBindings'
+
+-- A virtual let-binding.
+-- The key idea is virtual let-bindings: whereas clet introduces an ordinary let-binding
+-- whose location is fixed, glet generates the code for a fresh variable accompanied by a virtual
+-- binding of that variable. Virtual bindings do not have (yet) a fixed location: they are
+-- attached to the expression that uses their bound variables and ‘float up’ when their attached
+-- expression is incorporated into a bigger expression. Eventually, when they reach a point that
+-- a metaprogrammer has marked with a dedicated primitive, virtual bindings are converted to
+-- real let-bindings.
+newtype FloatingLets code = FloatingLets {unFloatingLets :: [FloatingLet code]}
+instance Show (FloatingLets code) where
+  showsPrec _p (FloatingLets l) = go l
+    where
+      go [] = showString "\n"
+      go (e : es) = showString "\n  , " . shows e . go es
+
+-- | Append two lists skipping the duplicates.
+-- The order of elements is preserved
+mergeFloatingLets :: FloatingLets code -> FloatingLets code -> FloatingLets code
+mergeFloatingLets (FloatingLets xs) (FloatingLets ys) =
+  traceShow (["mergeFloatingLets"], xs, ys) $
+    FloatingLets (loop (List.reverse xs) ys)
+  where
+    loop acc = \case
+      [] -> List.reverse acc
+      y@FloatingLet{floatingLetVarName = Var yv} : t ->
+        case List.filter
+          ( \a@FloatingLet{floatingLetVarName = Var av} ->
+              floatingLetLocus y == floatingLetLocus a
+                && floatingLetMemo y == floatingLetMemo a
+                && yv == av
+          )
+          acc of
+          [] -> loop (y : acc) t
+          _ -> loop (acc) t
+
+-- fs ->
+--  loop (List.concatMap (\FloatingLet{floatingLetBoundCode, floatingLetLocus, floatingLetMemo, floatingLetVarName=Var av} ->
+--    if av == yv
+--    then
+--    FloatingLet { floatingLetVarName=Var yv, .. }) fs <> acc) t
+instance Semigroup (FloatingLets code) where
+  (<>) = mergeFloatingLets
+
+-- instance Monoid (FloatingLets code) where
+--  mempty = FloatingLets []
+--  mappend = (<>)
+
+-- * Type 'BoundCode'
+
+-- | Bound expression
+data BoundCode (code :: Type -> Type) a
+  = BoundCodeOpenCode !(OpenCode code a)
+  | -- | In the recursive case,
+    -- a virtual binding may carry a yet to be computed piece of code.
+    BoundCodeLetInserter !(LetInserter code a)
+
+-- * Type 'AnnotatedCode'
+
+-- | A code value.
+-- A code value is annotated only with the type of the expression it generates,
+-- with no further classifiers (at present).
+data AnnotatedCode (code :: Type -> Type) a = AnnotatedCode
+  { annotatedCodeOpenCode :: !(OpenCode code a)
+  , annotatedCodeFloatingLets :: !(FloatingLets code)
+  , annotatedCodeFreeVars :: !(Set.Set VarName)
+  -- ^ The free variables of the code,
+  -- used to limit the floating up of locus-less 'FloatingLet's
+  -- at 'lam' or 'let_' level, in order to avoid scope extrusion.
+  }
+
+-- annotatedCodeFreeVars :: AnnotatedCode code a -> Set.Set VarName
+-- annotatedCodeFreeVars annotatedCode = foldMap floatingLetFreeVars (unFloatingLets (annotatedCodeFloatingLets annotatedCode))
+
+runCD :: Syn.Letable code => AnnotatedCode code a -> code a
+runCD AnnotatedCode{annotatedCodeOpenCode = oc, annotatedCodeFloatingLets = FloatingLets bindingFLS} =
+  let bindingLFSByMemo = groupBy ((==) `on` floatingLetMemo) bindingFLS
+  in (`unOpenCode` []) $ List.foldr bindFloatingLetMemo oc bindingLFSByMemo
+runCD _ = error "runCD: virtual binding extrusion"
+
+type instance Syn.Derived (AnnotatedCode code) = OpenCode code
+instance Syn.LiftDerived (AnnotatedCode code) where
+  liftDerived x = AnnotatedCode{annotatedCodeOpenCode = x, annotatedCodeFloatingLets = FloatingLets [], annotatedCodeFreeVars = Set.empty}
+instance Syn.LiftDerived1 (AnnotatedCode code) where
+  liftDerived1 f e1 =
+    AnnotatedCode
+      { annotatedCodeOpenCode = f (annotatedCodeOpenCode e1)
+      , annotatedCodeFloatingLets = annotatedCodeFloatingLets e1
+      , annotatedCodeFreeVars = annotatedCodeFreeVars e1
+      }
+instance Syn.LiftDerived2 (AnnotatedCode code) where
+  liftDerived2 f e1 e2 =
+    AnnotatedCode
+      { annotatedCodeOpenCode = f (annotatedCodeOpenCode e1) (annotatedCodeOpenCode e2)
+      , annotatedCodeFloatingLets = mergeFloatingLets (annotatedCodeFloatingLets e1) (annotatedCodeFloatingLets e2)
+      , annotatedCodeFreeVars = annotatedCodeFreeVars e1 <> annotatedCodeFreeVars e2
+      }
+instance Syn.LiftDerived3 (AnnotatedCode code) where
+  liftDerived3 f e1 e2 e3 =
+    AnnotatedCode
+      { annotatedCodeOpenCode = f (annotatedCodeOpenCode e1) (annotatedCodeOpenCode e2) (annotatedCodeOpenCode e3)
+      , annotatedCodeFloatingLets = mergeFloatingLets (annotatedCodeFloatingLets e1) (mergeFloatingLets (annotatedCodeFloatingLets e2) (annotatedCodeFloatingLets e3))
+      , annotatedCodeFreeVars = annotatedCodeFreeVars e1 <> annotatedCodeFreeVars e2 <> annotatedCodeFreeVars e3
+      }
+instance Syn.Constantable c code => Syn.Constantable c (AnnotatedCode code)
+instance Syn.Instantiable code => Syn.Instantiable (AnnotatedCode code)
+instance Syn.IfThenElseable code => Syn.IfThenElseable (AnnotatedCode code)
+instance Syn.Equalable code => Syn.Equalable (AnnotatedCode code)
+
+-- * Type 'LetInserter'
+newtype LetInserter code a = LetInserter {unR :: MT.State Natural (AnnotatedCode code a)}
+runLetInserter :: Syn.Letable code => LetInserter code a -> code a
+runLetInserter = runCD . (`MT.evalState` 0) . unR
+
+type instance Syn.Derived (LetInserter code) = AnnotatedCode code
+instance Syn.LiftDerived (LetInserter sem) where
+  liftDerived = LetInserter . return
+instance Syn.LiftDerived1 (LetInserter sem) where
+  liftDerived1 f e1 = LetInserter (f <$> unR e1)
+instance Syn.LiftDerived2 (LetInserter sem) where
+  liftDerived2 f e1 e2 = LetInserter (liftA2 f (unR e1) (unR e2))
+instance Syn.LiftDerived3 (LetInserter sem) where
+  liftDerived3 f e1 e2 e3 = LetInserter (liftA3 f (unR e1) (unR e2) (unR e3))
+instance Syn.IfThenElseable code => Syn.IfThenElseable (LetInserter code)
+instance Syn.Equalable code => Syn.Equalable (LetInserter code)
+instance Syn.Constantable c code => Syn.Constantable c (LetInserter code)
+instance Syn.Instantiable code => Syn.Instantiable (OpenCode code)
+
+freshNatural :: MT.State Natural Natural
+freshNatural = do
+  n <- MT.get
+  MT.put (Prelude.succ n)
+  return n
+
+-- type instance Syn.Derived (LetInserter sem) = Syn.Derived sem
+freshVar :: MT.State Natural (Var a)
+freshVar = Var . Locus <$> freshNatural
+
+-- instance Syn.Constantable c sem => Syn.Constantable c (LetInserter sem) where
+--   constant c = LetInserter $ pure $ Syn.constant c
+instance Syn.Instantiable sem => Syn.Instantiable (LetInserter sem)
+
+-- f .@ x = LetInserter $ Syn.liftDerived2 (Syn..@) (unR f) (unR x)
+-- instance Syn.Abstractable sem => Syn.Abstractable (LetInserter sem) where
+--   lam f = LetInserter $ \loc ->
+--       Syn.lam $ \x ->
+--       unR (f (LetInserter (\_loc -> x)))
+--         (PNote "lam":loc)
+-- instance Syn.Abstractable code => Syn.Abstractable (LetInserter (AnnotatedCode code)) where
+--   lam body = LetInserter $ \loc ->
+--     AnnotatedCode
+--       { annotatedCodeOpenCode = _e
+--       }
+--     -- \x -> unR (body (LetInserter (\_ -> x))) (PNote "lam":loc)
+-- instance Syn.Letable (LetInserter sem) where
+--   let_ exp body = LetInserter $ \loc ->
+--     let x = unR exp (POne:loc) in
+--     unR (body (LetInserter (\_loc -> x))) (PTwo:loc)
+
+-- instance Syn.Letable code => Syn.Letable (LetInserter code) where
+--  let_ e body = LetInserter $ \loc ->
+--    let x = unR e (POne:loc) in
+--    unR (body (LetInserter (\_loc -> x))) (PTwo:loc)
+
+-- instance Syn.Letable (LetInserter sem) where
+--  let_ exp body = LetInserter (\loc -> let x = unR exp (POne:loc) in unR (body (LetInserter (\_loc -> x))) (PTwo:loc))
+-- instance Syn.IfThenElseable code => Syn.IfThenElseable (LetInserter (AnnotatedCode code)) where
+--  ifThenElse =
+--    Syn.liftDerived3 $ \bC okC koC -> OpenCode $ \env ->
+--      Syn.ifThenElse (unOpenCode bC env) (unOpenCode okC env) (unOpenCode koC env)
+-- instance Syn.FromDerived Syn.Equalable sem => Syn.Equalable (LetInserter sem) where
+--   equal = LetInserter (\loc -> trace ("equal "<>show loc) $ Syn.equal)
+
+-- | Check to see there are no duplicate 'VarName's in 'VLBindings'.
+-- This is just a safety check
+check_no_dup_varname :: FloatingLets code -> a -> a
+check_no_dup_varname = loop []
+  where
+    loop :: [VarName] -> FloatingLets code -> a -> a
+    loop _found (FloatingLets []) = id
+    loop found (FloatingLets (FloatingLet{floatingLetVarName = Var v, ..} : t))
+      | v `List.elem` found =
+          error ("Duplicate varname " <> show v <> ", with locus " <> show floatingLetLocus)
+      | otherwise = loop (v : found) (FloatingLets t)
+
+-- | let-bind all vlbindings with the given locus.
+-- This is a non-recursive case. All these let-bindings are independent
+-- It is up to the user to ensure the independence: if the bindings
+-- are dependent, the user should have introduced several loci
+-- Virtual let bindings are sort of free variables.
+-- We bind free variables later. Likewise, we bind let-bindings later.
+-- 'Memo' is ignored here.
+-- class GenLetable sem where
+-- instance Syn.Letable code => GenLetable (LetInserter (AnnotatedCode code)) where
+floatingLetVarname :: FloatingLet code -> VarName
+floatingLetVarname FloatingLet{floatingLetVarName = Var v} = v
+
+instance
+  ( Syn.Abstractable code
+  , Syn.Letable code
+  , Syn.LetRecable Int code
+  ) =>
+  Syn.Abstractable (LetInserter code)
+  where
+  lam body = LetInserter $ do
+    vname <- freshVar
+    unR $
+      Syn.liftDerived1
+        ( \bodyCD ->
+            let (bindingFLS, passingFLS) =
+                  List.partition
+                    (\fl -> Set.member (unVar vname) (floatingLetFreeVars fl))
+                    (unFloatingLets (annotatedCodeFloatingLets bodyCD))
+            in let bindingLFSByMemo = groupBy ((==) `on` floatingLetMemo) bindingFLS
+               in traceShow
+                    ( "lam"
+                    , ("vname", unVar vname)
+                    , ("bindingFLS", FloatingLets bindingFLS)
+                    , ("passingFLS", FloatingLets passingFLS)
+                    , ("bodyFreeVars", annotatedCodeFreeVars bodyCD)
+                    , ("annotatedCodeFloatingLets bodyCD", annotatedCodeFloatingLets bodyCD)
+                    )
+                    $ Syn.liftDerived1
+                      ( \bodyOC ->
+                          OpenCode $ \env ->
+                            Syn.lam $ \x ->
+                              (`unOpenCode` insertVar vname x env) $
+                                List.foldr bindFloatingLetMemo bodyOC bindingLFSByMemo
+                      )
+                      bodyCD
+                        { annotatedCodeFloatingLets = FloatingLets passingFLS
+                        , annotatedCodeFreeVars =
+                            Set.delete (unVar vname) $
+                              annotatedCodeFreeVars bodyCD
+                                `Set.difference` Set.fromList (floatingLetVarname <$> bindingFLS)
+                        }
+        )
+        (body (lookupVarLetInserter vname))
+instance
+  ( Syn.Letable code
+  , Syn.LetRecable Int code
+  ) =>
+  Syn.Letable (LetInserter code)
+  where
+  let_ expR body = LetInserter $ do
+    vname <- freshVar
+    unR $
+      Syn.liftDerived2
+        ( \expCD bodyCD ->
+            let (bindingFLS, passingFLS) =
+                  List.partition
+                    (\fl -> Set.member (unVar vname) (floatingLetFreeVars fl))
+                    (unFloatingLets (annotatedCodeFloatingLets bodyCD))
+            in let bindingLFSByMemo = groupBy ((==) `on` floatingLetMemo) bindingFLS
+               in Syn.liftDerived2
+                    ( \expOC bodyOC ->
+                        OpenCode $ \env ->
+                          Syn.let_ (unOpenCode expOC env) $ \x ->
+                            (`unOpenCode` (insertVar vname x env)) $
+                              List.foldr bindFloatingLetMemo bodyOC bindingLFSByMemo
+                    )
+                    expCD
+                    bodyCD
+                      { annotatedCodeFloatingLets = FloatingLets passingFLS
+                      , annotatedCodeFreeVars =
+                          Set.delete (unVar vname) $
+                            annotatedCodeFreeVars bodyCD
+                              `Set.difference` Set.fromList (floatingLetVarname <$> bindingFLS)
+                      }
+        )
+        expR
+        (body (lookupVarLetInserter vname))
+
+bindLet :: Syn.Letable code => FloatingLet code -> OpenCode code a -> OpenCode code a
+bindLet fl2@FloatingLet{..} c =
+  traceShow ("bindLet", fl2) $
+    case floatingLetBoundCode of
+      BoundCodeOpenCode oc -> OpenCode $ \env ->
+        Syn.let_ (unOpenCode oc env) $ \x ->
+          unOpenCode c $
+            -- traceShow ("bindLet", floatingLetVarName) $
+            insertVar floatingLetVarName x env
+      _ -> error "bindLet is always called for evaluated bindings, without latent bound exp"
+
+-- instance Syn.LetRecable idx (LetInserter sem) where
+--  letRec _idx f body =
+--    let self idx = LetInserter (\loc ->
+--          --trace ("letRec "<>show loc) $
+--          unR (f self idx) (PNote "fix":loc))
+--    in body self
+
+-- mletRec :: p -> ((t1 -> LetInserter sem a) -> t1 -> LetInserter sem a) -> ((t1 -> LetInserter sem a) -> t2) -> t2
+-- mletRec _idx f body =
+--   let self idx = LetInserter $
+--         --trace ("letRec "<>show loc) $
+--         unR (f self idx)
+--   in body self
+--
+-- letR ::
+--   Syn.LetRecable Int sem =>
+--   (sem a -> sem a) ->
+--   (sem a -> sem w) ->
+--   sem w
+-- letR f body = Syn.letRec (1::Int)
+--   (\self _idx -> f (self 0))
+--   (\self -> body (self 0))
+-- type family MemoKey (sem:: Type -> Type) where MemoKey a = Memo
+
+-- | Memoizing non-recursive genLet
+class MemoGenLetable sem where
+  withLocus :: (Locus -> sem a) -> sem a
+  genLetMemoAtMaybe :: Maybe Memo -> Maybe Locus -> sem a -> sem a
+
+genLetMemo :: MemoGenLetable sem => Memo -> Locus -> sem a -> sem a
+genLetMemo m l = genLetMemoAtMaybe (Just m) (Just l)
+
+genLet :: MemoGenLetable sem => sem a -> sem a
+genLet = genLetMemoAtMaybe Nothing Nothing
+
+genLetAt :: MemoGenLetable sem => Locus -> sem a -> sem a
+genLetAt = genLetMemoAtMaybe Nothing . Just
+
+instance Syn.Letable code => MemoGenLetable (LetInserter code) where
+  withLocus body = LetInserter $ do
+    lname <- Locus <$> freshNatural
+    bodyCD <- unR (body lname)
+    let fls = annotatedCodeFloatingLets bodyCD
+    let bindingByLocusFLS = List.filter (\fl -> floatingLetLocus fl == Just lname) (unFloatingLets fls)
+    let bindingVars = foldMap (Set.singleton . floatingLetVarname) bindingByLocusFLS
+    let (bindingFLS, passingFLS) = List.partition (\fl -> not (Set.disjoint bindingVars (floatingLetFreeVars fl))) (unFloatingLets fls)
+    let bindingLFSByMemo = groupBy ((==) `on` floatingLetMemo) bindingFLS
+    return $
+      traceShow (["withLocus"], ("bindingLFSByMemo", bindingLFSByMemo)) $
+        -- check_no_dup_varname (annotatedCodeFloatingLets bodyCD) $
+        AnnotatedCode
+          { annotatedCodeOpenCode = List.foldr bindFloatingLetMemo (annotatedCodeOpenCode bodyCD) bindingLFSByMemo
+          , annotatedCodeFloatingLets = FloatingLets passingFLS
+          , annotatedCodeFreeVars = annotatedCodeFreeVars bodyCD `Set.difference` Set.fromList (floatingLetVarname <$> bindingFLS)
+          }
+  genLetMemoAtMaybe keyMaybe mlname body = LetInserter $ traceShow (["genLetMemoAtMaybe"]) $ do
+    vname <- freshVar
+    -- mkey <- runIdentity <$> unR key
+    bodyCD <-
+      traceShow ("genLetMemoAtMaybe", ("vname", vname), ("key", keyMaybe)) $
+        unR body
+    let fvs = Set.insert (unVar vname) (annotatedCodeFreeVars bodyCD)
+    -- if the bodyression to let-bind has itself let-bindings,
+    -- they are straightened out
+    -- The new binding is added at the end; it may depend on
+    -- the bindings in evl (as is the case for the nested genLet)
+    return $
+      AnnotatedCode
+        { annotatedCodeOpenCode = lookupVar vname
+        , annotatedCodeFloatingLets =
+            FloatingLets $
+              unFloatingLets (annotatedCodeFloatingLets bodyCD)
+                <> [ FloatingLet
+                      { floatingLetLocus = mlname
+                      , floatingLetMemo = MaybeMemo keyMaybe
+                      , floatingLetVarName = vname
+                      , floatingLetBoundCode = BoundCodeOpenCode (annotatedCodeOpenCode bodyCD)
+                      , floatingLetFreeVars = fvs
+                      }
+                   ]
+        , annotatedCodeFreeVars = fvs
+        }
+
+-- Bind the variables in order, preserving the dependency.
+-- Bind only one variable within each group, substituting the others
+bindFloatingLetMemo :: Syn.Letable code => [FloatingLet code] -> OpenCode code a -> OpenCode code a
+bindFloatingLetMemo gr oc =
+  traceShow (["withLocus", "bindFloatingLetMemo"]) $
+    case assertSameLocuses gr of
+      [] -> error "bindFloatingLetMemo: empty group"
+      [fl] -> bindLet fl oc
+      fl@FloatingLet{floatingLetVarName = vName} : fls ->
+        bindLet fl $ OpenCode $ \env ->
+          let defCode = unOpenCode (lookupVar vName) env
+          in let ext_equ env' fl2 =
+                  reBind2 (memKey fl) defCode fl2
+                    : env'
+             in unOpenCode oc $ List.foldl' ext_equ env fls
+  where
+    memKey fl@FloatingLet{floatingLetBoundCode = BoundCodeOpenCode{}} = floatingLetMemo fl
+    memKey fl = error $ "group pust be canonical: " <> show fl
+
+assertSameLocuses :: [FloatingLet code] -> [FloatingLet code]
+assertSameLocuses fls
+  | Set.size (foldMap (Set.singleton . floatingLetLocus) fls) <= 1 = traceShow ("assertSameLocuses", fls) fls
+  | otherwise = error "assertSameLocuses"
+
+-- | Especially for 'Locus', all bindings have the same type.
+-- In any event, 'Locus' plus the 'Memo' key witness for the bindings' type.
+reBind2 :: MaybeMemo -> code a -> FloatingLet sem -> EnvBind code
+reBind2 k c fl
+  | k == floatingLetMemo fl =
+      traceShow (["reBind2"], ("k", k), ("fl", fl)) $
+        EnvBind (Var (floatingLetVarname fl), c)
+reBind2 k _ fl = error $ "reBind2: locus/memo mismatch" <> show (("k", k), ("fl", fl))
+
+{-
+genLetAt :: LetInserter code a -> LetInserter code a
+genLetAt = LetInserter $
+  k <- freshKey
+  genLetMemo
+-}
+-- TODO: Here genlet is a version of genlet l em e described earlier, for the case of all memo keys being distinct.
+-- TODO: newtype LocusRec = Locus
+{-
+genLetM = LetInserter $ \loc ->
+  k <- freshKey
+  genLetMemo Nothing k
+-}
+
+-- | Unlike withLocus, we now have to evaluate expressions that
+-- may be present in virtual bindings.
+-- The rule: within an equivalence class, we evaluate only
+-- the bexp of its representative (the first member of the group)
+-- That evaluation may create more virtual bindings, which are to be merged
+
+-- Pick vlbindings for the given locus and convert them to
+
+-- * normal* equivalence classes.
+
+-- Return the list of normal equivalence classes and the
+-- remaining bindings (for different loci).
+-- An equivalence class is called normal if its representative
+-- (the first binding in the group) has the BoundCodeOpenCode as the bound
+-- expression (that is, it is determined which future-stage expression to bind).
+-- If a group is not normal, that is, its representative has a not
+-- yet determined bound expression, we have to determine it --
+-- which creates more bindings to normalize (some of which could be
+-- members of existing groups -- but never replace the existing
+-- representative -- we always preserve the order!)
+--
+-- Keeping the correct order and maintain dependency is actually
+-- very difficult. When evaluating the expression bound to var v,
+-- we obtain a code value with vlbindings. Some of them mention v
+-- (and so should be put into the same group as v, but _after_ it),
+-- some are new and so should be put in a group _before_ v because
+-- the v binding may depend on them.
+-- It seems simpler to just forget about the order between groups
+-- and dependencies and generate one big letrec with mutually dependent clauses.
+
+-- | @('groupBy' eq xs)@: partition a list @xs@
+-- into a list of equivalence classes quotiented by the @eq@ function
+--
+-- The order of the groups follows the order in the input list;
+-- within each group, the elements occur in the same order they
+-- do in the input list.
+groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
+groupBy eq = go []
+  where
+    go acc [] = List.reverse acc
+    go acc (x : xs) = go ((x : grp) : acc) rest
+      where
+        (grp, rest) = List.partition (eq x) xs
+
+normalizeGroup :: Locus -> FloatingLets code -> MT.State Natural ([FloatingLets code], FloatingLets code)
+normalizeGroup lname vl = do
+  let (bindingFLS, passingFLS) = List.partition (\FloatingLet{..} -> floatingLetLocus == Just lname) (unFloatingLets vl)
+  let bindingLFSByMemo = groupBy ((==) `on` floatingLetMemo) bindingFLS
+  -- Try to find an abnormal group
+  let (normalGroups, abnormalGroups) =
+        let abnomality_check = \case
+              FloatingLet{floatingLetBoundCode = BoundCodeLetInserter{}} : _ -> True
+              _ -> False
+        in List.break abnomality_check bindingLFSByMemo
+  -- traceShow (["normalizeGroup"], ("lname",lname), ("bindingLFSByMemo", FloatingLets <$> bindingLFSByMemo), ("normalGroups", FloatingLets <$> normalGroups), ("abnormalGroups", FloatingLets <$> abnormalGroups), ("passingFLS", FloatingLets passingFLS)) $
+  case abnormalGroups of
+    -- all groups are normal
+    [] ->
+      return $
+        traceShow (["normalizeGroup", "return"], ("lname", lname), ("normalGroups", FloatingLets <$> normalGroups), ("passingFLS", passingFLS)) $
+          (FloatingLets <$> normalGroups, FloatingLets passingFLS)
+    -- at least one group representative is abnormal
+    (fl0@FloatingLet{floatingLetBoundCode = BoundCodeLetInserter fl0Exp, floatingLetVarName = fl0VarName} : fls0) : grps -> do
+      fl0BodyCD <- unR fl0Exp
+      -- The group has become normal
+      let normalizedFL0 =
+            FloatingLet
+              { floatingLetLocus = floatingLetLocus fl0
+              , floatingLetMemo = floatingLetMemo fl0
+              , floatingLetVarName = fl0VarName
+              , floatingLetBoundCode = BoundCodeOpenCode (annotatedCodeOpenCode fl0BodyCD)
+              , -- Update the free variables, which could not be done in 'genLetMemoRec'
+                -- where fl0BodyCD was not yet available.
+                floatingLetFreeVars = floatingLetFreeVars fl0 <> annotatedCodeFreeVars fl0BodyCD
+              }
+              : fls0
+      let groups = normalGroups <> (normalizedFL0 : grps)
+      -- New bindings
+      -- let vl' = FloatingLets (List.concat groups) <> FloatingLets passingFLS <> annotatedCodeFloatingLets fl0BodyCD
+      let vl' = FloatingLets $ List.concat groups <> passingFLS <> unFloatingLets (annotatedCodeFloatingLets fl0BodyCD)
+      normalizeGroup lname vl'
+    _ -> error "normalizeGroup: groups are always non-empty"
+
+-- * Class 'MemoGenLetRecable'
+class MemoGenLetRecable sem where
+  withLocusRec :: (Locus -> sem a) -> sem a
+  genLetMemoRec :: Locus -> Memo -> sem a -> sem a
+
+instance
+  Syn.LetRecable Int code =>
+  MemoGenLetRecable (LetInserter code)
+  where
+  withLocusRec body = LetInserter $ do
+    lname <- Locus <$> freshNatural
+    bodyCD <- unR $ body lname
+    (bindingFLS, passingFLS) <-
+      check_no_dup_varname (annotatedCodeFloatingLets bodyCD) $
+        normalizeGroup lname (annotatedCodeFloatingLets bodyCD)
+    -- Bind the variables in order, preserving the dependency
+    return $
+      traceShow (["withLocusRec", "return"], ("lname", lname), ("bindingFLS", bindingFLS), ("passingFLS", passingFLS)) $
+        AnnotatedCode
+          { annotatedCodeOpenCode = bindFloatingLetMemoRec bindingFLS (annotatedCodeOpenCode bodyCD)
+          , annotatedCodeFloatingLets = passingFLS
+          , annotatedCodeFreeVars = annotatedCodeFreeVars bodyCD `Set.difference` foldMap (Set.fromList . (floatingLetVarname <$>) . unFloatingLets) bindingFLS
+          }
+  genLetMemoRec lname key body = LetInserter $ do
+    vname <- freshVar
+    -- Cannot compute all the free variables yet
+    -- because body is not yet evaluated to a 'AnnotatedCode'
+    let fvs = Set.singleton (unVar vname)
+    return $
+      traceShow (["genLetMemoRec"], ("key", key), ("vname", vname)) $
+        AnnotatedCode
+          { annotatedCodeOpenCode = lookupVar vname
+          , annotatedCodeFloatingLets =
+              FloatingLets
+                [ FloatingLet
+                    { floatingLetLocus = Just lname
+                    , floatingLetMemo = MaybeMemo (Just key)
+                    , floatingLetVarName = vname
+                    , floatingLetBoundCode = BoundCodeLetInserter body
+                    , floatingLetFreeVars = fvs
+                    }
+                ]
+          , annotatedCodeFreeVars = fvs
+          }
+
+-- | Create mutually-recursive letrec bindings.
+-- All variables are bound at the same time.
+-- The binding expression is taken from the group representative.
+-- It has to be 'BoundCodeOpenCode': all groups assumed to be normal.
+bindFloatingLetMemoRec ::
+  Syn.LetRecable Int code =>
+  [FloatingLets code] ->
+  OpenCode code a ->
+  OpenCode code a
+bindFloatingLetMemoRec [] c = c
+bindFloatingLetMemoRec bindingFLS c =
+  traceShow (["bindFloatingLetMemoRec", "OpenCode"], ("bindingFLS", bindingFLS)) $
+    OpenCode $ \env ->
+      traceShow (["bindFloatingLetMemoRec", "Syn.letRec"], ("bindingFLS", bindingFLS), ("env", env)) $
+        Syn.letRec
+          len
+          ( \self idx ->
+              traceShow (["bindFloatingLetMemoRec", "Syn.letRec", "binds"], ("idx", idx)) $
+                ( traceShow (["bindFloatingLetMemoRec", "Syn.letRec", "binds", "List.!!"], ("len", len), ("idx", idx), ("env", env)) $
+                    unOpenCode (tobindOC List.!! idx)
+                )
+                  ( traceShow (["bindFloatingLetMemoRec", "Syn.letRec", "binds", "bind_all"], ("idx", idx), ("env", env)) $
+                      bind_all self env
+                  )
+          )
+          ( \self ->
+              traceShow (["bindFloatingLetMemoRec", "Syn.letRec", "end"], ("env", env)) $
+                unOpenCode c (bind_all self env)
+          )
+  where
+    len = List.length bindingFLS
+    -- (label,key) of group representative
+    lk (fl@FloatingLet{floatingLetBoundCode = BoundCodeOpenCode{}} : _) = floatingLetMemo fl
+    lk _ = error "groups must be non-empty and canonical"
+    bind_group :: code a -> FloatingLets code -> [EnvBind code]
+    -- Create bindings to bind all identifiers in a group
+    bind_group ec (FloatingLets grp) =
+      traceShow (["bindFloatingLetMemoRec", "bind_group"], ("grp", grp)) $
+        List.map (reBind2 (lk grp) ec) (assertSameLocuses grp)
+    bind_all self env =
+      traceShow (["bindFloatingLetMemoRec", "bind_all"], ("bindingFLS", bindingFLS), ("env", env)) $
+        List.concat $
+          List.zipWith (\grp i -> bind_group (self i) grp) bindingFLS [0 :: Int .. pred len] <> [env]
+    tobindOC =
+      traceShow (["bindFloatingLetMemoRec", "tobindOC"], ("bindingFLS", bindingFLS)) $
+        List.map
+          ( \case
+              -- TODO: avoid unwrapping
+              FloatingLets (fl@FloatingLet{floatingLetBoundCode = BoundCodeOpenCode oc} : _) ->
+                traceShow (["bindFloatingLetMemoRec", "tobindOC", "map"], fl) $
+                  unsafeCoerce (oc)
+              _ -> error "tobindOC"
+          )
+          bindingFLS
+
+{-
+genLetLocus :: Syn.Letable code => (Locus -> LetInserter code a) -> LetInserter code a
+genLetLocus body = LetInserter $ do
+  lname <- Locus <$> freshNatural
+  bodyCD <-
+    traceShow ("genLetLocus", ("lname", lname)) $
+    unR (body lname)
+  let (bindingFLS, passingFLS) =
+        List.partition (\fl -> floatingLetLocus fl == Just lname)
+                       (unFloatingLets (annotatedCodeFloatingLets bodyCD))
+  -- Bind the variables in order, preserving the dependency
+  --traceShow ("genLetLocus", (bindingFLS, passingFLS)) $
+  return $
+    check_no_dup_varname (annotatedCodeFloatingLets bodyCD) $
+    AnnotatedCode{ annotatedCodeOpenCode = List.foldr bindLet (annotatedCodeOpenCode bodyCD) bindingFLS
+      , annotatedCodeFloatingLets = FloatingLets passingFLS
+      , annotatedCodeFreeVars = annotatedCodeFreeVars bodyCD `Set.difference` Set.fromList (floatingLetVarname <$> bindingFLS)
+      }
+-- In MetaOCaml, let(rec) is actually inserted either at the place
+-- of the explicit let locus,
+-- or at the binding that dominates all free variables of the bound expression,
+-- whichever has the narrowest scope.
+genLetAtMaybe :: Maybe Locus -> LetInserter code a -> LetInserter code a
+genLetAtMaybe mlname body = LetInserter $ do
+  vname <- freshVar
+  bodyCD <-
+        traceShow ("genLet", ("vname", vname)) $
+        unR body
+  -- if the expression to let-bind has itself let-bindings,
+  -- they are straightened out
+  -- The new binding is added at the end; it may depend on
+  -- the bindings coming before (as is the case for the nested genLet)
+  return $
+    traceShow (["genLet", "bodyCD"], ("vname", vname), ("bodyCDFreeVars", (annotatedCodeFreeVars bodyCD))) $
+    AnnotatedCode
+      { annotatedCodeOpenCode = traceShow (["genLet", "lookupVar"], ("vname", vname)) $ lookupVar vname
+      , annotatedCodeFloatingLets = FloatingLets $
+          --(\vls -> traceShow (["genLet", "bindings"], vls) vls) $
+          unFloatingLets (annotatedCodeFloatingLets bodyCD) <>
+          [ FloatingLet
+              { floatingLetLocus = mlname
+              , floatingLetMemo = MaybeMemo (Just 0)
+              , floatingLetVarName = vname
+              , floatingLetBoundCode = BoundCodeOpenCode (annotatedCodeOpenCode bodyCD)
+              , floatingLetFreeVars = Set.insert (unVar vname) (annotatedCodeFreeVars bodyCD)
+              }
+          ]
+      , annotatedCodeFreeVars = Set.insert (unVar vname) $ annotatedCodeFreeVars bodyCD
+      }
+genLetAt :: Locus -> LetInserter code a -> LetInserter code a
+genLetAt = genLetAtMaybe . Just
+
+genLet :: LetInserter code a -> LetInserter code a
+genLet = genLetAtMaybe Nothing
+-}
index e48c1f2c0bd1067f43ff6ed2684bdb500c28ba30..74f2e81abd7ce0a913446ba3db47f30bb387f3a8 100644 (file)
@@ -7,15 +7,19 @@
 
 -- | This module provides the 'Viewer' semantic
 -- which interprets combinators as human-readable text.
+-- However there is no wrapping nor indenting.
 module Symantic.Semantics.Viewer where
 
 import Data.Function qualified as Fun
+import Data.Int (Int)
+import Data.List qualified as List
 import Data.String
 import Numeric.Natural (Natural)
-import Prelude qualified
 import Text.Show
+import Prelude qualified
 
 import Symantic.Semantics.Data
+import Symantic.Semantics.LetInserter
 import Symantic.Semantics.Viewer.Fixity
 import Symantic.Syntaxes.Classes
 import Symantic.Syntaxes.Derive
@@ -27,17 +31,28 @@ data Viewer a where
   ViewerInfix :: Infix -> String -> String -> Viewer (a -> b -> c)
   ViewerApp :: Viewer (b -> a) -> Viewer b -> Viewer a
 
-view :: Viewer a -> ViewerEnv -> ShowS
-view (Viewer v) env = v env
-view (ViewerInfix _op name _infixName) _env = showString name
-view (ViewerUnifix _op name _unifixName) _env = showString name
-view (ViewerApp f x) env =
+view :: Viewer a -> String
+view v =
+  runView
+    v
+    ViewerEnv
+      { viewerEnvOp = (infixN 0, SideL)
+      , viewerEnvPair = pairParen
+      , viewerEnvLamDepth = 1
+      }
+    ""
+
+runView :: Viewer a -> ViewerEnv -> ShowS
+runView (Viewer v) env = v env
+runView (ViewerInfix _op name _infixName) _env = showString name
+runView (ViewerUnifix _op name _unifixName) _env = showString name
+runView (ViewerApp f x) env =
   pairViewer env op Fun.$
-    view f env{viewerEnvOp = (op, SideL)}
+    runView f env{viewerEnvOp = (op, SideL)}
       Fun.. showString " "
-      Fun.. view x env{viewerEnvOp = (op, SideR)}
+      Fun.. runView x env{viewerEnvOp = (op, SideR)}
   where
-    op = infixN 10
+    op = infixL 10
 
 -- | Unusual, but enables to leverage default definition of methods.
 type instance Derived Viewer = Viewer
@@ -48,14 +63,14 @@ instance LiftDerived Viewer where
 instance IsString (Viewer a) where
   fromString s = Viewer Fun.$ \_env -> showString s
 instance Show (Viewer a) where
-  showsPrec p =
-    ( `view`
-        ViewerEnv
-          { viewerEnvOp = (infixN p, SideL)
-          , viewerEnvPair = pairParen
-          , viewerEnvLamDepth = 1
-          }
-    )
+  showsPrec p =
+    runView
+      v
+      ViewerEnv
+        { viewerEnvOp = (infixN p, SideL)
+        , viewerEnvPair = pairParen
+        , viewerEnvLamDepth = 1
+        }
 instance Show (SomeData Viewer a) where
   showsPrec p (SomeData x) = showsPrec p (derive x :: Viewer a)
 
@@ -78,45 +93,93 @@ instance Abstractable Viewer where
   lam f = Viewer Fun.$ \env ->
     pairViewer env op Fun.$
       let x = showString "x" Fun.. shows (viewerEnvLamDepth env)
-       in showString "\\"
-            Fun.. x
-            Fun.. showString " -> "
-            Fun.. view
-              (f (Viewer (\_env -> x)))
-              env
-                { viewerEnvOp = (op, SideL)
-                , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
-                }
+      in showString "\\"
+          Fun.. x
+          Fun.. showString " -> "
+          Fun.. runView
+            (f (Viewer (\_env -> x)))
+            env
+              { viewerEnvOp = (op, SideL)
+              , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
+              }
     where
-      op = infixN 0
+      op = infixL 1
+instance Letable Viewer where
+  let_ x f = Viewer Fun.$ \env ->
+    pairViewer env op Fun.$
+      let l = showString "x" Fun.. shows (viewerEnvLamDepth env)
+      in showString "let "
+          Fun.. l
+          Fun.. showString " = "
+          Fun.. runView
+            x
+            env
+              { viewerEnvOp = (infixN 0, SideL)
+              , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
+              }
+          Fun.. showString " in "
+          Fun.. runView
+            (f (Viewer (\_env -> l)))
+            env
+              { viewerEnvOp = (infixN 0, SideL)
+              , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
+              }
+    where
+      op = infixL 1
+instance LetRecable Int Viewer where
+  letRec len f body = Viewer Fun.$ \env ->
+    let fns =
+          [ showString "u" Fun.. shows (viewerEnvLamDepth env Prelude.+ Prelude.fromIntegral idx)
+          | idx <- [0 .. len Prelude.- 1]
+          ]
+    in let self idx = Viewer Fun.$ \_env -> fns List.!! idx
+       in let lvs = List.zipWith (\v idx -> (v, f self idx)) fns [0 .. len Prelude.- 1]
+          in pairViewer env op Fun.$
+              showString "letRec "
+                Fun.. showListWith
+                  ( \(lhs, rhs) ->
+                      lhs
+                        Fun.. showString " = "
+                        Fun.. runView
+                          rhs
+                          env
+                            { viewerEnvOp = (infixN 0, SideL)
+                            , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
+                            }
+                  )
+                  lvs
+                Fun.. showString " in "
+                Fun.. runView
+                  (body self)
+                  env
+                    { viewerEnvOp = (infixN 0, SideL)
+                    , viewerEnvLamDepth = viewerEnvLamDepth env Prelude.+ Prelude.fromIntegral len
+                    }
+    where
+      op = infixN 10
 instance Abstractable1 Viewer where
   lam1 f = Viewer Fun.$ \env ->
     pairViewer env op Fun.$
       let x = showString "u" Fun.. shows (viewerEnvLamDepth env)
-       in showString "\\"
-            Fun.. x
-            Fun.. showString " -> "
-            Fun.. view
-              (f (Viewer (\_env -> x)))
-              env
-                { viewerEnvOp = (op, SideL)
-                , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
-                }
+      in showString "\\"
+          Fun.. x
+          Fun.. showString " -> "
+          Fun.. runView
+            (f (Viewer (\_env -> x)))
+            env
+              { viewerEnvOp = (op, SideL)
+              , viewerEnvLamDepth = Prelude.succ (viewerEnvLamDepth env)
+              }
     where
       op = infixN 0
 instance Instantiable Viewer where
-  ViewerInfix op _name infixName .@ ViewerApp x y = Viewer Fun.$ \env ->
+  ViewerApp (ViewerInfix op _name infixName) x .@ y = Viewer Fun.$ \env ->
     pairViewer env op Fun.$
-      view x env{viewerEnvOp = (op, SideL)}
+      runView x env{viewerEnvOp = (op, SideL)}
         Fun.. showString " "
         Fun.. showString infixName
         Fun.. showString " "
-        Fun.. view y env{viewerEnvOp = (op, SideR)}
-  ViewerInfix op name _infixName .@ x = Viewer Fun.$ \env ->
-    showParen Prelude.True Fun.$
-      view x env{viewerEnvOp = (op, SideL)}
-        Fun.. showString " "
-        Fun.. showString name
+        Fun.. runView y env{viewerEnvOp = (op, SideR)}
   f .@ x = ViewerApp f x
 instance Unabstractable Viewer where
   ap = ViewerInfix (infixL 4) "(<*>)" "<*>"
@@ -147,11 +210,19 @@ instance Maybeable Viewer where
 instance IfThenElseable Viewer where
   ifThenElse test ok ko = Viewer Fun.$ \env ->
     pairViewer env op Fun.$
-    showString "if"
-      Fun.. view test env{viewerEnvOp = (op, SideL)}
-      Fun.. showString "then"
-      Fun.. view ok env{viewerEnvOp = (op, SideL)}
-      Fun.. showString "else"
-      Fun.. view ko env{viewerEnvOp = (op, SideL)}
+      showString "if "
+        Fun.. runView test env{viewerEnvOp = (op, SideL)}
+        Fun.. showString " then "
+        Fun.. runView ok env{viewerEnvOp = (op, SideL)}
+        Fun.. showString " else "
+        Fun.. runView ko env{viewerEnvOp = (op, SideL)}
     where
-    op = infixN 0
+      op = infixN 1
+
+{-
+instance MemoGenLetRecable Viewer where
+  group_normalize :: Locus -> Locus -> VLBindings sem -> ([VLBindings sem], VLBindings sem)
+  memoGenLetRecLocus :: (Locus -> sem a) -> sem a
+  memoGenLetRecLocus f = f
+  memoGenLetRec :: Locus -> MemoKey sem -> sem a -> sem a
+-}
index 6eec73e5748065967d3cafd3521a8610f4aad843..4dca539fa21c2a9cc5efc75d38e9b07569308084 100644 (file)
@@ -61,6 +61,38 @@ class Abstractable sem where
     (sem a -> sem b) ->
     sem (a -> b)
 
+-- ** Class Letable'
+class Letable sem where
+  let_ :: sem a -> (sem a -> sem b) -> sem b
+  let_ e b = liftDerived (let_ (derive e) (derive Fun.. b Fun.. liftDerived))
+  default let_ ::
+    FromDerived Letable sem =>
+    Derivable sem =>
+    sem a ->
+    (sem a -> sem b) ->
+    sem b
+
+-- *** Class 'LetRecable'
+class LetRecable idx sem where
+  letRec ::
+    idx ->
+    ({-self-} (idx -> sem a) -> idx -> sem a) ->
+    ({-self-} (idx -> sem a) -> sem w) ->
+    sem w
+  default letRec ::
+    FromDerived (LetRecable idx) sem =>
+    Derivable sem =>
+    idx ->
+    ({-self-} (idx -> sem a) -> idx -> sem a) ->
+    ({-self-} (idx -> sem a) -> sem w) ->
+    sem w
+  letRec idx f body =
+    liftDerived Fun.$
+      letRec
+        idx
+        (\self i -> derive (f (liftDerived Fun.. self) i))
+        (\self -> derive (body (liftDerived Fun.. self)))
+
 -- ** Class 'Abstractable1'
 class Abstractable1 sem where
   -- | Like 'lam' but whose argument must be used only once,
@@ -94,22 +126,29 @@ class Instantiable sem where
     sem b
 
 -- ** Class 'Unabstractable'
+
 -- | All operations in lambda calculus can be encoded
 -- via abstraction elimination into the SK calculus
 -- as binary trees whose leaves are one
--- of the three symbols S ('ap') and K ('const').
+-- of the two symbols S ('ap') and K ('const').
 class Unabstractable sem where
   -- | S
   ap :: sem ((a -> b -> c) -> (a -> b) -> a -> c)
+
   -- | K
   const :: sem (a -> b -> a)
+
   -- | I
   id :: sem (a -> a)
+
   -- | B
   (.) :: sem ((b -> c) -> (a -> b) -> a -> c)
+
   infixr 9 .
+
   -- | C
   flip :: sem ((a -> b -> c) -> b -> a -> c)
+
   ($) :: sem ((a -> b) -> a -> b)
   infixr 0 $
 
@@ -157,6 +196,19 @@ class Constantable c sem where
     c ->
     sem c
 
+bool :: Constantable Bool sem => Bool -> sem Bool
+bool = constant
+char :: Constantable Char sem => Char -> sem Char
+char = constant
+int :: Constantable Int sem => Int -> sem Int
+int = constant
+natural :: Constantable Natural sem => Natural -> sem Natural
+natural = constant
+string :: Constantable String sem => String -> sem String
+string = constant
+unit :: Constantable () sem => sem ()
+unit = constant ()
+
 -- * Class 'Eitherable'
 class Eitherable sem where
   either :: sem ((l -> a) -> (r -> a) -> Either l r -> a)
@@ -211,19 +263,6 @@ class Inferable a sem where
   default infer :: FromDerived (Inferable a) sem => sem a
   infer = liftDerived infer
 
-unit :: Inferable () sem => sem ()
-unit = infer
-bool :: Inferable Bool sem => sem Bool
-bool = infer
-char :: Inferable Char sem => sem Char
-char = infer
-int :: Inferable Int sem => sem Int
-int = infer
-natural :: Inferable Natural sem => sem Natural
-natural = infer
-string :: Inferable String sem => sem String
-string = infer
-
 -- * Class 'Listable'
 class Listable sem where
   cons :: sem (a -> [a] -> [a])
@@ -357,7 +396,7 @@ construct ::
   EoTOfRep a =>
   CurryN args =>
   Tuples args ~ EoT (ADT a) =>
-  (args ~ Args (args -..-> a)) =>
+  args ~ Args (args -..-> a) =>
   (args -..-> a) ->
   sem (Tuples args) ->
   sem a
index 0713d422e5f21bbd05f5434caf4a46fbbf2277cb..320d76c411a047b69fdbdc989a8eded97edecca3 100644 (file)
@@ -8,10 +8,10 @@ copyright:          Julien Moutinho <julm+symantic-base@sourcephile.fr>
 license:            AGPL-3.0-or-later
 license-file:       LICENSES/AGPL-3.0-or-later.txt
 
--- PVP:  +-+------- breaking API changes
---       | | +----- non-breaking API additions
---       | | | +--- code changes with no API change
-version:            0.5.0.20221211
+-- PVP:             +-+------- breaking API changes
+--                  | | +----- non-breaking API additions
+--                  | | | +--- code changes with no API change
+version:            0.5.1.20240520
 stability:          experimental
 category:           Data Structures
 synopsis:
@@ -27,9 +27,6 @@ extra-doc-files:    ChangeLog.md
 extra-source-files:
   .envrc
   cabal.project
-  flake.lock
-  flake.nix
-  Makefile
 
 extra-tmp-files:
 
@@ -37,29 +34,9 @@ source-repository head
   type:     git
   location: git://git.sourcephile.fr/haskell/symantic-base
 
-library
-  hs-source-dirs:     src
-  exposed-modules:
-    Symantic
-    Symantic.Semantics
-    Symantic.Semantics.Data
-    Symantic.Semantics.Forall
-    Symantic.Semantics.Identity
-    Symantic.Semantics.Reader
-    Symantic.Semantics.SharingObserver
-    Symantic.Semantics.Viewer
-    Symantic.Semantics.Viewer.Fixity
-    Symantic.Syntaxes
-    Symantic.Syntaxes.Classes
-    Symantic.Syntaxes.CurryN
-    Symantic.Syntaxes.Derive
-    Symantic.Syntaxes.EithersOfTuples
-    Symantic.Syntaxes.Reify
-    Symantic.Syntaxes.TuplesOfFunctions
-
+common boilerplate
   default-language:   Haskell2010
   default-extensions:
-    NoImplicitPrelude
     DefaultSignatures
     FlexibleContexts
     FlexibleInstances
@@ -68,6 +45,7 @@ library
     LambdaCase
     MultiParamTypeClasses
     NamedFieldPuns
+    NoImplicitPrelude
     RecordWildCards
     ScopedTypeVariables
     TupleSections
@@ -79,6 +57,7 @@ library
     -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates
     -Wpartial-fields -fprint-potential-instances
 
+common library-deps
   build-depends:
     , base                  >=4.10 && <5
     , containers
@@ -86,3 +65,54 @@ library
     , template-haskell
     , transformers
     , unordered-containers
+
+library
+  import:          boilerplate, library-deps
+  hs-source-dirs:  src
+  exposed-modules:
+    Symantic
+    Symantic.Semantics
+    Symantic.Semantics.Data
+    Symantic.Semantics.Forall
+    Symantic.Semantics.Identity
+    Symantic.Semantics.LetInserter
+    Symantic.Semantics.Reader
+    Symantic.Semantics.SharingObserver
+    Symantic.Semantics.Viewer
+    Symantic.Semantics.Viewer.Fixity
+    Symantic.Syntaxes
+    Symantic.Syntaxes.Classes
+    Symantic.Syntaxes.CurryN
+    Symantic.Syntaxes.Derive
+    Symantic.Syntaxes.EithersOfTuples
+    Symantic.Syntaxes.Reify
+    Symantic.Syntaxes.TuplesOfFunctions
+
+test-suite symantic-base-tests
+  import:             boilerplate, library-deps
+  type:               exitcode-stdio-1.0
+  hs-source-dirs:     tests
+  main-is:            Spec.hs
+  build-tool-depends: sydtest-discover:sydtest-discover
+  ghc-options:        -threaded -rtsopts -with-rtsopts=-N
+
+  -- ghc-prof-options: -fexternal-interpreter
+  -- ghc-options: -ddump-splices -ddump-to-file
+  other-modules:
+    Paths_symantic_base
+    Symantic.Semantics.LetInserterSpec
+    Symantic.Syntaxes.Extras
+
+  autogen-modules:    Paths_symantic_base
+  build-depends:
+    , genvalidity
+    , genvalidity-containers
+    , genvalidity-sydtest
+    , genvalidity-text
+    , genvalidity-time
+    , relude
+    , sydtest
+    , symantic-base
+    , validity
+    , validity-containers
+    , validity-text
diff --git a/tests/Spec.hs b/tests/Spec.hs
new file mode 100644 (file)
index 0000000..ebed7e1
--- /dev/null
@@ -0,0 +1 @@
+{-# OPTIONS_GHC -F -pgmF sydtest-discover #-}
diff --git a/tests/Symantic/Semantics/LetInserterSpec.hs b/tests/Symantic/Semantics/LetInserterSpec.hs
new file mode 100644 (file)
index 0000000..f0dde86
--- /dev/null
@@ -0,0 +1,511 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE TemplateHaskellQuotes #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+
+module Symantic.Semantics.LetInserterSpec where
+
+import Data.Function (($), (.))
+import Data.Functor.Identity (Identity (..))
+import Data.Int (Int)
+import Data.Maybe (Maybe (..))
+import Data.Semigroup (Semigroup (..))
+import Debug.Trace (trace)
+import Language.Haskell.TH.Syntax qualified as TH
+import Symantic.Semantics.LetInserter
+import Symantic.Semantics.Viewer
+import Symantic.Syntaxes.Classes (Abstractable (..), Constantable, Equalable (..), IfThenElseable (..), Instantiable (..), LetRecable (..), Letable (..), int, unit, (==))
+import Test.Syd
+import Test.Syd.Validity
+import Text.Show (show)
+import Prelude qualified as P
+
+import Symantic.Syntaxes.Extras
+
+-- import Debug.Trace (traceShow)
+
+p12 ::
+  Addable sem =>
+  Instantiable sem =>
+  Constantable Int sem =>
+  sem Int
+p12 = int 1 + int 2
+
+{-
+t1 ::
+  Abstractable sem =>
+  Instantiable sem =>
+  Multiplicable sem =>
+  Addable sem =>
+  Constantable Int sem =>
+  --GHC.Num.Num (sem Int) =>
+  sem Int -> sem Int
+-}
+t1 = (\x -> x * (x + int 1))
+c1 = t1 p12
+
+t2 x = let_ x (\y -> y * (y + int 1))
+c2 = t2 p12
+ft1 t x = lam (\u -> t x + t (x + u))
+c3 = ft1 t2 p12
+
+p12l loc = genLetAt loc (int 1 + int 2)
+c1l = withLocus \loc -> p12l loc
+
+ft11 loc = \t -> \x -> lam (\u -> t x + t (genLetAt loc (x + u)))
+c3l1 = withLocus \loc -> ft11 loc t1 (p12l loc)
+
+ft12 = \t -> \x -> lam (\u -> withLocus \loc -> t x + t (genLetAt loc (x + u)))
+c3l2 = withLocus \loc -> ft12 t1 (p12l loc)
+
+r12 :: Int
+r12 = runIdentity (runLetInserter p12)
+runLII = runIdentity . runLetInserter
+
+{-
+-- letrec (fun self m -> lam @@ fun n ->
+--   if_ (m =. int 0) (n + (int 1)) @@
+--      if_ (n =. int 0) (self / (m - int 1) / int 1) @@
+--          self / (m - int 1) / (self / m / (n - int 1))
+--   ) @@ fun ack -> ack / int 2
+fib =
+  letR
+    (\self ->
+      \n ->
+        ifThenElse (n == int 0) (log "n==0" (int 1)) $
+        ifThenElse (n == int 1) (log "n==1" (int 2)) $
+        self .@ int 2
+    )
+    (\res -> res)
+let sac2 = sack 2
+;;
+ack2 =
+  letR
+    (\self -> \m ->
+      lam $ \n ->
+        ifThenElse (m == int 0) (n + int 1) $
+        ifThenElse (n == int 0) (self .@ (m - int 1) .@ int 1) $
+        self .@ (m - int 1) .@ (self .@ m .@ (n - int 1))
+    )
+    (\res -> res .@ int 2)
+
+let fibonacci n =
+  let rec loop n (x,y) =·
+    if n = 0 then x else if n = 1 then y else·
+    .< .~(loop (n-1) (x,y)) + .~(loop (n-2) (x,y)) >.
+  in .< fun x y -> .~(loop n (.< x >. , .< y >.)) >.
+
+-}
+
+-- derive
+quote :: LetInserter (AnnotatedCode code) a -> LetInserter (AnnotatedCode code) a
+-- quote :: sem a -> a
+quote = P.id
+
+-- liftDerived
+unquote :: a -> sem a
+unquote = P.undefined
+
+-- unQ ::
+
+{-
+fibonacci ::
+  ( P.Eq (sem Int)
+  , P.Num (sem Int)
+  , Substractable sem
+  , Instantiable sem
+  ) =>
+  sem Int -> sem2 Int -> sem3 Int
+fibonacci ::
+  ( Addable code
+  , Abstractable (LetInserter (AnnotatedCode code))
+  , Instantiable code
+  ) => Int -> LetInserter (AnnotatedCode code) (Int -> Int -> Int)
+fibonacci m =
+  let loop n (x,y) =
+        if n P.== 0 then x else
+        if n P.== 1 then y else
+        quote (loop (n P.-1) (x, y) + loop (n P.-2) (x, y))
+  in quote (lam $ \x -> lam $ \y -> loop m (x, y))
+-}
+
+{-
+(* Specialized to m. Without memoization, it diverges *)
+let sack m =
+  with_locus_rec @@ fun l ->
+  let g = mkgenlet l (=) in
+  let rec loop m =
+    if m = 0 then .<fun n -> n + 1>. else
+    .<fun n -> if n = 0 then .~(g loop (m-1)) 1
+    else .~(g loop (m-1)) (.~(g loop m) (n-1))>.
+  in g loop m
+;;
+-}
+-- https://en.wikipedia.org/wiki/Ackermann_function
+-- Specialized to m. Without memoization, it diverges
+sack ::
+  Abstractable code =>
+  Addable code =>
+  Constantable Int code =>
+  Equalable code =>
+  IfThenElseable code =>
+  Instantiable code =>
+  LetRecable Int code =>
+  Letable code =>
+  Substractable code =>
+  Int ->
+  LetInserter code (Int -> Int)
+sack m0 =
+  withLocusRec $ \l ->
+    let
+      memoLoop k = genLetMemoRec l (P.fromIntegral k) (loop k)
+      loop 0 =
+        traceShow (["sack", "loop"], ("m", 0 :: Int)) $
+          lam $
+            \n -> n + int 1
+      loop m =
+        traceShow (["sack", "loop"], ("m", m)) $
+          lam $ \n ->
+            ifThenElse
+              (n == int 0)
+              (memoLoop (m P.- 1) .@ int 1)
+              (memoLoop (m P.- 1) .@ (memoLoop m .@ (n - int 1)))
+    in
+      memoLoop m0
+
+-- https://simple.wikipedia.org/wiki/Fibonacci_number
+fibonacci ::
+  Addable code =>
+  Abstractable code =>
+  Letable code =>
+  Instantiable code =>
+  LetRecable Int code =>
+  Int ->
+  LetInserter code (Int -> Int -> Int)
+fibonacci m =
+  withLocusRec $ \loc ->
+    let
+      memoLoop k = genLetMemoRec loc (P.fromIntegral k) (loop k)
+      loop n =
+        if n P.== 0
+          then lam $ \x -> lam $ \_y -> x
+          else
+            if n P.== 1
+              then lam $ \_x -> lam $ \y -> y
+              else lam $ \x -> lam $ \y ->
+                add
+                  .@ (memoLoop (n P.- 1) .@ x .@ y)
+                  .@ (memoLoop (n P.- 2) .@ x .@ y)
+    in
+      loop m
+
+-- (int -> int -> int) code =
+-- .< fun x_1 -> fun y_2 -> (((y_2 + x_1) + y_2) + (y_2 + x_1)) + ((y_2 + x_1) + y_2)>.
+tfib :: TH.Quote m => Int -> TH.Code m (Int -> Int -> Int)
+tfib m =
+  let loop n (x, y) =
+        if n P.== 0
+          then x
+          else
+            if n P.== 1
+              then y
+              else [||$$(loop (n P.- 1) (x, y)) P.+ $$(loop (n P.- 2) (x, y))||]
+  in [||\x y -> $$(loop m ([||x||], [||y||]))||]
+
+{-
+acs :: Int -> LetInserter (AnnotatedCode Viewer) (Int -> Int)
+acs m =
+  withLocusRec $ \l ->
+  letR
+    (\self -> --lam $ \mm ->
+      lam $ \n ->
+        --if (runIdentity m P.== 0) then (n + int 1) else
+        ifThenElse (n == int 0) n $
+          --genLetMemoRec l m
+          (self .@ int m) .@ (n - int 1)
+        --ifThenElse (m == int 0) (n + int 1) $
+        --ifThenElse (n == int 0) (self .@ (m - int 1) .@ int 1) $
+        --genLetMemo l m $ self .@ (m - int 1) .@ (self .@ m .@ (n - int 1))
+    )
+    (\self -> genLetMemoRec l (2) (self .@ int 2))
+-}
+ack :: Int -> Int -> Int
+ack 0 n = trace ("ack 0 " <> show n) $ n P.+ 1
+ack m 0 = trace ("ack " <> show m <> " 0") $ ack (m P.- 1) 1
+ack m n = trace ("ack " <> show n <> " " <> show m) $ ack (m P.- 1) (ack m (n P.- 1))
+
+spec :: Spec
+spec = do
+  describe "let_" do
+    it "can bind a literal" do
+      view (runLetInserter (let_ (int 3) (\v -> int 4 + v)))
+        `shouldBe` "let x1 = 3 in 4 + x1"
+    it "can bind an expression of literals" do
+      view (runLetInserter (let_ (int 1 + int 2) (\v -> int 4 + v)))
+        `shouldBe` "let x1 = 1 + 2 in 4 + x1"
+    it "can bind a variable" do
+      view (runLetInserter (lam (\x -> let_ x (\v -> int 4 + v))))
+        `shouldBe` "\\x1 -> let x2 = x1 in 4 + x2"
+    it "can bind an expression with a variable" do
+      view (runLetInserter (lam (\x -> let_ (x + int 1) (\v -> int 4 + v))))
+        `shouldBe` "\\x1 -> let x2 = x1 + 1 in 4 + x2"
+  describe "genLet" do
+    it "can bind" do
+      let exp = int 1 + genLet (int 2)
+      view (runLetInserter exp) `shouldBe` "let x1 = 2 in 1 + x1"
+      runIdentity (runLetInserter exp) `shouldBe` 3
+    it "can bind a literal across a lambda" do
+      let exp = lam (\x -> x + genLet (int 2))
+      view (runLetInserter exp) `shouldBe` "let x1 = 2 in \\x2 -> x2 + x1"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 3
+    it "can bind an operation across a lambda" do
+      let exp = lam (\x -> x + genLet (int 1 + int 2))
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in \\x2 -> x2 + x1"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 4
+    it "can wrap a variable" do
+      let exp = lam (\x -> withLocus (\l -> x + genLetAt l x))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 in x1 + x2"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    -- This must not raise a lookupVar failure for var=([1])  env=[]
+    it "cannot cause a scope extrusion using withLocus" do
+      view (runLetInserter (withLocus (\l -> lam (\x -> x + genLetAt l x))))
+        `shouldBe` "\\x1 -> let x2 = x1 in x1 + x2"
+    -- Check that 'lam' limits the scope extrusion:
+    -- as soon as that passing a virtual binding would
+    -- make a variable extrude its scope, this virtual binding is bound.
+    it "has its locus limited by lam when wrapping its variable" do
+      let exp = lam (\x -> x + genLet x)
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 in x1 + x2"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    it "does not have its locus limited by lam when not wrapping its variable" do
+      let exp = lam (\x -> x + genLet (int 1))
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 in \\x2 -> x2 + x1"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    it "can wrap let_" do
+      let exp = lam (\x -> x + genLet (let_ (int 1) (\y -> y)))
+      view (runLetInserter exp) `shouldBe` "let x1 = let x2 = 1 in x2 in \\x2 -> x2 + x1"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    it "can have its locus limited by withLocus" do
+      let exp = lam (\x -> withLocus (\l -> x + genLetAt l (let_ (int 1) (\y -> y))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = let x3 = 1 in x3 in x1 + x2"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    it "can wrap lam and a variable" do
+      let exp = lam (\x -> x + genLet (lam (\_y -> x)) .@ unit)
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = \\x3 -> x1 in x1 + x2 ()"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 2
+    it "can wrap lam and be called" do
+      let exp = lam (\x -> x + genLet (lam (\y -> y + int 2)) .@ x)
+      view (runLetInserter exp) `shouldBe` "let x1 = \\x2 -> x2 + 2 in \\x2 -> x2 + x1 x2"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 4
+    it "can wrap a variable in an operation" do
+      let exp = lam (\x -> x + genLet (x + int 1))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 + 1 in x1 + x2"
+      runIdentity (runLetInserter exp) 1 `shouldBe` 3
+    it "can wrap literals inside an expression with two variables" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (int 2 + int 3)))
+      view (runLetInserter exp) `shouldBe` "let x1 = 2 + 3 in \\x2 -> \\x3 -> x2 + x3 + x1"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 10
+    it "can wrap the farthest variable inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet x))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 in \\x3 -> x1 + x3 + x2"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 6
+    it "can wrap another variable inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet y))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = x2 in x1 + x2 + x3"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 9
+    it "can wrap another variable inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet y))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = x2 in x1 + x2 + x3"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 9
+    it "can wrap the farthest variable in an expression inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (x + int 1)))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 + 1 in \\x3 -> x1 + x3 + x2"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 7
+    it "can wrap the closest variable in an expression inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (y + int 1)))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = x2 + 1 in x1 + x2 + x3"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 10
+    it "can wrap both variables in an expression inside two lam" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (y + int 1 + x)))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = x2 + 1 + x1 in x1 + x2 + x3"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 11
+    it "can be nested" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (genLet (y + int 1 + x))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = x2 + 1 + x1 in let x4 = x3 in x1 + x2 + x4"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 11
+    it "can be nested within an expression of literals" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (y + genLet (int 1 + int 2))))
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in \\x2 -> \\x3 -> let x4 = x3 + x1 in x2 + x3 + x4"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 12
+    it "can be nested within an expression with a variable" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (y + genLet (int 1 + x))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = 1 + x1 in \\x3 -> let x4 = x3 + x2 in x1 + x3 + x4"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 11
+    it "can be nested within an expression with two variables" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (y + genLet (int 1 + x + y))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = 1 + x1 + x2 in let x4 = x2 + x3 in x1 + x2 + x4"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 15
+    it "can be nested within another expression with two variables" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (x + genLet (int 1 + x + y))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> \\x2 -> let x3 = 1 + x1 + x2 in let x4 = x1 + x3 in x1 + x2 + x4"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 12
+    it "can be nested within an expression of literals and one variable" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (int 1 + genLet (int 1 + x + int 2))))
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = 1 + x1 + 2 in let x3 = 1 + x2 in \\x4 -> x1 + x4 + x3"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 10
+    it "can be nested within an expression of literals" do
+      let exp = lam (\x -> lam (\y -> x + y + genLet (int 1 + genLet (int 1 + int 2 + int 3))))
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 + 3 in let x2 = 1 + x1 in \\x3 -> \\x4 -> x3 + x4 + x2"
+      runIdentity (runLetInserter exp) 1 4 `shouldBe` 12
+    it "duplicates literals when used with host-let" do
+      let exp = let x = genLet (int 1 + int 2) in x + x
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + 2 in x1 + x2"
+      runIdentity (runLetInserter exp) `shouldBe` 6
+    it "duplicates literals when using host-let and no specific memo key" do
+      let exp =
+            let x = genLet (int 1 + int 2)
+            in let y = genLet (int 1 + x)
+               in x + y
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + 2 in let x3 = 1 + x2 in x1 + x3"
+      runIdentity (runLetInserter exp) `shouldBe` 7
+    it "does not duplicate host-let when using a specific memo key" do
+      let exp =
+            let x = genLetMemoAtMaybe (Just 0) Nothing (int 1 + int 2)
+            in let y = genLet (int 1 + x)
+               in x + y
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + x1 in x1 + x2"
+      runIdentity (runLetInserter exp) `shouldBe` 7
+    it "does not duplicate host-lets when using specific memo keys" do
+      let exp =
+            let x = genLetMemoAtMaybe (Just 0) Nothing (int 1 + int 2)
+            in let y = genLetMemoAtMaybe (Just 1) Nothing (int 1 + x)
+               in let z = genLet (int 1 + y)
+                  in x + z + y
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + x1 in let x3 = 1 + x2 in x1 + x3 + x2"
+      runIdentity (runLetInserter exp) `shouldBe` 12
+    it "does not duplicate host-let when using a specific memo key, inside a lam" do
+      let exp =
+            lam $ \u ->
+              let x = genLetMemoAtMaybe (Just 0) Nothing (int 1 + int 2)
+              in let y = genLet (u + x)
+                 in x + y
+      view (runLetInserter exp) `shouldBe` "let x1 = 1 + 2 in \\x2 -> let x3 = x2 + x1 in x1 + x3"
+      runIdentity (runLetInserter exp) 2 `shouldBe` 8
+    it "does not duplicate host-let when using a specific memo key, inside a lam and using its variable" do
+      let exp =
+            lam $ \u ->
+              let x = genLetMemoAtMaybe (Just 0) Nothing (u + int 2)
+              in let y = genLet (int 1 + x)
+                 in x + y
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 + 2 in let x3 = 1 + x2 in x2 + x3"
+      runIdentity (runLetInserter exp) 2 `shouldBe` 9
+    it "does not duplicate host-lets when using specific memo keys, inside a lam and using its variable" do
+      let exp =
+            lam $ \u ->
+              let x = genLetMemoAtMaybe (Just 0) Nothing (u + int 2)
+              in let y = genLetMemoAtMaybe (Just 1) Nothing (int 1 + x)
+                 in let z = genLetMemoAtMaybe (Just 2) Nothing (int 1 + y)
+                    in x + z + y + z
+      view (runLetInserter exp) `shouldBe` "\\x1 -> let x2 = x1 + 2 in let x3 = 1 + x2 in let x4 = 1 + x3 in x2 + x4 + x3 + x4"
+      runIdentity (runLetInserter exp) 2 `shouldBe` 21
+  describe "Ex0" do
+    it "p12" do
+      runLII p12 `shouldBe` 3
+    it "p12" do
+      view (runLetInserter p12)
+        `shouldBe` "1 + 2"
+    it "c1" do
+      view (runLetInserter c1)
+        `shouldBe` "(1 + 2) * (1 + 2 + 1)"
+    it "c2" do
+      view (runLetInserter c2)
+        `shouldBe` "let x1 = 1 + 2 in x1 * (x1 + 1)"
+    it "c3" do
+      view (runLetInserter c3)
+        `shouldBe` "\\x1 -> (let x2 = 1 + 2 in x2 * (x2 + 1)) + (let x2 = 1 + 2 + x1 in x2 * (x2 + 1))"
+  describe "Ex1" do
+    it "c1l" do
+      view (runLetInserter c1l)
+        `shouldBe` "let x1 = 1 + 2 in x1"
+    -- , it "c3l1" do -- expected scope extrusion
+    --     view (runLetInserter c3l1) `shouldBe` ""
+    it "c3l2" do
+      -- serious duplication
+      view (runLetInserter c3l2)
+        `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + 2 in let x3 = 1 + 2 in let x4 = 1 + 2 in \\x5 -> let x6 = x3 + x5 in let x7 = x4 + x5 in x1 * (x2 + 1) + x6 * (x7 + 1)"
+    it "let x1 = 1 in let x2 = 2 in x1 + x2" do
+      let exp = withLocus $ \loc -> genLetAt loc (int 1) + genLetAt loc (int 2)
+      view (runLetInserter exp)
+        `shouldBe` "let x1 = 1 in let x2 = 2 in x1 + x2"
+    it "let_ (genLetAt loc (int 1 + int 2)) (\\x -> x + x)" do
+      let exp = withLocus $ \loc ->
+            let_ (genLetAt loc (int 1 + int 2)) $ \x ->
+              x + x
+      view (runLetInserter exp)
+        `shouldBe` "let x1 = 1 + 2 in let x2 = x1 in x2 + x2"
+    it "withLocus does not cause a scope extrusion" do
+      let exp = withLocus $ \loc1 ->
+            withLocus $ \loc2 ->
+              genLetAt
+                loc1 -- expected scope extrusion if it's not bound at loc2 instead of loc1
+                (int 1 + genLetAt loc2 (int 2))
+                * int 3
+      view (runLetInserter exp)
+        `shouldBe` "let x1 = 2 in let x2 = 1 + x1 in x2 * 3"
+    it "let x1 = 1 + 2 in let x2 = 1 + 2 in let x3 = 1 + x2 in x1 + x3" do
+      let exp = withLocus $ \loc ->
+            let x = (genLetAt loc (int 1 + int 2))
+            in let y = (genLetAt loc (int 1 + x))
+               in x + y
+      view (runLetInserter exp)
+        `shouldBe` "let x1 = 1 + 2 in let x2 = 1 + 2 in let x3 = 1 + x2 in x1 + x3"
+    -- , it "let x1 = 1 + 2 in let x2 = 1 + x1 in x1 + x2" do
+    --    let exp = withLocus $ \loc ->
+    --                let__ (genLetAt loc (int 1 + int 2)) $ \x ->
+    --                let__ (genLetAt loc (int 1 + x)) $ \y ->
+    --                x + y
+    --    view (runLetInserter exp) `shouldBe`
+    --      "let x1 = 1 + 2 in let x2 = 1 + x1 in x1 + x2"
+    it "let x1 = 1 + 2 in let x2 = x1 in let x3 = 1 + x2 in let x4 = x3 in x2 + x4" do
+      let exp = withLocus $ \loc ->
+            let_ (genLetAt loc (int 1 + int 2)) $ \x ->
+              withLocus $ \locX ->
+                let_ (genLetAt locX (int 1 + x)) $ \y ->
+                  x + y
+      view (runLetInserter exp)
+        `shouldBe` "let x1 = 1 + 2 in let x2 = x1 in let x3 = 1 + x2 in let x4 = x3 in x2 + x4"
+  describe "Fibonacci" do
+    it "fib 2" do
+      view (runLetInserter (fibonacci 2))
+        `shouldBe` "letRec [u1 = \\x2 -> \\x3 -> x3,u2 = \\x2 -> \\x3 -> x2] in \\x3 -> \\x4 -> u1 x3 x4 + u2 x3 x4"
+    it "fib 5" do
+      view (runLetInserter (fibonacci 5))
+        `shouldBe` "letRec [u1 = \\x2 -> \\x3 -> u2 x2 x3 + u3 x2 x3,u2 = \\x2 -> \\x3 -> u3 x2 x3 + u4 x2 x3,u3 = \\x2 -> \\x3 -> u4 x2 x3 + u5 x2 x3,u4 = \\x2 -> \\x3 -> x3,u5 = \\x2 -> \\x3 -> x2] in \\x6 -> \\x7 -> u1 x6 x7 + u2 x6 x7"
+    it "fib 2" do
+      runIdentity (runLetInserter (fibonacci 2)) 0 1 `shouldBe` 1
+    it "fib 5" do
+      runIdentity (runLetInserter (fibonacci 5)) 0 1 `shouldBe` 5
+    it "fib 10" do
+      runIdentity (runLetInserter (fibonacci 10)) 0 1 `shouldBe` 55
+  describe "Ackermann" do
+    it "sack 2 3" do
+      view (runLetInserter (sack 2))
+        `shouldBe` "letRec [u1 = \\x2 -> if x2 == 0 then u2 1 else u2 (u1 (x2 - 1)),u2 = \\x2 -> if x2 == 0 then u3 1 else u3 (u2 (x2 - 1)),u3 = \\x2 -> x2 + 1] in u1"
+    it "sack 2 3" do
+      runLII (sack 2) 3 `shouldBe` 9
+    it "sack 3 4" do
+      runLII (sack 3) 4 `shouldBe` 125
+
+--  {-
+--    it "fib 0" do
+--      runLII (fib .@ int 0) `shouldBe` 1
+--  , it "fib 1" do
+--      runLII (fib .@ int 1) `shouldBe` 2
+--  , it "fib 2" do
+--      runLII (fib .@ int 2) `shouldBe` 42
+--    it "fib 1" do
+--      runIdentity (fib .@ int 1) `shouldBe` 2
+--  , it "sack 3" do
+--      runIdentity (sack .@ int 3) `shouldBe` 9
+--  ,  it "acs" do
+--      view (runLetInserter acs) `shouldBe` ""
+--  -}
diff --git a/tests/Symantic/Syntaxes/Extras.hs b/tests/Symantic/Syntaxes/Extras.hs
new file mode 100644 (file)
index 0000000..4d79829
--- /dev/null
@@ -0,0 +1,124 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+module Symantic.Syntaxes.Extras where
+
+import Data.Function qualified as Fun
+import Data.Int (Int)
+import Debug.Trace (trace)
+import Symantic.Semantics.Identity
+import Symantic.Semantics.LetInserter
+import Symantic.Semantics.Viewer
+import Symantic.Semantics.Viewer.Fixity
+import Symantic.Syntaxes.Classes (Abstractable (..), Instantiable (..))
+import Symantic.Syntaxes.Derive
+import Text.Show (show, showString)
+import Prelude qualified
+
+-- * Type 'Addable'
+class Addable sem where
+  add :: sem (Int -> Int -> Int)
+  default add ::
+    FromDerived Addable sem =>
+    sem (Int -> Int -> Int)
+  add = liftDerived add
+infixl 6 `add`, +
+(+) ::
+  Instantiable sem =>
+  Addable sem =>
+  sem Int ->
+  sem Int ->
+  sem Int
+(+) x y = add .@ x .@ y
+instance Addable sem => Addable (OpenCode sem)
+instance Addable sem => Addable (AnnotatedCode sem)
+instance Addable sem => Addable (LetInserter sem)
+instance Addable Viewer where
+  add = ViewerInfix (infixB SideL 6) "(+)" "+"
+instance Addable Identity where
+  add = lam (\x -> lam (x Prelude.+))
+
+-- * Type 'Traceable'
+class Logable sem where
+  log :: Prelude.String -> sem a -> sem a
+  default log ::
+    FromDerived1 Logable sem =>
+    Prelude.String ->
+    sem a ->
+    sem a
+  log msg = liftDerived1 (log msg)
+instance Logable sem => Logable (OpenCode sem)
+instance Logable sem => Logable (AnnotatedCode sem)
+instance Logable sem => Logable (LetInserter sem)
+instance Logable Viewer where
+  log msg a = Viewer (\_env -> showString "log " Fun.. showString (show msg)) .@ a
+instance Logable Identity where
+  log msg = Identity Fun.. trace msg Fun.. runIdentity
+
+-- * Type 'Substractable'
+class Substractable sem where
+  substract :: sem (Int -> Int -> Int)
+  default substract ::
+    FromDerived Substractable sem =>
+    sem (Int -> Int -> Int)
+  substract = liftDerived substract
+infixl 6 `substract`, -
+(-) ::
+  Instantiable sem =>
+  Substractable sem =>
+  sem Int ->
+  sem Int ->
+  sem Int
+(-) x y = substract .@ x .@ y
+instance Substractable sem => Substractable (OpenCode sem)
+instance Substractable sem => Substractable (AnnotatedCode sem)
+instance Substractable sem => Substractable (LetInserter sem)
+instance Substractable Viewer where
+  substract = ViewerInfix (infixB SideL 6) "(-)" "-"
+instance Substractable Identity where
+  substract = lam (\x -> lam (x Prelude.-))
+
+-- * Type Multiplicable
+class Multiplicable sem where
+  multiply :: sem (Int -> Int -> Int)
+  default multiply ::
+    FromDerived Multiplicable sem =>
+    sem (Int -> Int -> Int)
+  multiply = liftDerived multiply
+infixl 7 `multiply`, *
+(*) ::
+  Instantiable sem =>
+  Multiplicable sem =>
+  sem Int ->
+  sem Int ->
+  sem Int
+(*) x y = multiply .@ x .@ y
+instance Multiplicable sem => Multiplicable (OpenCode sem)
+instance Multiplicable sem => Multiplicable (AnnotatedCode sem)
+instance Multiplicable sem => Multiplicable (LetInserter sem)
+instance Multiplicable Viewer where
+  multiply = ViewerInfix (infixB SideL 7) "(*)" "*"
+instance Multiplicable Identity where
+  multiply = lam (\x -> lam (x Prelude.*))
+
+-- * Type Divisible
+class Divisible i sem where
+  divide :: sem (i -> i -> i)
+  default divide ::
+    FromDerived (Divisible i) sem =>
+    sem (i -> i -> i)
+  divide = liftDerived divide
+infixl 7 `divide`, /
+(/) ::
+  Instantiable sem =>
+  Divisible i sem =>
+  sem i ->
+  sem i ->
+  sem i
+(/) x y = divide .@ x .@ y
+instance Divisible i sem => Divisible i (OpenCode sem)
+instance Divisible i sem => Divisible i (AnnotatedCode sem)
+instance Divisible i sem => Divisible i (LetInserter sem)
+instance Divisible i Viewer where
+  divide = ViewerInfix (infixB SideL 7) "(/)" "/"
+instance Prelude.Fractional i => Divisible i Identity where
+  divide = lam (\x -> lam (x Prelude./))