migration-guides/0.14-Migration-Guide.md
This guide summarizes the changes you may need to make to your code to migrate from PureScript 0.13 to PureScript 0.14. It covers major changes to the compiler, the core libraries, and tooling. For a detailed review of changes to the compiler in this release, please see the compiler release notes for PureScript 0.14.
Compiler releases are often accompanied by breaking changes in the core libraries. While some major library changes are described in this document, you should consult the individual changelogs for any libraries you depend on.
Contents
The PureScript 0.14 compiler introduces a number of new features, including support for polymorphic kinds and zero-cost coercions. These features may require changes to your code.
This section of the guide will walk through common changes you may need to make, but it isn't a complete overview of changes in the 0.14 compiler and doesn't provide an explanation of these new features.
PureScript now supports polymorphic kinds and kind signatures. Most code is unaffected, but there are some changes.
The old Kind data type and namespace has been removed. The foreign import kind syntax is deprecated, so this will warn:
foreign import kind Boolean
foreign import data True :: Boolean
foreign import data False :: Boolean
and should be rewritten as an empty data declaration:
data Boolean
foreign import data True :: Boolean
foreign import data False :: Boolean
The old unary # Type syntax for row kinds is deprecated and should instead be written as Row Type. The kind of RowList has changed from Type to Type -> Type, so if you had RowList in 0.13.x code it should now be written as RowList Type. For example, here's how the ListToRow class has changed:
- class ListToRow (list :: RowList) (row :: # Type)
+ class ListToRow (list :: RowList Type) (row :: Row Type)
Finally, all type-level declarations are now generalized. In the below declaration, the kind for a would default to Type:
data Proxy a
> :k Proxy
Type -> Type
Now, the kind of Proxy will be generalized to forall k. k -> Type. It is better to be explicit about polymorphism by writing signatures, and the compiler will warn about missing kind signatures when it infers polymorphic kinds. You can provide a kind signature to avoid the compiler warning:
data Proxy :: forall k. k -> Type
data Proxy a = Proxy
Classes can have signatures too, and they must end with a Constraint kind instead of Type. For example, here's the new definition of Prim.Row.Cons:
class Cons :: forall k. Symbol -> k -> Row k -> Row k -> Constraint
class Cons label a tail row | label a tail -> row, label row -> a tail
PureScript 0.14 introduces a new compiler-solved class, Prim.Coerce.Coercible, to relate types with the same runtime representation.
You can now use Safe.Coerce.coerce from the new safe-coerce library in place of Unsafe.Coerce.unsafeCoerce to turn an a into a b when Coercible a b holds. If you are currently using unsafeCoerce to transform two types with the same runtime representation, you should consider replacing it with coerce.
The Coercible implementation introduces role annotations for type parameters. Most users do not need to use roles explicitly, as role inference covers the vast majority of cases and this is a new feature that largely does not affect existing code. However, if you would like to understand more about role annotations, please see the roles documentation in this repository.
Primes are no longer allowed in JavaScript FFI definitions because PureScript 0.15 will generate ES modules.
To fix:
Rename any functions that contain a prime and are implemented via the FFI:
// before
exports["functionName'"] = function (a) { return a; }
// after
exports.functionNameImpl = function (a) { return a; }
You will also need to update the foreign import in your PureScript code:
- foreign import functionName' :: forall a. a -> a
+ foreign import functionNameImpl :: forall a. a -> a
+
+ functionName' :: forall a. a -> a
+ functionName' = functionNameImpl
Several core library changes accompanied the PureScript 0.14 compiler release. This guide covers notable changes to the purescript (core), purescript-contrib, purescript-node, and purescript-web organizations.
However, the changes listed below are not exhaustive. As of the PureScript 0.14 release, all libraries in these organizations contain a CHANGELOG.md file which lists any breaking changes in their latest release. You should consult the changelogs for any libraries you depend on in addition to this migration guide.
Several core libraries have been restructured. In almost all cases functionality has been preserved, but some functions, types, and / or instances have been moved to new locations.
proxy library have been deprecated, with Proxy merged into prelude.PureScript 0.14 introduced support for polymorphic kinds, which means we no longer need a proliferation of different proxy types which differ only in the kind of their parameter. For example, we previously had many proxy types:
data Proxy (a :: Type) = Proxy
data Proxy2 (a :: Type -> Type) = Proxy2
data Proxy3 (a :: Type -> Type -> Type) = Proxy3
data SProxy (a :: Symbol) = SProxy
data RProxy (row :: # Type) = RProxy
data RLProxy (row :: RowList) = RLProxy
-- ...and so on.
All proxy types except for Proxy have been deprecated, and Proxy is now implemented with a polymorphic kind:
-- | Proxy type for all kinds.
data Proxy :: forall k. k -> Type
data Proxy a = Proxy
In addition, the proxy library has been deprecated and all relevant functionality moved into prelude.
To fix:
You should remove proxy as a dependency of your library or application, as the relevant code now lives in the prelude package.
You do not need to update code using proxy types like Proxy2 and SProxy, because these types have been deprecated but will not be removed until the PureScript 0.15 compiler release. However, if you'd like to get ahead of this change, you should replace all uses of proxy types other than Proxy to use Proxy instead. For example:
-- Code written pre-PureScript 0.14, which will compile with PureScript 0.14
-- but will not compile with PureScript 0.15.
getFoo = Record.get (SProxy :: SProxy "foo")
-- Code written for PureScript 0.14 which will continue to work with
-- PureScript 0.15.
getFoo = Record.get (Proxy :: Proxy "foo")
generics-rep library has been merged into preludeThe Generic type class now lives in the prelude library. This makes it easier for us to provide Generic instances for common types which should have them such as Tuple or Either.
To fix:
You should remove generics-rep as a dependency of your library or application, as most of the relevant code and instances live in the prelude package. Code related to Enum has been moved to the enums package, but you should already depend on this package if you were using it. If you provide deriving via Generic for a type class you've defined, we recommend the following module naming convention: <full module path to module containing class>.Generic.
You should rename any modules that used Generic instances for common type classes, as those modules have been renamed to match this naming convention.
Data.Generic.Rep.Bounded -> Data.Bounded.Generic
Data.Generic.Rep.Enum -> Data.Enum.Generic (in the `enums` package)
Data.Generic.Rep.Eq -> Data.Eq.Generic
Data.Generic.Rep.HeytingAlgebra -> Data.HeytingAlgebra.Generic
Data.Generic.Rep.Monoid -> Data.Monoid.Generic
Data.Generic.Rep.Ord -> Data.Ord.Generic
Data.Generic.Rep.Ring -> Data.Ring.Generic
Data.Generic.Rep.Semigroup -> Data.Semigroup.Generic
Data.Generic.Rep.Semiring -> Data.Semiring.Generic
Data.Generic.Rep.Show -> Data.Show.Generic
The argonaut-generic library has also renamed its modules to match this naming convention:
Data.Argonaut.Decode.Generic.Rep -> Data.Argonaut.Decode.Generic
Data.Argonaut.Encode.Generic.Rep -> Data.Argonaut.Encode.Generic
Data.Argonaut.Types.Generic.Rep -> Data.Argonaut.Types.Generic
globals library has been deprecatedThe globals library provided various functions and values that are in the global scope in JavaScript. The library has been deprecated in particular because PureScript supports multiple backends besides JavaScript, and the relevant functions have been moved or reimplemented in other libraries.
Specifically:
isFinite, parsing, and formatting have been moved to the numbers package, which itself has moved into the core organization.integers package.encodeURIComponent and decodeURIComponent have been moved to the new js-uri package.Global.Unsafe) have been removed altogether.To fix:
You should remove the globals dependency from your library or application.
If you used any Number-related code from the globals package, then you should add a new dependency on numbers and rename your imports:
- import Global (isNan, nan, isFinite, infinity)
+ import Data.Number (isNan, nan, isFinite, infinity)
- import Global (toFixed, toPrecision, toExponential)
+ import Data.Number.Format (toStringWith, fixed, precision, exponential)
-- toFixed, toPrecision, and toExponential can be replaced as follows:
- x = toFixed someInt 4.0
+ x = toStringWith (fixed someInt) 4.0
- y = toPrecision someInt 4.0
+ y = toStringWith (precision someInt) 4.0
- z = toExponential intArg 4.0
+ z = toStringWith (exponential someInt) 4.0
If you used readInt, then you should add a dependency on integers and either use Data.Int.fromString if you were parsing a base-10 integer, or use Data.Int.fromStringAs given a Data.Int.Radix if you were parsing a number with another base:
- import Global (readInt)
+ import Data.Int (binary, fromStringAs)
fromBinaryString :: String -> Maybe Int
- fromBinaryString = readInt 2
+ fromBinaryString = fromStringAs binary
If you used URI encoding and decoding functions, then you should add a dependency on js-uri and update your imports:
- import Global (decodeURI, decodeURIComponent, encodeURI, encodeURIComponent)
+ import JSURI (decodeURI, decodeURIComponent, encodeURI, encodeURIComponent)
If you used any of the unsafe functions exposed by the globals package, you can in most cases use functions from the partials package (like unsafePartial) combined with partial functions like Data.Maybe.fromJust to produce an unsafe function from a safe one.
functors and related packages have changed in order to unify newtypes among the libraries.Implemented in purescript-foldable-traversable#131, purescript-functors#31, purescript-profunctor#41, purescript-distributive#17, purescript-contravariant#30, purescript-bifunctors#22, purescript-tuples#46, purecsript-identity#26, purescript-either#64, and purecsript-const#19.
Mostly discussed in purescript-profunctor#23.
A number of dependencies have been rearranged in the core libraries so that more types can be reused among them (especially newtypes between profunctor and bifunctors) and so that fundamental data types like either and tuple are at the bottom of the dependency hierarchy.
Most of these changes involve instances and should be invisible to user code. However, some functions and modules have changed locations and will require changes in your code if you use them.
To fix:
The lookup function has moved from Data.Tuple to Data.Foldable and the import should be updated accordingly:
- import Data.Tuple (lookup)
+ import Data.Foldable (lookup)
Several modules have moved or been renamed, all from the profunctors and bifunctors packages into the functors package. You'll need to update your imports accordingly:
Data.Profunctor.Clown -> Data.Functor.Clown
Data.Profunctor.Cowrap -> Data.Functor.Flip
Data.Profunctor.Joker -> Data.Functor.Joker
Data.Profunctor.Costar -> Data.Functor.Costar
Data.Bifunctor.Clown -> Data.Functor.Clown
Data.Bifunctor.Flip -> Data.Functor.Flip
Data.Bifunctor.Joker -> Data.Functor.Joker
Data.Bifunctor.Product -> Data.Functor.Product2
The Data.Profunctor.Wrap and Data.Bifunctor.Wrap types have been deleted, as any profunctor or bifunctor should also have a functor instance.
Newtype class now has Coercible as a superclass and no longer has wrap and unwrap class members.Implemented in purescript-newtype#22.
The Newtype class exists to support convenient wrapping and unwrapping and to implement the combinators in the Data.Newtype module. The Newtype class now has Coercible as a superclass, which allows most combinators to now be implemented with coerce from the new safe-coerce library. This makes these combinators zero-cost for a performance gain.
The Newtype class no longer has wrap and unwrap members. Instead, wrap and unwrap are synonyms for coerce and are exported as functions from the module. Most code does not need to change, but if you manually defined a Newtype instance in your code it will no longer compile.
In addition, newtypes that do not have their constructors exported can no longer be unwrapped via the Newtype class. Most code will be unaffected, as giving a Newtype instance to a newtype with hidden constructors was already considered bad practice and usually accidental.
To fix:
Most Newtype instances are derived and will continue to work. If you manually defined a Newtype instance for a type, however, you can simply remove the wrap and unwrap definitions:
+ instance newtypeAdditive :: Newtype (Additive a) a
- instance newtypeAdditive :: Newtype (Additive a) a where
- wrap = Additive
- unwrap (Additive a) = a
Most newtypes either export their constructors and have a Newtype instance, or hide their constructors and do not have the instance. A new compiler warning is raised for types with a Newtype instance but hidden constructors.
To address the compiler warning, you can either:
Newtype instance -- a breaking change, but the correct option if you used hidden constructors to make your newtype a smart constructor.MonadZero class has been deprecated.Implemented in purescript-control#64 with additional discussion in purescript-control#62 and purescript-control#51.
The MonadZero class has been deprecated, as the additional law that it provides (empty >>= f = empty) is already covered by the Monad laws and parametricity. MonadZero will be removed in the next compiler release.
The MonadZero class was already little-used; in the core libraries it only shows up as a superclass of MonadPlus and as a constraint on the guard function. Accordingly, the guard function has been moved from Control.MonadZero to Control.Alternative and the MonadZero m constraint has been replaced with Monad m => Alternative m => ....
To fix:
If you are using the guard function, update your imports:
- import Control.MonadZero (guard)
+ import Control.Alternative (guard)
If you are using the MonadZero m constraint, replace it with Monad m and Alternative m:
- x :: forall m. MonadZero m => ...
+ x :: forall m. Monad m => Alternative m => ...
If you are providing a MonadZero instance, consider removing it, as the class will be removed in a future release.
Note: When you compile core modules that export MonadZero instances, such as Data.List or Data.Maybe, they will yield deprecation warnings until we remove the instances. Those warnings can be hidden by installing psa and compiling with psa --censor-codes=UserDefinedWarning.
Foldable1 class added foldl1 and foldr1 as members.Implemented in purescript-foldable-traversable#121, with additional discussion in purescript-foldable-traversable#110.
The Foldable class provides three ways to fold: from the left via foldl, from the right via foldr, or via foldMap. However, the Foldable1 class only defined two ways to fold, without specifying direction: fold1 and foldMap1. It was possible to implement foldr1 or foldl1 in terms of these functions, but this removed the ability for instances to provide a performant implementation for these folds.
The Foldable1 class now has three type class members: foldl1, foldr1, and foldMap1, mirroring the Foldable class. The fold1 function is no longer a type class member and is instead defined in the module via foldMap1 identity.
This is a breaking change because data types that implement Foldable1 need to update their instances to remove fold1 and add foldl1 and foldr1. Foldable1 provides default implementations that you can use to implement your instances.
To fix:
If you currently define an instance of Foldable1, then you should remove the fold1 implementation and add new implementations for foldl1 and foldr1. You can implement your instance by hand, or you can use one of the default implementations provided by the Data.Semigroup.Foldable module:
foldl1Default for foldl1foldr1Default for foldr1foldMap1DefaultL or foldMapDefaultR for foldMap1.Not all of these default implementations can safely be used together; any unsafe interactions are described in the documentation comments for each of these default functions.
Here's an example of updating a Foldable1 instance:
instance foldable1Coyoneda :: Foldable1 f => Foldable1 (Coyoneda f) where
foldMap1 f = unCoyoneda \k -> foldMap1 (f <<< k)
- fold1 = unCoyoneda \k -> foldMap1 k
+ foldr1 = foldr1Default
+ foldl1 = foldl1Default
Semigroup and Monoid instances for Map have been removed.Implemented in purescript-ordered-collections#38, with additional discussion in purescript-ordered-collections#36 and the Unbiasing the Semigroup instance for Map proposal on Discourse.
The Semigroup instance for Map a was previously left-biased, which meant that it would preserve the first value and throw away the second value when appending. This instance has been removed for several reasons
Semigroup instance for other types like Object a and Maybe a is unbiased, which means that when appending values the instance defers to the underlying Semigroup (ie. Semigroup a => Object a).The Semigroup instance for Map will be restored in PureScript 0.15, but will be unbiased rather than left-biased. The instance is removed in PureScript 0.14 to avoid silently changing the behavior of existing code. Since the Monoid class requires Semigroup, the Monoid instance for Map has also been removed.
To mitigate breaking changes, a new SemigroupMap newtype has been added to the Data.Map module which supplies an unbiased Semigroup instance.
To fix:
If you previously relied on the Monoid instance for Data.Map (ie. you used mempty), then you may be able to simply update your code to use Map.empty directly instead:
import Data.Map as Map
m :: Map Int String
- m = mempty
+ m = Map.empty
If this is not possible and you previously relied on the Semigroup or Monoid instances for Data.Map, then you can use the SemigroupMap newtype to continue using those instances. Note that the behavior of the instances has changed to be unbiased rather than left-biased. You can use the First and Last newtypes to produce left-biased or right-biased behavior, as demonstrated below.
import Data.Map (SemigroupMap(..), singleton)
import Data.Newtype (under2)
import Data.Semigroup.First (First(..))
import Data.Semigroup.Last (Last(..))
> under2 SemigroupMap (<>) (singleton 1 "foo") (singleton 1 "bar")
(fromFoldable [(Tuple 1 "foobar")])
> under2 SemigroupMap (<>) (singleton 1 (First "foo")) (singleton 1 (First "bar"))
(fromFoldable [(Tuple 1 (First "foo"))])
> under2 SemigroupMap (<>) (singleton 1 (Last "foo")) (singleton 1 (Last "bar"))
(fromFoldable [(Tuple 1 (Last "bar"))])
Semigroup instance for foreign objects has changed.Implemented in purescript-foreign-object#23 and purescript-foreign-object#19.
The Semigroup instance for Foreign.Object.Object previously appended values from the right to values from the left, which is in reverse order from how this instance should have been implemented. To demonstrate:
-- these were previously equivalent
singleton k a <> singleton k b == singleton k (b <> a)
-- now these are equivalent, as expected
singleton k a <> singleton k b == singleton k (a <> b)
Code using these instances will now append in the correct order.
To fix:
Most users should not change their code and will now be using the correct instance. However, if you were intentionally relying on the behavior of this instance then you can recover it by using the Data.Monoid.Dual newtype (ie. Object (Dual a)).
Alt instance for ZipList has changed.Implemented in purescript-lists#150.
The previous Alt instance for ZipList relied on a simple append, which doesn't satisfy the Alternative distributivity law:
(f <|> g) <*> x == (f <*> x) <|> (g <*> x)
Using an instance implemented with append, the left-hand side will be a list of length min(length f + length g, x), whereas the right-hand side will be a list of length min(length f, length x) + min(length g, length x). For example, given:
f = g = ZipList ((_ + 1) : Nil)
x = ZipList (0 : Nil)
we get the left-hand side as ZipList (1 : Nil) but the right-hand side ZipList (1 : 1 : Nil).
To fix:
Most users will not need to change their code, as this is a bug fix for incorrect behavior. However, if you were previously relying on the specific behavior of this instance, then you can create your own newtype around List which defines an Alt instance with append:
newtype MyZipList = MyZipList List
instance altMyZipList :: Alt MyZipList where
alt = append
Record.Builder.merge and Record.Builder.union functions have changed.Implemented in purescript-record#73 with additional discussion in purescript-record#55.
The Record.Builder.merge and Record.Builder.union functions have changed to behave more like Record.merge and Record.union, in that fields from the argument will override those of the record being built in the case of overlaps. This is a breaking change because the result types may change -- for example, this:
Builder.build (Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
will now produce the type:
{ x :: Int, y :: String, z :: Boolean }
where previously it would produce the type:
{ x :: Int, y :: Int, z :: Boolean }
To fix:
If you would like to use the new behavior, then your code doesn't need to change. Otherwise, you can use the new Record.Builder.flip function to flip the arguments and recover the old behavior.
val :: { x :: Int, y :: Int, z :: Boolean }
val = Builder.build (Builder.flip Builder.merge { x: 1, y: "y" }) { y: 2, z: true }
Data.Either.fromLeft and Data.Either.fromRight functions are now total functions.Implemented in purescript-either#48 with additional discussion in purescript-either#47 and purescript-either#56.
The Data.Either.fromLeft and Data.Either.fromRight functions are now total functions and take a default value (like Data.Maybe.fromMaybe) instead of partial functions (like Data.Maybe.fromJust).
To fix:
The most common way to use fromLeft and fromRight is to unsafely discharge the Partial constraint using unsafePartial like this:
x = unsafePartial fromLeft $ Left 10
y = unsafePartial fromRight $ Right "hi"
This can be replaced with the new lazy fromLeft' and fromRight' combined with unsafeCrashWith:
x = fromLeft' (\_ -> unsafeCrashWith "Unexpected Right") $ Left 10
x = fromRight' (\_ -> unsafeCrashWith "Unexpected Left") $ Right "hi"
Alternately, you can provide a default value in order to use fromLeft and fromRight safely.
quickcheck library now uses NonEmptyArray and NonEmptyList instead of NonEmpty Array and NonEmpty List.Implemented in purescript-quickcheck#118 with additional discussion in purescript-quickcheck#109.
The NonEmpty Array type predates the NonEmptyArray type, which provides O(n) conversion to and from Array. The QuickCheck library has switched to use NonEmptyArray and NonEmptyList, which affects libraries which create generators using functions like elements or frequency.
To fix:
Instead of constructing a NonEmpty Array for functions like Test.QuickCheck.Gen.elements, you should instead construct a NonEmptyArray.
Here's a common way to construct a NonEmpty Array in an Arbitrary instance:
import Data.NonEmpty ((:|))
instance arbitraryOrdering :: Arbitrary Ordering where
arbitrary = elements $ LT :| [ EQ, GT ]
This can be replaced by this instance, to minimize the difference between the old code and the new code:
import Data.Array.NonEmpty (cons')
instance arbitraryOrdering :: Arbitrary Ordering where
arbitrary = elements $ cons' LT [ EQ, GT ]
Or, for a slightly more performant version which constructs the array and converts it without an intermediate cons:
import Data.Array.NonEmpty (fromArray)
instance arbitraryOrdering :: Arbitrary Ordering where
arbitrary = elements $ unsafePartial fromJust $ fromArray [ LT, EQ, GT ]
unicode library now uses CodePoint instead of Char.Implemented in purescript-unicode#15.
The Char type only works with non-astral characters due to its JavaScript representation, whereas the CodePoint type represents any unicode character. The Unicode library now uses CodePoint for all its string transformations, which is a more correct behavior.
As part of this change, some modules and functions have been renamed.
To fix:
If you were importing functions from Data.Char.Unicode, rename the import to Data.CodePoint.Unicode` instead:
- import Data.Char.Unicode
+ import Data.CodePoint.Unicode
If you were previously using functions which accepted a Char and need to transform that Char into a CodePoint for compatibility, you should use the codePointFromChar function:
- Data.Char.Unicode.isSeparator '\n'
+ Data.CodePoint.Unicode.isSeparator
+ (Data.CodePoint.Unicode.codePointFromChar '\n')
In PureScript 0.14, compilation errors are printed to stderr instead of stdout (#3672, #3839). This is the only change in the compiler that affected tools downstream.
Spago does not require changes to be compatible with PureScript 0.14. You will, however, want to update to a 0.14-compatible package set in your packages.dhall file, starting from psc-0.14.0.
Pulp does not require changes to be compatible with PureScript 0.14. You will, however, need to update your bower.json file to point at new major versions for your dependencies.
The PureScript 0.14 compiler requires updates to some other commonly-used tools in the PureScript community which are outside the core organization.
purescript-psa to version v0.8.0The error-reporting frontend psa is compatible with how the 0.14 compiler reports errors as of version v0.8.0 (#44) and backwards-compatible with the PureScript 0.13.8 compiler as of v0.8.2 (#49).
purescript-language-serverThe PureScript language server, which provides IDE support for editors like VSCode, also requires a version bump to be compatible with PureScript 0.14 error reporting.