packages feed

Jikka-5.2.0.0: src/Jikka/Core/Convert/MakeScanl.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}

-- |
-- Module      : Jikka.Core.Convert.MakeScanl
-- Description : converts @foldl@ on lists to @scanl@. / リスト上の @foldl@ を @scanl@ に変換します。
-- Copyright   : (c) Kimiyuki Onaka, 2021
-- License     : Apache License 2.0
-- Maintainer  : kimiyuki95@gmail.com
-- Stability   : experimental
-- Portability : portable
--
-- \[
--     \newcommand\int{\mathbf{int}}
--     \newcommand\bool{\mathbf{bool}}
--     \newcommand\list{\mathbf{list}}
-- \]
module Jikka.Core.Convert.MakeScanl
  ( run,

    -- * internal rules
    rule,
    reduceScanlBuild,
    reduceFoldlSetAtRecurrence,
    reduceFoldlSetAtAccumulation,
    reduceFoldlSetAtGeneric,
    getRecurrenceFormulaBase,
    getRecurrenceFormulaStep1,
    getRecurrenceFormulaStep,
  )
where

import Control.Monad.Trans.Maybe
import Data.List
import qualified Data.Map as M
import Jikka.Common.Alpha
import Jikka.Common.Error
import Jikka.Core.Language.ArithmeticalExpr
import Jikka.Core.Language.Beta
import Jikka.Core.Language.BuiltinPatterns
import Jikka.Core.Language.Expr
import Jikka.Core.Language.FreeVars
import Jikka.Core.Language.Lint
import Jikka.Core.Language.RewriteRules
import Jikka.Core.Language.Util

-- |
-- == List of builtin functions which are reduced
--
-- * `Nil` \(: \forall \alpha. \list(\alpha)\)
-- * `Cons` \(: \forall \alpha. \alpha \to \list(\alpha) \to \list(\alpha)\)
-- * `Scanl` \(: \forall \alpha \beta. (\beta \to \alpha \to \beta) \to \beta \to \list(\alpha) \to \list(\beta)\)
reduceScanlBuild :: Monad m => RewriteRule m
reduceScanlBuild = simpleRewriteRule "reduceScanlBuild" $ \case
  Scanl' _ t2 _ init (Nil' _) -> Just $ Cons' t2 init (Nil' t2)
  Scanl' t1 t2 f init (Cons' _ x xs) -> Just $ Cons' t2 init (Scanl' t1 t2 f (App2 f init x) xs)
  _ -> Nothing

-- | `getRecurrenceFormulaStep1` removes `At` in @body@.
getRecurrenceFormulaStep1 :: MonadAlpha m => Integer -> Type -> VarName -> VarName -> Expr -> m (Maybe Expr)
getRecurrenceFormulaStep1 shift t a i body = do
  x <- genVarName a
  let proj k =
        if toInteger shift + k == 0
          then Just $ Var x
          else Nothing
  let go :: Expr -> Maybe Expr
      go = \case
        At' _ (Var a') i' | a' == a -> case unNPlusKPattern (parseArithmeticalExpr i') of
          Just (i', k) | i' == i -> proj k
          _ -> Nothing
        Var x -> if x == a then Nothing else Just (Var x)
        Lit lit -> Just $ Lit lit
        App f e -> App <$> go f <*> go e
        Lam x t e -> Lam x t <$> if x == a then Just e else go e
        Let x t e1 e2 -> Let x t <$> go e1 <*> if x == a then Just e2 else go e2
        Assert f e -> Assert <$> go f <*> go e
  return $ case go body of
    Just body -> Just $ Lam2 x t i IntTy body
    Nothing -> Nothing

-- | `getRecurrenceFormulaStep` replaces `At` in @body@ with `Proj`.
getRecurrenceFormulaStep :: MonadAlpha m => Integer -> Integer -> Type -> VarName -> VarName -> Expr -> m (Maybe Expr)
getRecurrenceFormulaStep shift size t a i body = do
  x <- genVarName a
  let ts = replicate (fromInteger size) t
  let proj k =
        if 0 <= toInteger shift + k && toInteger shift + k < toInteger size
          then Just $ Proj' ts (shift + k) (Var x)
          else Nothing
  let go :: Expr -> Maybe Expr
      go = \case
        At' _ (Var a') i' | a' == a -> case unNPlusKPattern (parseArithmeticalExpr i') of
          Just (i', k) | i' == i -> proj k
          _ -> Nothing
        Var x -> if x == a then Nothing else Just (Var x)
        Lit lit -> Just $ Lit lit
        App f e -> App <$> go f <*> go e
        Lam x t e -> Lam x t <$> if x == a then Just e else go e
        Let x t e1 e2 -> Let x t <$> go e1 <*> if x == a then Just e2 else go e2
        Assert f e -> Assert <$> go f <*> go e
  return $ case go body of
    Just body -> Just $ Lam2 x (TupleTy ts) i IntTy (uncurryApp (Tuple' ts) (map (\i -> Proj' ts i (Var x)) [1 .. size - 1] ++ [body]))
    Nothing -> Nothing

-- |
-- * This assumes that `Range2` and `Range3` are already converted to `Range1` (`Jikka.Core.Convert.ShortCutFusion`).
-- * This assumes that combinations `Foldl` and `Map` squashed (`Jikka.Core.Convert.ShortCutFusion`).
-- * This assumes that constants are already folded (`Jikka.Core.Convert.ConstantFolding`).
reduceFoldlSetAtRecurrence :: MonadAlpha m => RewriteRule m
reduceFoldlSetAtRecurrence = makeRewriteRule "reduceFoldlSetAtRecurrence" $ \_ -> \case
  -- foldl (fun a i -> setat a index(i) step(a, i)) base indices
  Foldl' _ (ListTy t2) (Lam2 a _ i _ (SetAt' _ (Var a') index step)) base indices | a' == a && a `isUnusedVar` index -> runMaybeT $ do
    -- index(i) = i + k
    k <- hoistMaybe $ case unNPlusKPattern (parseArithmeticalExpr index) of
      Just (i', k) | i' == i -> Just k
      _ -> Nothing
    -- indices = range n
    n <- hoistMaybe $ case indices of
      Range1' n -> Just n -- We can do this because foldl-map combinations are already reduced.
      _ -> Nothing
    -- init = setat (k-1) a_{k-1} (... (setat 0 a_0 (range n)) ...)
    (base, _) <- return $ getRecurrenceFormulaBase base -- TODO: care about cases when base is longer than indices
    case base of
      [] ->
        if k == 0 && a `isUnusedVar` step
          then return $ Map' IntTy t2 (Lam i IntTy step) (Range1' n)
          else hoistMaybe Nothing
      [base] -> do
        step <- MaybeT $ getRecurrenceFormulaStep1 (- 1 + fromInteger k) t2 a i step
        return $ Scanl' IntTy t2 step base (Range1' n)
      _ -> do
        let ts = replicate (length base) t2
        let base' = uncurryApp (Tuple' ts) base
        step <- MaybeT $ getRecurrenceFormulaStep (- genericLength base + k) (genericLength base) t2 a i step
        x <- lift (genVarName a)
        return $ foldr (Cons' t2) (Map' (TupleTy ts) t2 (Lam x (TupleTy ts) (Proj' ts (genericLength base - 1) (Var x))) (Scanl' IntTy (TupleTy ts) step base' (Range1' n))) (init base)
  _ -> return Nothing

-- | `checkAccumulationFormulaStep` checks that all `At` in @body@ about @a@ are @At a i@.
checkAccumulationFormulaStep :: VarName -> VarName -> Expr -> Bool
checkAccumulationFormulaStep a i = go
  where
    go = \case
      At' _ (Var a') i' | a' == a -> case i' of
        Var i' | i' == i -> True
        _ -> False
      Var x -> x /= a
      Lit _ -> True
      App f e -> go f && go e
      Lam x _ e -> x == a || go e
      Let x _ e1 e2 -> go e1 && (x == a || go e2)
      Assert e1 e2 -> go e1 && go e2

-- |
-- * This assumes that `Range2` and `Range3` are already converted to `Range1` (`Jikka.Core.Convert.ShortCutFusion`).
-- * This assumes that combinations `Foldl` and `Map` squashed (`Jikka.Core.Convert.ShortCutFusion`).
-- * This assumes that constants are already folded (`Jikka.Core.Convert.ConstantFolding`).
reduceFoldlSetAtAccumulation :: MonadAlpha m => RewriteRule m
reduceFoldlSetAtAccumulation = makeRewriteRule "reduceFoldlSetAtAccumulation" $ \_ -> \case
  -- foldl (fun a i -> setat a index() step(a, i)) base indices
  Foldl' _ (ListTy t2) (Lam2 a _ i _ (SetAt' _ (Var a') index step)) base indices | a' == a && a `isUnusedVar` index && i `isUnusedVar` index -> runMaybeT $ do
    -- step(a, i) = op (at a index()) step'(a, i)
    (accumulate, step) <- hoistMaybe $ case step of
      Max2' t (At' _ (Var a') index') step | a' == a && index' == index -> Just (Max1' t, step)
      Min2' t (At' _ (Var a') index') step | a' == a && index' == index -> Just (Min1' t, step)
      Plus' (At' _ (Var a') index') step | a' == a && index' == index -> Just (Sum', step)
      Mult' (At' _ (Var a') index') step | a' == a && index' == index -> Just (Product', step)
      ModPlus' (At' _ (Var a') index') step m | a' == a && index' == index && a `isUnusedVar` m && i `isUnusedVar` m -> Just ((`ModSum'` m), step)
      ModMult' (At' _ (Var a') index') step m | a' == a && index' == index && a `isUnusedVar` m && i `isUnusedVar` m -> Just ((`ModProduct'` m), step)
      _ -> Nothing
    -- indices = range (index())
    guard $ indices == Range1' index
    -- step'(a, i) = step''(at a i)
    guard $ checkAccumulationFormulaStep a i step
    step <- lift $ substitute a base step
    return $ SetAt' t2 base index (accumulate (Map' IntTy t2 (Lam i IntTy step) (Range1' index)))
  _ -> return Nothing

-- | `checkGenericRecurrenceFormulaStep` checks that all `At` in @body@ about @a@ have indices less than @i + k@.
checkGenericRecurrenceFormulaStep :: VarName -> VarName -> Integer -> Expr -> Bool
checkGenericRecurrenceFormulaStep a = \i k -> go (M.fromList [(i, k - 1)])
  where
    -- (i, k) in env menas a[i + k] is accessible but a[i + k + 1] is not.
    go :: M.Map VarName Integer -> Expr -> Bool
    go env = \case
      At' _ (Var a') i | a' == a -> case unNPlusKPattern (parseArithmeticalExpr i) of
        Just (i, k) -> case M.lookup i env of
          Just limit -> k <= limit
          Nothing -> False
        _ -> False
      Map' _ _ (Lam j _ body) (Range1' n) | j /= a -> case unNPlusKPattern (parseArithmeticalExpr n) of
        Just (i, k) -> case M.lookup i env of
          Just limit -> go (M.insert j (limit - k + 1) env) body
          Nothing -> go env body && go env n
        _ -> go env body && go env n
      Var x -> x /= a
      Lit _ -> True
      App f e -> go env f && go env e
      Lam x _ e -> x == a || go env e
      Let x _ e1 e2 -> go env e1 && (x == a || go env e2)
      Assert e1 e2 -> go env e1 && go env e2

reduceFoldlSetAtGeneric :: MonadAlpha m => RewriteRule m
reduceFoldlSetAtGeneric = makeRewriteRule "reduceFoldlSetAtGeneric" $ \_ -> \case
  -- foldl (fun a i -> setat a index(i) step(a, i)) base indices
  Foldl' _ (ListTy t2) (Lam2 a _ i _ (SetAt' _ (Var a') index step)) base indices | a' == a && a `isUnusedVar` index -> runMaybeT $ do
    -- index(i) = i + k
    k <- hoistMaybe $ case unNPlusKPattern (parseArithmeticalExpr index) of
      Just (i', k) | i' == i -> Just k
      _ -> Nothing
    -- indices = range n
    n <- hoistMaybe $ case indices of
      Range1' n -> Just n -- We can do this because foldl-map combinations are already reduced.
      _ -> Nothing
    -- base = setat (k - 1) a_{k - 1} (... (setat 0 a_0 (range n)) ...)
    (base, _) <- return $ getRecurrenceFormulaBase base -- TODO: care about cases when base is longer than indices
    -- step(a, i) = step(a[0], a[1], ..., a[i + k - 1], i)
    guard $ checkGenericRecurrenceFormulaStep a i k step
    step <- lift $ substitute i (Minus' (Len' t2 (Var a)) (LitInt' k)) step
    return $ Build' t2 (Lam a (ListTy t2) step) (foldl (Snoc' t2) (Nil' t2) base) n
  _ -> return Nothing

rule :: MonadAlpha m => RewriteRule m
rule =
  mconcat
    [ reduceScanlBuild,
      reduceFoldlSetAtRecurrence,
      reduceFoldlSetAtAccumulation,
      reduceFoldlSetAtGeneric
    ]

runProgram :: (MonadAlpha m, MonadError Error m) => Program -> m Program
runProgram = applyRewriteRuleProgram' rule

-- | `run` replaces `Foldl` with `Scanl`.
--
-- == Example
--
-- Before:
--
-- > let xs = range n
-- > xs[0] <- 0
-- > xs[1] <- 1
-- > foldl (fun a i -> do
-- >    xs[i + 2] <- xs[i] + xs[i + 1]
-- >    xs
-- > ) xs (range (n - 2))
--
-- After:
--
-- > 0 : map snd (
-- >    scanl (fun a i -> (snd a, fst a + snd a))
-- >          (0, 1)
-- >          (range (n - 2)))
--
-- == List of builtin functions which are reduced
--
-- === Build functions
--
-- * `Nil` \(: \forall \alpha. \list(\alpha)\)
-- * `Cons` \(: \forall \alpha. \alpha \to \list(\alpha) \to \list(\alpha)\)
-- * `Range1` \(: \int \to \list(\int)\)
-- * `Build` \(: \forall \alpha. (\list(\alpha) \to \alpha) \to \list(\alpha) \to \int \to \list(\alpha)\)
--
-- === Map functions
--
-- * `Scanl` \(: \forall \alpha \beta. (\beta \to \alpha \to \beta) \to \beta \to \list(\alpha) \to \list(\beta)\)
-- * `SetAt` \(: \forall \alpha. \list(\alpha) \to \int \to \alpha \to \list(\alpha)\)
--
-- === Fold functions
--
-- * `Foldl` \(: \forall \alpha \beta. (\beta \to \alpha \to \beta) \to \beta \to \list(\alpha) \to \beta\)
-- * `At` \(: \forall \alpha. \list(\alpha) \to \int \to \alpha\)
run :: (MonadAlpha m, MonadError Error m) => Program -> m Program
run prog = wrapError' "Jikka.Core.Convert.MakeScanl" $ do
  precondition $ do
    ensureWellTyped prog
  prog <- runProgram prog
  postcondition $ do
    ensureWellTyped prog
  return prog