1- {-# LANGUAGE LambdaCase #-}
1+ {-# LANGUAGE LambdaCase #-}
2+ {-# LANGUAGE ViewPatterns #-}
23{-|
34A simple beta-reduction pass.
45-}
56module PlutusIR.Transform.Beta (
67 beta
78 ) where
89
9- import PlutusPrelude
10-
1110import PlutusIR
11+ import PlutusIR.Core.Type
1212
13- import Control.Lens (transformOf )
13+ import Control.Lens.Setter ((%~) )
14+ import Data.Function ((&) )
15+ import Data.List.NonEmpty qualified as NE
1416
15- {-|
16- A single non-recursive application of the beta rule.
17+ {- Note [Multi-beta]
18+ Consider two examples where applying beta should be helpful.
19+
20+ 1: [(\x . [(\y . t) b]) a]
21+ 2: [[(\x . (\y . t)) a] b]
22+
23+ (1) is the typical "let-binding" pattern: each binding corresponds to an immediately-applied lambda.
24+ (2) is the typical "function application" pattern: a multi-argument function applied to multiple arguments.
25+
26+ In both cases we would like to produce something like
27+
28+ let
29+ x = a
30+ y = b
31+ in t
32+
33+ However, if we naively do a bottom-up pattern-matching transformation on the AST
34+ to look for immediately-applied lambda abstractions then we will get the following:
35+
36+ 1:
37+ [(\x . [(\y . t) b]) a]
38+ -->
39+ [(\x . let y = b in t) a]
40+ ->
41+ let x = a in let y = b in t
42+
43+ 2:
44+ [[(\x . (\y . t)) a] b]
45+ -->
46+ [(let x = a in (\y . t)) b]
47+
48+ Now, if we later lift the let out, then we will be able to see that we can transform (2) further.
49+ But that means that a) we'd have to do the expensive let-floating pass in every iteration of the simplifier, and
50+ b) we can only inline one function argument per iteration of the simplifier, so for a function of
51+ arity N we *must* do at least N passes.
52+
53+ This isn't great, so the solution is to recognize case (2) properly and handle all the arguments in one go.
54+ That will also match cases like (1) just fine, since it's just made up of unary function applications.
55+
56+ That does mean that we need to do a manual traversal rather than doing standard bottom-up processing.
1757-}
18- betaStep
19- :: Term tyname name uni fun a
20- -> Term tyname name uni fun a
21- betaStep = \ case
22- Apply a (LamAbs _ name typ body) arg ->
23- let varDecl = VarDecl a name typ
24- binding = TermBind a Strict varDecl arg
25- bindings = binding :| []
26- in
27- Let a NonRec bindings body
28- -- This case is disabled as it introduces a lot of type inlining (determined from profiling)
29- -- and is currently unsound https://input-output.atlassian.net/browse/SCP-2570.
30- -- TyInst a (TyAbs _ tyname kind body) typ ->
31- -- let tyVarDecl = TyVarDecl a tyname kind
32- -- tyBinding = TypeBind a tyVarDecl typ
33- -- bindings = tyBinding :| []
34- -- in
35- -- Let a NonRec bindings body
36- t -> t
58+
59+ {-| Extract the list of bindings from a term, a bit like a "multi-beta" reduction.
60+
61+ Some examples will help:
62+
63+ [(\x . t) a] -> Just ([x |-> a], t)
64+
65+ [[[(\x . (\y . (\z . t))) a] b] c] -> Just ([x |-> a, y |-> b, z |-> c]) t)
66+
67+ [[(\x . t) a] b] -> Nothing
68+
69+ When we decide that we want to do beta for types, we will need to extend this to handle type instantiations too.
70+ -}
71+ extractBindings :: Term tyname name uni fun a -> Maybe (NE. NonEmpty (Binding tyname name uni fun a ), Term tyname name uni fun a )
72+ extractBindings = collectArgs []
73+ where
74+ collectArgs argStack (Apply _ f arg) = collectArgs (arg: argStack) f
75+ collectArgs argStack t = matchArgs argStack [] t
76+ matchArgs (arg: rest) acc (LamAbs a n ty body) = matchArgs rest (TermBind a Strict (VarDecl a n ty) arg: acc) body
77+ matchArgs [] acc t =
78+ case NE. nonEmpty (reverse acc) of
79+ Nothing -> Nothing
80+ Just acc' -> Just (acc', t)
81+ matchArgs (_: _) _ _ = Nothing
3782
3883{-|
3984Recursively apply the beta transformation on the code, both for the terms
@@ -57,4 +102,17 @@ and types
57102beta
58103 :: Term tyname name uni fun a
59104 -> Term tyname name uni fun a
60- beta = transformOf termSubterms betaStep
105+ beta = \ case
106+ -- See Note [Multi-beta]
107+ -- This maybe isn't the best annotation for this term, but it will do.
108+ (extractBindings -> Just (bs, t)) -> Let (termAnn t) NonRec bs (beta t)
109+ -- This case is disabled as it introduces a lot of type inlining (determined from profiling)
110+ -- and is currently unsound https://input-output.atlassian.net/browse/SCP-2570.
111+ -- TyInst a (TyAbs _ tyname kind body) typ ->
112+ -- let tyVarDecl = TyVarDecl a tyname kind
113+ -- tyBinding = TypeBind a tyVarDecl typ
114+ -- bindings = tyBinding :| []
115+ -- in
116+ -- Let a NonRec bindings body
117+
118+ t -> t & termSubterms %~ beta
0 commit comments