@@ -13,10 +13,32 @@ This use of `case` in UPLC was introduced with protocol version 11 and requires
1313:::
1414
1515In UPLC, it is possible to branch on expressions of certain built-in types, like
16- ` Integer ` and ` Bool ` . This can be done with ` case ` syntax, which is also used
17- for [ sums-of-products] ( ./encoding#sums-of-products ) . Using ` case ` on built-in
18- values may improve script performance and size.
19- The built-in types that ` case ` currently supports are:
16+ ` Integer ` and ` Bool ` using ` case ` syntax, which is also used
17+ for [ sums-of-products] ( ./encoding#sums-of-products ) . This may improve script performance
18+ and size, compared to older alternatives that use built-in functions.
19+
20+ This page describes the built-in types that are supported in UPLC and how Plinth
21+ developers can benefit from the improved performance.
22+
23+ ## Usage from Plinth
24+
25+ ::: info
26+
27+ The Plinth compiler option will change from ` datatypes=BuiltinCasing ` to
28+ ` datatypes=SumsOfProducts ` in the future.
29+
30+ :::
31+
32+
33+ Plinth developers can benefit from the performance of ` case ` by enabling the
34+ compiler [ option] ( ./plinth-compiler-options ) ` datatypes=BuiltinCasing ` , and
35+ using standard library functions such as ` caseList ` , ` fstPair ` .
36+ Note that Plinth's ` case ... of ... ` syntax is not generally compiled to UPLC,
37+ as it can be more expressive.
38+
39+ ## Supported types
40+
41+ The UPLC built-in types that ` case ` can be used on are:
2042
2143- ` bool `
2244- ` unit `
@@ -26,144 +48,184 @@ The built-in types that `case` currently supports are:
2648
2749In the future, support for ` data ` is also planned.
2850
29- For each type, the allowed branches and their order differs. See [ Supported
30- Types] ( #supported-types ) for more detail.
31-
32- ## Compiling to ` case ` in Plinth
33-
34- When compiling Plinth code with the [ option] ( ./plinth-compiler-options )
35- ` datatypes=BuiltinCasing ` (which in the future be achieved with
36- `datatypes=SumsOfProducts), many standard library functions will be compiled
37- into this use of ` case ` , such as ` fstPair ` , ` ifThenElse ` and ` caseList ` . Note
38- that Plinth's ` case ... of ... ` syntax is not necessarily compiled to UPLC, as
39- it can be more expressive.
40-
41- ## Supported types
42-
4351### Bool
4452
45- Booleans can be used in ` case ` with either one or two branches, where the first
46- is the ` False ` branch. Boolean negation can be written for example as:
53+ The following Plinth code implements a basic assertion:
4754
48- ``` uplc
49- lam b (case b (con bool True) (con bool False))
55+ ``` haskell
56+ assert :: Bool -> BuiltinUnit
57+ assert False = error ()
58+ assert True = unitval
5059```
5160
52- When only a single branch is provided, script execution will fail when the
53- boolean evaluates to ` True ` .
61+ With ` datatypes=BuiltinCasing ` , it is compiled to the new casing on builtins:
5462
55- Using a single branch is appropriate when the second branch was supposed to fail
56- already, saving script size.
63+ ``` uplc
64+ \b -> case b [error, ()]
65+ ```
5766
67+ In UPLC, booleans can be used in ` case ` with either one or two branches, where
68+ the first is always the ` False ` branch. When only a single branch is provided,
69+ script execution will fail when the boolean evaluates to True.
5870
5971::: info
6072
61- When compiling without ` datatypes=BuiltinCasing ` , Plinth's ` ifThenElse ` is
62- compiled into UPLC's ` ifThenElse ` built-in function, which usually requires more
63- AST nodes since each branch argument needs to be delayed (function application is
64- strict), and finally force the chosen branch. This impacts the size and
65- execution cost.
73+ Compare this to the UPLC that would have been generated otherwise:
74+
75+ ``` uplc
76+ \b -> force ( force ifThenElse b (delay ()) (delay error))
77+ ```
6678
79+ This uses the UPLC builtin function ` ifThenElse ` , which requires delaying the branch
80+ arguments, since application in UPLC is strict. The additional forcing and
81+ delaying impacts the size and execution cost.
6782:::
6883
6984
70- ### Unit
85+ ### BuiltinUnit
86+
87+ The built-in unit type can be used in a trivial way with ` case ` in UPLC, which
88+ takes exactly one branch. With ` datatypes=BuiltinCasing ` , Plinth will compile
89+ ` chooseUnit ` from ` PlutusTx.Builtins.Internal ` into ` case ` . For example:
90+
91+ ``` haskell
92+ forceUnit :: BuiltinUnit -> Integer
93+ forceUnit e = chooseUnit e 5
94+ ```
7195
72- Needs exactly one branch. If the expression being cased on evaluates to a unit
73- value, evaluation will continue with the expression in that branch. For example,
74- this expression evaluates to ` con integer 5 ` .
96+ Which results in the following UPLC:
7597
7698``` uplc
77- case (con unit ()) (con integer 5)
99+ \e -> case e [5]
78100```
79101
80- ### Pair
102+ UPLC's case on built-in unit requires exactly one branch. If the expression
103+ being cased on evaluates to the unit value, evaluation will continue with the
104+ expression in that branch.
81105
82- A built-in pair expects a single branch: a function that takes both components
83- of the pair.
106+ ### BuiltinPair
84107
85- This example sums the two integer constants in a pair.
108+ To destruct a built-in pair, use ` casePair ` from ` PlutusTx.Builtins ` . For example:
86109
87- ``` uplc
88- lam x (case x (lam a (lam b [(builtin addInteger) a b])))
110+ ``` haskell
111+ addPair :: BuiltinPair Integer Integer -> Integer
112+ addPair p = casePair p (+)
113+ ```
114+
115+ This compiles into ` case ` in UPLC, which expects a single branch:
116+
117+ ```
118+ \p -> case p [(\x y -> addInteger x y)]
89119```
90120
91121::: info
92122
93123When compiling without ` datatypes=BuiltinCasing ` , Plinth's ` choosePair ` is
94124compiled into multiple built-in function calls to project out the pair's
95- components, impacting size and execution cost.
125+ components, impacting size and execution cost:
126+
127+ ``` uplc
128+ \p -> addInteger (force (force fstPair) p) (force (force sndPair) p)
129+ ```
96130
97131:::
98132
99133### Integer
100134
101- Casing on integers can be used for non-negative integers only, and a variable
135+ Casing on integers in UPLC can be used for non-negative integers only, and a variable
102136amount of branches may be given. If the expression ` e ` evaluates to an integer
103- ` i ` , the ` i ` th branch will be evaluated. If there is no branch, ` case ` will fail.
137+ ` i ` , the ` i ` th branch will be evaluated. If there is no branch, ` case ` will
138+ fail.
104139
105- For example, the following expression evaluates to ` con string "c" `
140+ In Plinth, use the ` caseInteger ` function:
106141
107- ```
108- case [(builtin addInteger) (con integer 1) (con integer 1)]
109- (con string "a")
110- (con string "b")
111- (con string "c")
142+ ``` haskell
143+ integerABC :: Integer -> BuiltinString
144+ integerABC i = caseInteger i [" a" , " b" , " c" ]
112145```
113146
114- If the ` i ` th branch is not given, or ` i ` is a negative integer, evaluation will
115- fail:
147+ Applying this function to ` 2 ` gives ` "c" ` , while ` 10 ` or ` -1 ` produce an error.
148+ Note that the second argument must be given as a literal list, otherwise it is a
149+ Plinth compile error.
116150
117- ```
118- case [(builtin addInteger) (con integer 2) (con integer 2)]
119- (con string "a")
120- (con string "b")
121- (con string "c")
122- ```
123151
124- Results in
152+ Plinth generates the following UPLC:
125153
126154```
127- An error has occurred:
128- 'case' over a value of a built-in type failed with
129- 'case 4' is out of bounds for the given number of branches: 3
130- Caused by: 4
155+ \i -> case i ["a", "b", "c"]
131156```
132157
133- Note that there is no way to provide a "catch-all" case for integers.
158+ In general, if ` i ` th branch is not given, or ` i ` is a negative integer, evaluation will
159+ fail. Note that there is no way to provide a "catch-all" case for integers.
134160
135161::: info
136162
137- In Plinth, using ` caseInteger ` with ` datatypes=BuiltinCasing ` will be compiled into
138- the above ` case ` construct in PIR, provided the second argument is given as a
139- literal list (otherwise this is a compile error).
140-
141163When not using ` datatypes=BuiltinCasing ` , Plinth's ` caseInteger ` is compiled
142164into a much less efficient implementation that turns the second argument in a
143165list (of which the representation depends on the chosen ` datatypes= ` flag), and
144- does a recursive lookup in that list.
166+ does a recursive lookup in that list. The above Plinth code is compiled to:
167+
145168
169+ ``` uplc
170+ (\traceError ->
171+ (\go i ->
172+ force
173+ (force ifThenElse
174+ (lessThanInteger i 0)
175+ (delay (traceError "PT6"))
176+ (delay
177+ (go
178+ i
179+ (constr 1
180+ [ "a"
181+ , (constr 1
182+ ["b", (constr 1 ["c", (constr 0 [])])]) ])))))
183+ ((\s -> s s)
184+ (\s ds ds ->
185+ case
186+ ds
187+ [ (traceError "PT7")
188+ , (\x xs ->
189+ force
190+ (force ifThenElse
191+ (equalsInteger 0 ds)
192+ (delay x)
193+ (delay
194+ ((\x -> s s x) (subtractInteger ds 1) xs)))) ])))
195+ (\str -> (\x -> error) (force trace str (constr 0 [])))
196+ ```
146197:::
147198
148199
149- ### List
200+ ### BuiltinList
150201
151202A ` case ` on built-in lists may be given one or two branches (similar to
152203booleans), where the first one deals with the cons case, and the second one with
153204the empty list. If no second branch is given, execution will fail when the list
154205turns out to be empty.
155206
156- This example implements the ` head ` function, which fails if the list if empty.
207+ This example implements a ` head ` function for boolean lists , which fails if the list if empty.
157208
158209``` uplc
159- lam xs (case xs (lam y (lam ys y)))
210+ head :: BuiltinList Bool -> Bool
211+ head xs = caseList (\_ -> error ()) (\x _ -> x) xs
160212```
161213
162214::: info
163215
164- When compiling without ` datatypes=BuiltinCasing ` , Plinth's ` caseList ` is
165- compiled into a combination of built-in calls such as ` chooseList ` , ` headList `
166- and ` tailList ` . Similarly to booleans, the branches are also thunked, impacting
167- script size and execution cost.
216+ When compiling without ` datatypes=BuiltinCasing ` , compilation falls back on
217+ using multiple built-ins, such as ` chooseList ` , ` headList ` and ` tailList ` .
218+ Similarly to booleans, the branches are thunked, impacting script size and
219+ execution cost:
220+
221+ ``` uplc
222+ (\xs ->
223+ force
224+ (force (force chooseList)
225+ xs
226+ (delay (\ds -> error))
227+ (delay ((\x xs ds -> x) (force headList xs) (force tailList xs))))
228+ (constr 0 []))
229+ ```
168230
169231:::
0 commit comments