-
Notifications
You must be signed in to change notification settings - Fork 2
The Language (Differences between vanilla PureScript and Purus)
While we have endeavored to make Purus as close to PureScript as possible, there are several places where the requirements of UPLC compilation necessitate a few divergences from vanilla PureScript. This page consists of a list of those changes, along with (hopefully) enough of an explanation to allow users familiar with PureScript to quickly adjust.
Due to an ambiguity in PureScript's parser which prevents kind annotations of type variables in contexts where they may be necessary for successful compilation to UPLC, we have altered the syntax for Rows of types in PureScript.
In vanilla PureScript, the syntax for a row type is:
(field1 :: Type1, field2 :: Type2, field3 :: Type3)In Purus, if you wish to write out a row type, you should use the syntax:
[field1 :: Type1, field2 :: Type2, field3 :: Type3]IMPORTANT NOTE: This changes ONLY applied to Row types, and not to records. The type of a record in both vanilla PureScript and Purus may be represented with the normal syntax: {field1 :: Type1, field2 :: Type2, field3 :: Type3}. Since most users are likely to interact with Rows only through the records they support, this change should not be a concern for a majority of users.
Both vanilla PureScript and Purus support polymorphic rows/records, which are most commonly encountered in a form like:
gimmeAnA :: forall (r :: Row Type). {a :: Int | r} -> Int
gimmeAnA r = r.a (The quantifier and type annotation are required in Purus, but not in PureScript, for reasons that will be explained below).
However, there is a significant difference between vanilla PureScript and Purus: Because Plutus does not and cannot support the Row kind, you must ensure that all function expressions containing a polymorphic record are applied to arguments with a concrete type.
Briefly, the reason for this is that records are translated into tuples during UPLC compilation. For example, the non-polymorphic record {foo :: Int, bar :: Bool} will be translated into a Tuple2 Int Bool. In order to support accessors for record fields (e.g. x.foo), it is necessary that the types of all fields are known at compile time - if they aren't known, we cannot determine which tuple index corresponds to the record labels (foo/bar here). This is unproblematic for records with a static and known type, such as the previous example. But this restriction poses a problem for polymorphic records appearing in function signatures, since the structure of the "rest of the row" variable (r in the example above) is not known until that function is applied.
In practice, users do not need to understand this restriction, and following the rule: MAKE SURE YOUR MAIN FUNCTION DOES NOT HAVE ANY POLYMORPHIC/OPEN RECORDS will be sufficient to satisfy the requirement.
While we could, in theory, support some kind of FFI with either PIR, PLC, or UPLC, doing so does not seem very useful and increases compiler complexity quite a bit, so we have disabled the ability to declare foreign imports of every sort.
In Purus, unlike PureScript, explicit quantifiers are required for all type variables.
This is, this will compile fine:
aFunction :: forall x. x -> IntWhereas this will throw an error and fail to compile:
aFunction' :: x -> IntThis change was not essential, but we chose to require explicit quantifiers in order to increase the likelihood of confusion or mistakes resulting from failure to properly annotate the kind of type variables.
Purus (unlike PureScript), performs type variable defaulting