diff --git a/docs/man/futhark-pyopencl.rst b/docs/man/futhark-pyopencl.rst
--- a/docs/man/futhark-pyopencl.rst
+++ b/docs/man/futhark-pyopencl.rst
@@ -34,7 +34,7 @@
 OPTIONS
 =======
 
-Accepts the same options as :ref:`futhark-c(1)`.
+Accepts the same options as :ref:`futhark-opencl(1)`.
 
 SEE ALSO
 ========
diff --git a/futhark.cabal b/futhark.cabal
--- a/futhark.cabal
+++ b/futhark.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name:           futhark
-version:        0.22.1
+version:        0.22.2
 synopsis:       An optimising compiler for a functional, array-oriented language.
 
 description:    Futhark is a small programming language designed to be compiled to
@@ -89,6 +89,56 @@
 common common
   ghc-options: -Wall -Wcompat -Wno-incomplete-uni-patterns -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -Wunused-packages
   default-language: Haskell2010
+  default-extensions:
+    OverloadedStrings
+    -- The following extensions correspond to the GHC2021 language,
+    -- but we want to remain compatible with GHC 9.0, so we cannot use
+    -- it just yet.
+    BangPatterns
+    BinaryLiterals
+    ConstrainedClassMethods
+    ConstraintKinds
+    DeriveDataTypeable
+    DeriveFoldable
+    DeriveFunctor
+    DeriveGeneric
+    DeriveLift
+    DeriveTraversable
+    DoAndIfThenElse
+    EmptyCase
+    EmptyDataDecls
+    EmptyDataDeriving
+    ExistentialQuantification
+    ExplicitForAll
+    FlexibleContexts
+    FlexibleInstances
+    ForeignFunctionInterface
+    GADTSyntax
+    GeneralisedNewtypeDeriving
+    HexFloatLiterals
+    ImplicitPrelude
+    ImportQualifiedPost
+    InstanceSigs
+    KindSignatures
+    MonomorphismRestriction
+    MultiParamTypeClasses
+    NamedFieldPuns
+    NamedWildCards
+    NumericUnderscores
+    PatternGuards
+    PolyKinds
+    PostfixOperators
+    RankNTypes
+    RelaxedPolyRec
+    ScopedTypeVariables
+    StandaloneDeriving
+    StandaloneKindSignatures
+    StarIsType
+    TraditionalRecordSyntax
+    TupleSections
+    TypeApplications
+    TypeOperators
+    TypeSynonymInstances
 
 library
   import: common
@@ -160,8 +210,10 @@
       Futhark.CodeGen.Backends.GenericC.CLI
       Futhark.CodeGen.Backends.GenericC.Code
       Futhark.CodeGen.Backends.GenericC.EntryPoints
+      Futhark.CodeGen.Backends.GenericC.Fun
       Futhark.CodeGen.Backends.GenericC.Monad
       Futhark.CodeGen.Backends.GenericC.Options
+      Futhark.CodeGen.Backends.GenericC.Pretty
       Futhark.CodeGen.Backends.GenericC.Server
       Futhark.CodeGen.Backends.GenericC.Types
       Futhark.CodeGen.Backends.GenericPython
@@ -345,7 +397,7 @@
       Futhark.Transform.Rename
       Futhark.Transform.Substitute
       Futhark.Util
-      Futhark.Util.Console
+      Futhark.Util.CMath
       Futhark.Util.IntegralExp
       Futhark.Util.Loc
       Futhark.Util.Log
@@ -357,6 +409,7 @@
       Language.Futhark
       Language.Futhark.Core
       Language.Futhark.Interpreter
+      Language.Futhark.Interpreter.Values
       Language.Futhark.FreeVars
       Language.Futhark.Parser
       Language.Futhark.Parser.Monad
@@ -423,7 +476,7 @@
     , haskeline
     , language-c-quote >= 0.12
     , lens
-    , lsp == 1.5.*
+    , lsp >= 1.5.0
     , mainland-pretty >=0.7.1
     , cmark-gfm >=0.2.1
     , megaparsec >=9.0.0
@@ -447,6 +500,8 @@
     , zlib >=0.6.1.2
     , statistics
     , mwc-random
+    , prettyprinter >= 1.7
+    , prettyprinter-ansi-terminal >= 1.1
 
 executable futhark
   import: common
diff --git a/prelude/math.fut b/prelude/math.fut
--- a/prelude/math.fut
+++ b/prelude/math.fut
@@ -242,6 +242,10 @@
   -- | The difference between 1.0 and the next larger representable
   -- number.
   val epsilon: t
+
+  -- | Produces the next representable number from `x` in the
+  -- direction of `y`.
+  val nextafter : (x: t) -> (y: t) -> t
 }
 
 -- | Boolean numbers.  When converting from a number to `bool`, 0 is
@@ -953,6 +957,8 @@
 
   def round = intrinsics.round64
 
+  def nextafter x y = intrinsics.nextafter64 (x,y)
+
   def to_bits (x: f64): u64 = u64m.i64 (intrinsics.to_bits64 x)
   def from_bits (x: u64): f64 = intrinsics.from_bits64 (intrinsics.sign_i64 x)
 
@@ -1065,6 +1071,8 @@
 
   def round = intrinsics.round32
 
+  def nextafter x y = intrinsics.nextafter32 (x,y)
+
   def to_bits (x: f32): u32 = u32m.i32 (intrinsics.to_bits32 x)
   def from_bits (x: u32): f32 = intrinsics.from_bits32 (intrinsics.sign_i32 x)
 
@@ -1180,6 +1188,8 @@
   def trunc (x: f16) : f16 = i16 (i16m.f16 x)
 
   def round = intrinsics.round16
+
+  def nextafter x y = intrinsics.nextafter16 (x,y)
 
   def to_bits (x: f16): u16 = u16m.i16 (intrinsics.to_bits16 x)
   def from_bits (x: u16): f16 = intrinsics.from_bits16 (intrinsics.sign_i16 x)
diff --git a/prelude/soacs.fut b/prelude/soacs.fut
--- a/prelude/soacs.fut
+++ b/prelude/soacs.fut
@@ -128,8 +128,7 @@
 -- **Span:** *O(n ✕ W(op))* in the worst case (all updates to same
 -- position), but *O(W(op))* in the best case.
 --
--- In practice, the *O(n)* behaviour only occurs if *m* is also very
--- large.
+-- In practice, linear span only occurs if *k* is also very large.
 def hist 'a [n] (op: a -> a -> a) (ne: a) (k: i64) (is: [n]i64) (as: [n]a) : *[k]a =
   intrinsics.hist_1d (1, map (\_ -> ne) (0..1..<k), op, ne, is, as)
 
@@ -142,8 +141,7 @@
 -- **Span:** *O(n ✕ W(op))* in the worst case (all updates to same
 -- position), but *O(W(op))* in the best case.
 --
--- In practice, the *O(n)* behaviour only occurs if *m* is also very
--- large.
+-- In practice, linear span only occurs if *k* is also very large.
 def reduce_by_index 'a [k] [n] (dest : *[k]a) (f : a -> a -> a) (ne : a) (is : [n]i64) (as : [n]a) : *[k]a =
   intrinsics.hist_1d (1, dest, f, ne, is, as)
 
diff --git a/rts/c/half.h b/rts/c/half.h
--- a/rts/c/half.h
+++ b/rts/c/half.h
@@ -235,4 +235,22 @@
   return u.y;
 }
 
+static uint16_t halfbitsnextafter(uint16_t from, uint16_t to) {
+  int fabs = from & 0x7FFF, tabs = to & 0x7FFF;
+  if(fabs > 0x7C00 || tabs > 0x7C00) {
+    return ((from&0x7FFF)>0x7C00) ? (from|0x200) : (to|0x200);
+  }
+  if(from == to || !(fabs|tabs)) {
+    return to;
+  }
+  if(!fabs) {
+    return (to&0x8000)+1;
+  }
+  unsigned int out =
+    from +
+    (((from>>15)^(unsigned int)((from^(0x8000|(0x8000-(from>>15))))<(to^(0x8000|(0x8000-(to>>15))))))<<1)
+    - 1;
+  return out;
+}
+
 // End of half.h.
diff --git a/rts/c/scalar.h b/rts/c/scalar.h
--- a/rts/c/scalar.h
+++ b/rts/c/scalar.h
@@ -1925,6 +1925,10 @@
   return ceil(x);
 }
 
+static inline float futrts_nextafter32(float x, float y) {
+  return nextafter(x, y);
+}
+
 static inline float futrts_lerp32(float v0, float v1, float t) {
   return mix(v0, v1, t);
 }
@@ -2117,6 +2121,16 @@
   return ceil(x);
 }
 
+extern "C" unmasked uniform float nextafterf(uniform float x, uniform float y);
+static inline float futrts_nextafter32(float x, float y) {
+  float res;
+  foreach_active (i) {
+    uniform float r = nextafterf(extract(x, i), extract(y, i));
+    res = insert(res, i, r);
+  }
+  return res;
+}
+
 static inline float futrts_lerp32(float v0, float v1, float t) {
   return v0 + (v1 - v0) * t;
 }
@@ -2243,6 +2257,10 @@
   return ceilf(x);
 }
 
+static inline float futrts_nextafter32(float x, float y) {
+  return nextafterf(x, y);
+}
+
 static inline float futrts_lerp32(float v0, float v1, float t) {
   return v0 + (v1 - v0) * t;
 }
@@ -2529,6 +2547,16 @@
   return ceil(x);
 }
 
+extern "C" unmasked uniform double nextafter(uniform float x, uniform double y);
+static inline float futrts_nextafter64(double x, double y) {
+  double res;
+  foreach_active (i) {
+    uniform double r = nextafter(extract(x, i), extract(y, i));
+    res = insert(res, i, r);
+  }
+  return res;
+}
+
 static inline double futrts_floor64(double x) {
   return floor(x);
 }
@@ -2841,6 +2869,10 @@
 
 static inline double futrts_ceil64(double x) {
   return ceil(x);
+}
+
+static inline float futrts_nextafter64(float x, float y) {
+  return nextafter(x, y);
 }
 
 static inline double futrts_floor64(double x) {
diff --git a/rts/c/scalar_f16.h b/rts/c/scalar_f16.h
--- a/rts/c/scalar_f16.h
+++ b/rts/c/scalar_f16.h
@@ -317,6 +317,10 @@
   return ceil(x);
 }
 
+static inline f16 futrts_nextafter16(f16 x, f16 y) {
+  return nextafter(x, y);
+}
+
 static inline f16 futrts_lerp16(f16 v0, f16 v1, f16 t) {
   return mix(v0, v1, t);
 }
@@ -463,6 +467,10 @@
   return (float16)ceil((float)x);
 }
 
+static inline f16 futrts_nextafter16(f16 x, f16 y) {
+  return (float16)futrts_nextafter32((float)x, (float) y);
+}
+
 static inline f16 futrts_lerp16(f16 v0, f16 v1, f16 t) {
   return v0 + (v1 - v0) * t;
 }
@@ -589,6 +597,10 @@
   return hceil(x);
 }
 
+static inline f16 futrts_nextafter16(f16 x, f16 y) {
+  return __ushort_as_half(halfbitsnextafter(__half_as_ushort(x), __half_as_ushort(y)));
+}
+
 static inline f16 futrts_lerp16(f16 v0, f16 v1, f16 t) {
   return v0 + (v1 - v0) * t;
 }
@@ -781,6 +793,10 @@
 
 static inline f16 futrts_ceil16(f16 x) {
   return futrts_ceil32(x);
+}
+
+static inline f16 futrts_nextafter16(f16 x, f16 y) {
+  return halfbits2float(halfbitsnextafter(float2halfbits(x), float2halfbits(y)));
 }
 
 static inline f16 futrts_lerp16(f16 v0, f16 v1, f16 t) {
diff --git a/rts/python/opencl.py b/rts/python/opencl.py
--- a/rts/python/opencl.py
+++ b/rts/python/opencl.py
@@ -74,6 +74,7 @@
 
 def initialise_opencl_object(self,
                              program_src='',
+                             build_options=[],
                              command_queue=None,
                              interactive=False,
                              platform_pref=None,
@@ -214,8 +215,6 @@
     # compiler should provide us with the variables to which
     # parameters are mapped.
     if (len(program_src) >= 0):
-        build_options = []
-
         build_options += ["-DLOCKSTEP_WIDTH={}".format(lockstep_width)]
 
         build_options += ["-D{}={}".format(s.
diff --git a/rts/python/scalar.py b/rts/python/scalar.py
--- a/rts/python/scalar.py
+++ b/rts/python/scalar.py
@@ -508,6 +508,9 @@
 def futhark_floor64(x):
   return np.floor(x)
 
+def futhark_nextafter64(x, y):
+  return np.nextafter(x, y)
+
 def futhark_isnan64(x):
   return np.isnan(x)
 
@@ -603,6 +606,9 @@
 def futhark_floor32(x):
   return np.floor(x)
 
+def futhark_nextafter32(x, y):
+  return np.nextafter(x, y)
+
 def futhark_isnan32(x):
   return np.isnan(x)
 
@@ -697,6 +703,9 @@
 
 def futhark_floor16(x):
   return np.floor(x)
+
+def futhark_nextafter16(x, y):
+  return np.nextafter(x, y)
 
 def futhark_isnan16(x):
   return np.isnan(x)
diff --git a/src/Futhark/AD/Derivatives.hs b/src/Futhark/AD/Derivatives.hs
--- a/src/Futhark/AD/Derivatives.hs
+++ b/src/Futhark/AD/Derivatives.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Partial derivatives of scalar Futhark operations and built-in functions.
 module Futhark.AD.Derivatives
   ( pdBuiltin,
@@ -371,6 +369,9 @@
 pdBuiltin "floor16" [_] = Just [fConst Float16 0]
 pdBuiltin "floor32" [_] = Just [fConst Float32 0]
 pdBuiltin "floor64" [_] = Just [fConst Float64 0]
+pdBuiltin "nextafter16" [_, _] = Just [fConst Float16 1, fConst Float16 0]
+pdBuiltin "nextafter32" [_, _] = Just [fConst Float32 1, fConst Float32 0]
+pdBuiltin "nextafter64" [_, _] = Just [fConst Float64 1, fConst Float64 0]
 pdBuiltin "clz8" [_] = Just [iConst Int32 0]
 pdBuiltin "clz16" [_] = Just [iConst Int32 0]
 pdBuiltin "clz32" [_] = Just [iConst Int32 0]
diff --git a/src/Futhark/AD/Fwd.hs b/src/Futhark/AD/Fwd.hs
--- a/src/Futhark/AD/Fwd.hs
+++ b/src/Futhark/AD/Fwd.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -12,10 +7,10 @@
 import Control.Monad.RWS.Strict
 import Control.Monad.State.Strict
 import Data.Bifunctor (second)
-import qualified Data.Kind
+import Data.Kind qualified
 import Data.List (transpose)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.AD.Derivatives
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.Builder
@@ -24,7 +19,7 @@
 
 zeroTan :: Type -> ADM SubExp
 zeroTan (Prim t) = pure $ constant $ blankPrimValue t
-zeroTan t = error $ "zeroTan on non-primitive type: " ++ pretty t
+zeroTan t = error $ "zeroTan on non-primitive type: " ++ prettyString t
 
 zeroExp :: Type -> Exp SOACS
 zeroExp (Prim pt) =
@@ -262,7 +257,7 @@
     Rotate rots arr -> do
       arr_tan <- tangent arr
       addStm $ Let pat_tan aux $ BasicOp $ Rotate rots arr_tan
-    _ -> error $ "basicFwd: Unsupported op " ++ pretty op
+    _ -> error $ "basicFwd: Unsupported op " ++ prettyString op
 
 fwdLambda :: Lambda SOACS -> ADM (Lambda SOACS)
 fwdLambda l@(Lambda params body ret) =
@@ -401,7 +396,7 @@
       let arg_pes = zipWith primExpFromSubExp argts (map fst args)
       case pdBuiltin f arg_pes of
         Nothing ->
-          error $ "No partial derivative defined for builtin function: " ++ pretty f
+          error $ "No partial derivative defined for builtin function: " ++ prettyString f
         Just derivs -> do
           let convertTo tt e
                 | e_t == tt = e
@@ -411,7 +406,7 @@
                       (FloatType tt', FloatType ft) -> ConvOpExp (FPConv ft tt') e
                       (Bool, FloatType ft) -> ConvOpExp (FToB ft) e
                       (FloatType tt', Bool) -> ConvOpExp (BToF tt') e
-                      _ -> error $ "fwdStm.convertTo: " ++ pretty (f, tt, e_t)
+                      _ -> error $ "fwdStm.convertTo: " ++ prettyString (f, tt, e_t)
                 where
                   e_t = primExpType e
           zipWithM_ (letBindNames . pure) (patNames pat_tan)
@@ -459,7 +454,7 @@
     removeIndexTans _ ps = ps
 fwdStm (Let pat aux (Op soac)) = fwdSOAC pat aux soac
 fwdStm stm =
-  error $ "unhandled forward mode AD for Stm: " ++ pretty stm ++ "\n" ++ show stm
+  error $ "unhandled forward mode AD for Stm: " ++ prettyString stm ++ "\n" ++ show stm
 
 fwdBody :: Body SOACS -> ADM (Body SOACS)
 fwdBody (Body _ stms res) = buildBody_ $ do
diff --git a/src/Futhark/AD/Rev.hs b/src/Futhark/AD/Rev.hs
--- a/src/Futhark/AD/Rev.hs
+++ b/src/Futhark/AD/Rev.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- Naming scheme:
@@ -15,7 +11,7 @@
 import Control.Monad
 import Data.List ((\\))
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.AD.Derivatives
 import Futhark.AD.Rev.Loop
 import Futhark.AD.Rev.Monad
@@ -30,7 +26,7 @@
 
 patName :: Pat Type -> ADM VName
 patName (Pat [pe]) = pure $ patElemName pe
-patName pat = error $ "Expected single-element pattern: " ++ pretty pat
+patName pat = error $ "Expected single-element pattern: " ++ prettyString pat
 
 -- The vast majority of BasicOps require no special treatment in the
 -- forward pass and produce one value (and hence one adjoint).  We
@@ -252,12 +248,12 @@
           convert (FloatType ft) (FloatType tt) = ConvOpExp (FPConv ft tt)
           convert Bool (FloatType tt) = ConvOpExp (BToF tt)
           convert (FloatType ft) Bool = ConvOpExp (FToB ft)
-          convert ft tt = error $ "diffStm.convert: " ++ pretty (f, ft, tt)
+          convert ft tt = error $ "diffStm.convert: " ++ prettyString (f, ft, tt)
 
       contribs <-
         case pdBuiltin f arg_pes of
           Nothing ->
-            error $ "No partial derivative defined for builtin function: " ++ pretty f
+            error $ "No partial derivative defined for builtin function: " ++ prettyString f
           Just derivs ->
             forM (zip derivs argts) $ \(deriv, argt) ->
               letExp "contrib" <=< toExp . convert ret argt $ pat_adj' ~*~ deriv
@@ -313,7 +309,7 @@
         let body' = Body () stms $ take (length inputs) res <> takeLast (length get_adjs_for) res
         ts' <- mapM lookupType get_adjs_for
         pure $ Lambda params body' $ take (length inputs) ts <> ts'
-diffStm stm _ = error $ "diffStm unhandled:\n" ++ pretty stm
+diffStm stm _ = error $ "diffStm unhandled:\n" ++ prettyString stm
 
 diffStms :: Stms SOACS -> ADM ()
 diffStms all_stms
diff --git a/src/Futhark/AD/Rev/Loop.hs b/src/Futhark/AD/Rev/Loop.hs
--- a/src/Futhark/AD/Rev/Loop.hs
+++ b/src/Futhark/AD/Rev/Loop.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE DeriveTraversable #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.AD.Rev.Loop (diffLoop, stripmineStms) where
@@ -8,10 +5,10 @@
 import Control.Monad
 import Data.Foldable (toList)
 import Data.List (nub, (\\))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Futhark.AD.Rev.Monad
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.Builder
 import Futhark.IR.Aliases (consumedInStms)
@@ -38,7 +35,7 @@
   a
 bindForLoop (DoLoop val_pats form@(ForLoop i it bound loop_vars) body) f =
   f val_pats form i it bound loop_vars body
-bindForLoop e _ = error $ "bindForLoop: not a for-loop:\n" <> pretty e
+bindForLoop e _ = error $ "bindForLoop: not a for-loop:\n" <> prettyString e
 
 -- | A convenience function to rename a for-loop and then bind the
 -- renamed components.
@@ -94,7 +91,7 @@
       pure (pure (subExpRes bound_plus_one) <> bodyResult body)
   res <- letTupExp' "loop" $ DoLoop ((bound_param, bound_init) : val_pats) (WhileLoop b) body'
   pure $ head res
-computeWhileIters e = error $ "convertWhileIters: not a while-loop:\n" <> pretty e
+computeWhileIters e = error $ "convertWhileIters: not a while-loop:\n" <> prettyString e
 
 -- | Converts a 'WhileLoop' into a 'ForLoop'. Requires that the
 -- surrounding 'DoLoop' is annotated with a @#[bound(n)]@ attribute,
@@ -113,7 +110,7 @@
             (resultBodyM $ map (Var . paramName . fst) val_pats)
         ]
     pure $ DoLoop val_pats (ForLoop i Int64 bound_se mempty) body'
-convertWhileLoop _ e = error $ "convertWhileLoopBound: not a while-loop:\n" <> pretty e
+convertWhileLoop _ e = error $ "convertWhileLoopBound: not a while-loop:\n" <> prettyString e
 
 -- | @nestifyLoop n bound loop@ transforms a loop into a depth-@n@ loop nest
 -- of @bound@-iteration loops. This transformation does not preserve
diff --git a/src/Futhark/AD/Rev/Map.hs b/src/Futhark/AD/Rev/Map.hs
--- a/src/Futhark/AD/Rev/Map.hs
+++ b/src/Futhark/AD/Rev/Map.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.AD.Rev.Map (vjpMap) where
diff --git a/src/Futhark/AD/Rev/Monad.hs b/src/Futhark/AD/Rev/Monad.hs
--- a/src/Futhark/AD/Rev/Monad.hs
+++ b/src/Futhark/AD/Rev/Monad.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- Naming scheme:
@@ -61,9 +56,9 @@
 import Control.Monad.State.Strict
 import Data.Bifunctor (second)
 import Data.List (foldl')
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.Builder
 import Futhark.IR.Aliases (consumedInStms)
@@ -78,7 +73,7 @@
   BasicOp $ SubExp $ Constant $ blankPrimValue pt
 zeroExp (Array pt shape _) =
   BasicOp $ Replicate shape $ Constant $ blankPrimValue pt
-zeroExp t = error $ "zeroExp: " ++ pretty t
+zeroExp t = error $ "zeroExp: " ++ prettyString t
 
 onePrim :: PrimType -> PrimValue
 onePrim (IntType it) = IntValue $ intValue it (1 :: Int)
@@ -90,7 +85,7 @@
 oneExp (Prim t) = BasicOp $ SubExp $ constant $ onePrim t
 oneExp (Array pt shape _) =
   BasicOp $ Replicate shape $ Constant $ onePrim pt
-oneExp t = error $ "oneExp: " ++ pretty t
+oneExp t = error $ "oneExp: " ++ prettyString t
 
 -- | Whether 'Sparse' should check bounds or assume they are correct.
 -- The latter results in simpler code.
@@ -271,7 +266,7 @@
     onConsumed v = do
       v_t <- lookupType v
       case v_t of
-        Acc {} -> error $ "copyConsumedArrsInBody: Acc " <> pretty v
+        Acc {} -> error $ "copyConsumedArrsInBody: Acc " <> prettyString v
         Array {} -> M.singleton v <$> letExp (baseString v <> "_ad_copy") (BasicOp $ Copy v)
         _ -> pure mempty
 
@@ -360,7 +355,7 @@
       lam <- addLambda $ rowType x_t
       pure $ Op $ Screma (arraySize 0 x_t) [x, y] (mapSOAC lam)
     _ ->
-      error $ "addExp: unexpected type: " ++ pretty x_t
+      error $ "addExp: unexpected type: " ++ prettyString x_t
 
 lookupAdj :: VName -> ADM Adj
 lookupAdj v = do
diff --git a/src/Futhark/AD/Rev/Reduce.hs b/src/Futhark/AD/Rev/Reduce.hs
--- a/src/Futhark/AD/Rev/Reduce.hs
+++ b/src/Futhark/AD/Rev/Reduce.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.AD.Rev.Reduce
diff --git a/src/Futhark/AD/Rev/SOAC.hs b/src/Futhark/AD/Rev/SOAC.hs
--- a/src/Futhark/AD/Rev/SOAC.hs
+++ b/src/Futhark/AD/Rev/SOAC.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.AD.Rev.SOAC (vjpSOAC) where
@@ -87,4 +83,4 @@
 vjpSOAC ops pat aux (Scatter w lam ass written_info) m =
   vjpScatter ops pat aux (w, lam, ass, written_info) m
 vjpSOAC _ _ _ soac _ =
-  error $ "vjpSOAC unhandled:\n" ++ pretty soac
+  error $ "vjpSOAC unhandled:\n" ++ prettyString soac
diff --git a/src/Futhark/AD/Rev/Scatter.hs b/src/Futhark/AD/Rev/Scatter.hs
--- a/src/Futhark/AD/Rev/Scatter.hs
+++ b/src/Futhark/AD/Rev/Scatter.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.AD.Rev.Scatter (vjpScatter) where
diff --git a/src/Futhark/Actions.hs b/src/Futhark/Actions.hs
--- a/src/Futhark/Actions.hs
+++ b/src/Futhark/Actions.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | All (almost) compiler pipelines end with an 'Action', which does
 -- something with the result of the pipeline.
 module Futhark.Actions
@@ -30,26 +27,26 @@
 import Control.Monad.IO.Class
 import Data.List (intercalate)
 import Data.Maybe (fromMaybe)
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Futhark.Analysis.Alias
 import Futhark.Analysis.CallGraph (buildCallGraph)
-import qualified Futhark.Analysis.Interference as Interference
-import qualified Futhark.Analysis.LastUse as LastUse
-import qualified Futhark.Analysis.MemAlias as MemAlias
+import Futhark.Analysis.Interference qualified as Interference
+import Futhark.Analysis.LastUse qualified as LastUse
+import Futhark.Analysis.MemAlias qualified as MemAlias
 import Futhark.Analysis.Metrics
-import qualified Futhark.CodeGen.Backends.CCUDA as CCUDA
-import qualified Futhark.CodeGen.Backends.COpenCL as COpenCL
-import qualified Futhark.CodeGen.Backends.MulticoreC as MulticoreC
-import qualified Futhark.CodeGen.Backends.MulticoreISPC as MulticoreISPC
-import qualified Futhark.CodeGen.Backends.MulticoreWASM as MulticoreWASM
-import qualified Futhark.CodeGen.Backends.PyOpenCL as PyOpenCL
-import qualified Futhark.CodeGen.Backends.SequentialC as SequentialC
-import qualified Futhark.CodeGen.Backends.SequentialPython as SequentialPy
-import qualified Futhark.CodeGen.Backends.SequentialWASM as SequentialWASM
-import qualified Futhark.CodeGen.ImpGen.GPU as ImpGenGPU
-import qualified Futhark.CodeGen.ImpGen.Multicore as ImpGenMulticore
-import qualified Futhark.CodeGen.ImpGen.Sequential as ImpGenSequential
+import Futhark.CodeGen.Backends.CCUDA qualified as CCUDA
+import Futhark.CodeGen.Backends.COpenCL qualified as COpenCL
+import Futhark.CodeGen.Backends.MulticoreC qualified as MulticoreC
+import Futhark.CodeGen.Backends.MulticoreISPC qualified as MulticoreISPC
+import Futhark.CodeGen.Backends.MulticoreWASM qualified as MulticoreWASM
+import Futhark.CodeGen.Backends.PyOpenCL qualified as PyOpenCL
+import Futhark.CodeGen.Backends.SequentialC qualified as SequentialC
+import Futhark.CodeGen.Backends.SequentialPython qualified as SequentialPy
+import Futhark.CodeGen.Backends.SequentialWASM qualified as SequentialWASM
+import Futhark.CodeGen.ImpGen.GPU qualified as ImpGenGPU
+import Futhark.CodeGen.ImpGen.Multicore qualified as ImpGenMulticore
+import Futhark.CodeGen.ImpGen.Sequential qualified as ImpGenSequential
 import Futhark.Compiler.CLI
 import Futhark.IR
 import Futhark.IR.GPUMem (GPUMem)
@@ -62,7 +59,7 @@
 import System.Directory
 import System.Exit
 import System.FilePath
-import qualified System.Info
+import System.Info qualified
 
 -- | Print the result to stdout.
 printAction :: ASTRep rep => Action rep
@@ -70,7 +67,7 @@
   Action
     { actionName = "Prettyprint",
       actionDescription = "Prettyprint the resulting internal representation on standard output.",
-      actionProcedure = liftIO . putStrLn . pretty
+      actionProcedure = liftIO . putStrLn . prettyString
     }
 
 -- | Print the result to stdout, alias annotations.
@@ -79,7 +76,7 @@
   Action
     { actionName = "Prettyprint",
       actionDescription = "Prettyprint the resulting internal representation on standard output.",
-      actionProcedure = liftIO . putStrLn . pretty . aliasAnalysis
+      actionProcedure = liftIO . putStrLn . prettyString . aliasAnalysis
     }
 
 -- | Print last use information to stdout.
@@ -88,7 +85,7 @@
   Action
     { actionName = "print last use gpu",
       actionDescription = "Print last use information on gpu.",
-      actionProcedure = liftIO . putStrLn . pretty . LastUse.analyseGPUMem
+      actionProcedure = liftIO . print . LastUse.analyseGPUMem
     }
 
 -- | Print interference information to stdout.
@@ -97,7 +94,7 @@
   Action
     { actionName = "print interference gpu",
       actionDescription = "Print interference information on gpu.",
-      actionProcedure = liftIO . putStrLn . pretty . Interference.analyseProgGPU
+      actionProcedure = liftIO . print . Interference.analyseProgGPU
     }
 
 -- | Print memory alias information to stdout
@@ -106,7 +103,7 @@
   Action
     { actionName = "print mem alias gpu",
       actionDescription = "Print memory alias information on gpu.",
-      actionProcedure = liftIO . putStrLn . pretty . MemAlias.analyzeGPUMem
+      actionProcedure = liftIO . print . MemAlias.analyzeGPUMem
     }
 
 -- | Print call graph to stdout.
@@ -115,7 +112,7 @@
   Action
     { actionName = "call-graph",
       actionDescription = "Prettyprint the callgraph of the result to standard output.",
-      actionProcedure = liftIO . putStrLn . pretty . buildCallGraph
+      actionProcedure = liftIO . putStrLn . prettyString . buildCallGraph
     }
 
 -- | Print metrics about AST node counts to stdout.
@@ -133,7 +130,7 @@
   Action
     { actionName = "Compile imperative",
       actionDescription = "Translate program into imperative IL and write it on standard output.",
-      actionProcedure = liftIO . putStrLn . pretty . snd <=< ImpGenSequential.compileProg
+      actionProcedure = liftIO . putStrLn . prettyString . snd <=< ImpGenSequential.compileProg
     }
 
 -- | Convert the program to GPU ImpCode and print it to stdout.
@@ -142,7 +139,7 @@
   Action
     { actionName = "Compile imperative kernels",
       actionDescription = "Translate program into imperative IL with kernels and write it on standard output.",
-      actionProcedure = liftIO . putStrLn . pretty . snd <=< ImpGenGPU.compileProgOpenCL
+      actionProcedure = liftIO . putStrLn . prettyString . snd <=< ImpGenGPU.compileProgOpenCL
     }
 
 -- | Convert the program to CPU multicore ImpCode and print it to stdout.
@@ -151,7 +148,7 @@
   Action
     { actionName = "Compile to imperative multicore",
       actionDescription = "Translate program into imperative multicore IL and write it on standard output.",
-      actionProcedure = liftIO . putStrLn . pretty . snd <=< ImpGenMulticore.compileProg
+      actionProcedure = liftIO . putStrLn . prettyString . snd <=< ImpGenMulticore.compileProg
     }
 
 -- Lines that we prepend (in comments) to generated code.
diff --git a/src/Futhark/Analysis/Alias.hs b/src/Futhark/Analysis/Alias.hs
--- a/src/Futhark/Analysis/Alias.hs
+++ b/src/Futhark/Analysis/Alias.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Alias analysis of a full Futhark program.  Takes as input a
@@ -22,7 +21,7 @@
 where
 
 import Data.List (foldl')
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.IR.Aliases
 
 -- | Perform alias analysis on a Futhark program.
diff --git a/src/Futhark/Analysis/CallGraph.hs b/src/Futhark/Analysis/CallGraph.hs
--- a/src/Futhark/Analysis/CallGraph.hs
+++ b/src/Futhark/Analysis/CallGraph.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | This module exports functionality for generating a call graph of
 -- an Futhark program.
 module Futhark.Analysis.CallGraph
@@ -15,9 +13,9 @@
 
 import Control.Monad.Writer.Strict
 import Data.List (foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe (isJust)
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.SOACS
 import Futhark.Util.Pretty
 
@@ -141,17 +139,17 @@
         }
 
 instance Pretty FunCalls where
-  ppr = stack . map f . M.toList . fcMap
+  pretty = stack . map f . M.toList . fcMap
     where
-      f (x, (attrs, y)) = "=>" <+> ppr y <+> parens ("at" <+> ppr x <+> ppr attrs)
+      f (x, (attrs, y)) = "=>" <+> pretty y <+> parens ("at" <+> pretty x <+> pretty attrs)
 
 instance Pretty CallGraph where
-  ppr (CallGraph fg cg) =
+  pretty (CallGraph fg cg) =
     stack $
       punctuate line $
         ppFunCalls ("called at top level", cg) : map ppFunCalls (M.toList fg)
     where
       ppFunCalls (f, fcalls) =
-        ppr f
-          </> text (map (const '=') (nameToString f))
-          </> indent 2 (ppr fcalls)
+        pretty f
+          </> pretty (map (const '=') (nameToString f))
+          </> indent 2 (pretty fcalls)
diff --git a/src/Futhark/Analysis/DataDependencies.hs b/src/Futhark/Analysis/DataDependencies.hs
--- a/src/Futhark/Analysis/DataDependencies.hs
+++ b/src/Futhark/Analysis/DataDependencies.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Facilities for inspecting the data dependencies of a program.
 module Futhark.Analysis.DataDependencies
   ( Dependencies,
@@ -8,8 +6,8 @@
   )
 where
 
-import qualified Data.List as L
-import qualified Data.Map.Strict as M
+import Data.List qualified as L
+import Data.Map.Strict qualified as M
 import Futhark.IR
 
 -- | A mapping from a variable name @v@, to those variables on which
diff --git a/src/Futhark/Analysis/HORep/MapNest.hs b/src/Futhark/Analysis/HORep/MapNest.hs
--- a/src/Futhark/Analysis/HORep/MapNest.hs
+++ b/src/Futhark/Analysis/HORep/MapNest.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Analysis.HORep.MapNest
@@ -15,13 +13,13 @@
 where
 
 import Data.List (find)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Analysis.HORep.SOAC (SOAC)
-import qualified Futhark.Analysis.HORep.SOAC as SOAC
+import Futhark.Analysis.HORep.SOAC qualified as SOAC
 import Futhark.Construct
 import Futhark.IR hiding (typeOf)
-import qualified Futhark.IR.SOACS.SOAC as Futhark
+import Futhark.IR.SOACS.SOAC qualified as Futhark
 import Futhark.Transform.Substitute
 
 data Nesting rep = Nesting
diff --git a/src/Futhark/Analysis/HORep/SOAC.hs b/src/Futhark/Analysis/HORep/SOAC.hs
--- a/src/Futhark/Analysis/HORep/SOAC.hs
+++ b/src/Futhark/Analysis/HORep/SOAC.hs
@@ -72,7 +72,7 @@
 
 import Data.Foldable as Foldable
 import Data.Maybe
-import qualified Data.Sequence as Seq
+import Data.Sequence qualified as Seq
 import Futhark.Construct hiding (toExp)
 import Futhark.IR hiding
   ( Iota,
@@ -81,17 +81,17 @@
     Reshape,
     typeOf,
   )
-import qualified Futhark.IR as Futhark
+import Futhark.IR qualified as Futhark
 import Futhark.IR.SOACS.SOAC
   ( HistOp (..),
     ScremaForm (..),
     scremaType,
   )
-import qualified Futhark.IR.SOACS.SOAC as Futhark
+import Futhark.IR.SOACS.SOAC qualified as Futhark
 import Futhark.Transform.Rename (renameLambda)
 import Futhark.Transform.Substitute
-import Futhark.Util.Pretty (ppr, text)
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty (pretty)
+import Futhark.Util.Pretty qualified as PP
 
 -- | A single, simple transformation.  If you want several, don't just
 -- create a list, use 'ArrayTransforms' instead.
@@ -388,30 +388,30 @@
   deriving (Eq, Show)
 
 instance PP.Pretty Input where
-  ppr (Input (ArrayTransforms ts) arr _) = foldl f (ppr arr) ts
+  pretty (Input (ArrayTransforms ts) arr _) = foldl f (pretty arr) ts
     where
       f e (Rearrange cs perm) =
-        text "rearrange" <> ppr cs <> PP.apply [PP.apply (map ppr perm), e]
+        "rearrange" <> pretty cs <> PP.apply [PP.apply (map pretty perm), e]
       f e (Reshape cs ReshapeArbitrary shape) =
-        text "reshape" <> ppr cs <> PP.apply [ppr shape, e]
+        "reshape" <> pretty cs <> PP.apply [pretty shape, e]
       f e (ReshapeOuter cs ReshapeArbitrary shape) =
-        text "reshape_outer" <> ppr cs <> PP.apply [ppr shape, e]
+        "reshape_outer" <> pretty cs <> PP.apply [pretty shape, e]
       f e (ReshapeInner cs ReshapeArbitrary shape) =
-        text "reshape_inner" <> ppr cs <> PP.apply [ppr shape, e]
+        "reshape_inner" <> pretty cs <> PP.apply [pretty shape, e]
       f e (Reshape cs ReshapeCoerce shape) =
-        text "coerce" <> ppr cs <> PP.apply [ppr shape, e]
+        "coerce" <> pretty cs <> PP.apply [pretty shape, e]
       f e (ReshapeOuter cs ReshapeCoerce shape) =
-        text "coerce_outer" <> ppr cs <> PP.apply [ppr shape, e]
+        "coerce_outer" <> pretty cs <> PP.apply [pretty shape, e]
       f e (ReshapeInner cs ReshapeCoerce shape) =
-        text "coerce_inner" <> ppr cs <> PP.apply [ppr shape, e]
+        "coerce_inner" <> pretty cs <> PP.apply [pretty shape, e]
       f e (Replicate cs ne) =
-        text "replicate" <> ppr cs <> PP.apply [ppr ne, e]
+        "replicate" <> pretty cs <> PP.apply [pretty ne, e]
 
 instance PrettyRep rep => PP.Pretty (SOAC rep) where
-  ppr (Screma w form arrs) = Futhark.ppScrema w arrs form
-  ppr (Hist len ops bucket_fun imgs) = Futhark.ppHist len imgs ops bucket_fun
-  ppr (Stream w lam nes arrs) = Futhark.ppStream w arrs nes lam
-  ppr (Scatter w lam arrs dests) = Futhark.ppScatter w arrs lam dests
+  pretty (Screma w form arrs) = Futhark.ppScrema w arrs form
+  pretty (Hist len ops bucket_fun imgs) = Futhark.ppHist len imgs ops bucket_fun
+  pretty (Stream w lam nes arrs) = Futhark.ppStream w arrs nes lam
+  pretty (Scatter w lam arrs dests) = Futhark.ppScatter w arrs lam dests
 
 -- | Returns the inputs used in a SOAC.
 inputs :: SOAC rep -> [Input]
diff --git a/src/Futhark/Analysis/Interference.hs b/src/Futhark/Analysis/Interference.hs
--- a/src/Futhark/Analysis/Interference.hs
+++ b/src/Futhark/Analysis/Interference.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Interference analysis for Futhark programs.
@@ -10,13 +8,13 @@
 import Data.Function ((&))
 import Data.Functor ((<&>))
 import Data.Map (Map)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
 import Data.Set (Set)
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Analysis.LastUse (LastUseMap)
-import qualified Futhark.Analysis.LastUse as LastUse
-import qualified Futhark.Analysis.MemAlias as MemAlias
+import Futhark.Analysis.LastUse qualified as LastUse
+import Futhark.Analysis.MemAlias qualified as MemAlias
 import Futhark.IR.GPUMem
 import Futhark.Util (invertMap)
 
diff --git a/src/Futhark/Analysis/LastUse.hs b/src/Futhark/Analysis/LastUse.hs
--- a/src/Futhark/Analysis/LastUse.hs
+++ b/src/Futhark/Analysis/LastUse.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Provides last-use analysis for Futhark programs.
@@ -19,7 +15,7 @@
 import Data.Foldable
 import Data.Function ((&))
 import Data.Map (Map)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Tuple
 import Futhark.Analysis.Alias (aliasAnalysis)
 import Futhark.IR.Aliases
diff --git a/src/Futhark/Analysis/MemAlias.hs b/src/Futhark/Analysis/MemAlias.hs
--- a/src/Futhark/Analysis/MemAlias.hs
+++ b/src/Futhark/Analysis/MemAlias.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Analysis.MemAlias
@@ -14,9 +12,9 @@
 import Data.Bifunctor
 import Data.Function ((&))
 import Data.Functor ((<&>))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe (fromMaybe, mapMaybe)
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.GPUMem
 import Futhark.IR.SeqMem
 import Futhark.Util
@@ -45,9 +43,9 @@
   mempty = MemAliases mempty
 
 instance Pretty MemAliases where
-  ppr (MemAliases m) = stack $ map f $ M.toList m
+  pretty (MemAliases m) = stack $ map f $ M.toList m
     where
-      f (v, vs) = ppr v <+> "aliases:" </> indent 2 (oneLine $ ppr vs)
+      f (v, vs) = pretty v <+> "aliases:" </> indent 2 (oneLine $ pretty vs)
 
 addAlias :: VName -> VName -> MemAliases -> MemAliases
 addAlias v1 v2 m =
diff --git a/src/Futhark/Analysis/Metrics.hs b/src/Futhark/Analysis/Metrics.hs
--- a/src/Futhark/Analysis/Metrics.hs
+++ b/src/Futhark/Analysis/Metrics.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Abstract Syntax Tree metrics.  This is used in the @futhark test@
 -- program, for the @structure@ stanzas.
 module Futhark.Analysis.Metrics
@@ -22,9 +17,9 @@
 
 import Control.Monad.Writer
 import Data.List (tails)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Analysis.Metrics.Type
 import Futhark.IR
 
diff --git a/src/Futhark/Analysis/Metrics/Type.hs b/src/Futhark/Analysis/Metrics/Type.hs
--- a/src/Futhark/Analysis/Metrics/Type.hs
+++ b/src/Futhark/Analysis/Metrics/Type.hs
@@ -3,9 +3,9 @@
 -- test modules.
 module Futhark.Analysis.Metrics.Type (AstMetrics (..)) where
 
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- | AST metrics are simply a collection from identifiable node names
 -- to the number of times that node appears.
diff --git a/src/Futhark/Analysis/PrimExp.hs b/src/Futhark/Analysis/PrimExp.hs
--- a/src/Futhark/Analysis/PrimExp.hs
+++ b/src/Futhark/Analysis/PrimExp.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
 
 -- | A primitive expression is an expression where the non-leaves are
@@ -73,8 +71,8 @@
 
 import Control.Category
 import Control.Monad
-import qualified Data.Map as M
-import qualified Data.Set as S
+import Data.Map qualified as M
+import Data.Set qualified as S
 import Data.Traversable
 import Futhark.IR.Prop.Names
 import Futhark.Util.IntegralExp
@@ -535,7 +533,7 @@
 
 numBad :: Pretty a => String -> a -> b
 numBad s x =
-  error $ "Invalid argument to PrimExp method " ++ s ++ ": " ++ pretty x
+  error $ "Invalid argument to PrimExp method " ++ s ++ ": " ++ prettyString x
 
 -- | Evaluate a 'PrimExp' in the given monad.  Invokes 'fail' on type
 -- errors.
@@ -566,9 +564,9 @@
 evalBad op arg =
   fail $
     "evalPrimExp: Type error when applying "
-      ++ pretty op
+      ++ prettyString op
       ++ " to "
-      ++ pretty arg
+      ++ prettyString arg
 
 -- | The type of values returned by a 'PrimExp'.  This function
 -- returning does not imply that the 'PrimExp' is type-correct.
@@ -684,16 +682,16 @@
 -- Prettyprinting instances
 
 instance Pretty v => Pretty (PrimExp v) where
-  ppr (LeafExp v _) = ppr v
-  ppr (ValueExp v) = ppr v
-  ppr (BinOpExp op x y) = ppr op <+> parens (ppr x) <+> parens (ppr y)
-  ppr (CmpOpExp op x y) = ppr op <+> parens (ppr x) <+> parens (ppr y)
-  ppr (ConvOpExp op x) = ppr op <+> parens (ppr x)
-  ppr (UnOpExp op x) = ppr op <+> parens (ppr x)
-  ppr (FunExp h args _) = text h <+> parens (commasep $ map ppr args)
+  pretty (LeafExp v _) = pretty v
+  pretty (ValueExp v) = pretty v
+  pretty (BinOpExp op x y) = pretty op <+> parens (pretty x) <+> parens (pretty y)
+  pretty (CmpOpExp op x y) = pretty op <+> parens (pretty x) <+> parens (pretty y)
+  pretty (ConvOpExp op x) = pretty op <+> parens (pretty x)
+  pretty (UnOpExp op x) = pretty op <+> parens (pretty x)
+  pretty (FunExp h args _) = pretty h <+> parens (commasep $ map pretty args)
 
 instance Pretty v => Pretty (TPrimExp t v) where
-  ppr = ppr . untyped
+  pretty = pretty . untyped
 
 -- | Produce a mapping from the leaves of the 'PrimExp' to their
 -- designated types.
diff --git a/src/Futhark/Analysis/PrimExp/Convert.hs b/src/Futhark/Analysis/PrimExp/Convert.hs
--- a/src/Futhark/Analysis/PrimExp/Convert.hs
+++ b/src/Futhark/Analysis/PrimExp/Convert.hs
@@ -25,9 +25,9 @@
   )
 where
 
-import qualified Control.Monad.Fail as Fail
+import Control.Monad.Fail qualified as Fail
 import Control.Monad.Identity
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Analysis.PrimExp
 import Futhark.Construct
diff --git a/src/Futhark/Analysis/PrimExp/Parse.hs b/src/Futhark/Analysis/PrimExp/Parse.hs
--- a/src/Futhark/Analysis/PrimExp/Parse.hs
+++ b/src/Futhark/Analysis/PrimExp/Parse.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Building blocks for parsing prim primexpressions.  *Not* an infix
 -- representation.
 module Futhark.Analysis.PrimExp.Parse
@@ -12,7 +10,7 @@
 where
 
 import Data.Functor
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Void
 import Futhark.Analysis.PrimExp
 import Futhark.Util.Pretty (prettyText)
diff --git a/src/Futhark/Analysis/PrimExp/Simplify.hs b/src/Futhark/Analysis/PrimExp/Simplify.hs
--- a/src/Futhark/Analysis/PrimExp/Simplify.hs
+++ b/src/Futhark/Analysis/PrimExp/Simplify.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Defines simplification functions for 'PrimExp's.
 module Futhark.Analysis.PrimExp.Simplify (simplifyPrimExp, simplifyExtPrimExp) where
 
diff --git a/src/Futhark/Analysis/Rephrase.hs b/src/Futhark/Analysis/Rephrase.hs
--- a/src/Futhark/Analysis/Rephrase.hs
+++ b/src/Futhark/Analysis/Rephrase.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-
 -- | Facilities for changing the rep of some fragment, with no
 -- context.  We call this "rephrasing", for no deep reason.
 module Futhark.Analysis.Rephrase
diff --git a/src/Futhark/Analysis/SymbolTable.hs b/src/Futhark/Analysis/SymbolTable.hs
--- a/src/Futhark/Analysis/SymbolTable.hs
+++ b/src/Futhark/Analysis/SymbolTable.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Analysis.SymbolTable
@@ -56,13 +54,13 @@
 import Control.Arrow ((&&&))
 import Control.Monad
 import Data.List (elemIndex, foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Ord
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.IR hiding (FParam, lookupType)
-import qualified Futhark.IR as AST
-import qualified Futhark.IR.Prop.Aliases as Aliases
+import Futhark.IR qualified as AST
+import Futhark.IR.Prop.Aliases qualified as Aliases
 import Prelude hiding (elem, lookup)
 
 data SymbolTable rep = SymbolTable
diff --git a/src/Futhark/Analysis/UsageTable.hs b/src/Futhark/Analysis/UsageTable.hs
--- a/src/Futhark/Analysis/UsageTable.hs
+++ b/src/Futhark/Analysis/UsageTable.hs
@@ -27,8 +27,8 @@
 where
 
 import Data.Bits
-import qualified Data.Foldable as Foldable
-import qualified Data.IntMap.Strict as IM
+import Data.Foldable qualified as Foldable
+import Data.IntMap.Strict qualified as IM
 import Data.List (foldl')
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
diff --git a/src/Futhark/Bench.hs b/src/Futhark/Bench.hs
--- a/src/Futhark/Bench.hs
+++ b/src/Futhark/Bench.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Facilities for handling Futhark benchmark results.  A Futhark
 -- benchmark program is just like a Futhark test program.
 module Futhark.Bench
@@ -20,17 +17,17 @@
 
 import Control.Applicative
 import Control.Monad.Except
-import qualified Data.Aeson as JSON
-import qualified Data.Aeson.Key as JSON
-import qualified Data.Aeson.KeyMap as JSON
-import qualified Data.ByteString.Char8 as SBS
-import qualified Data.ByteString.Lazy.Char8 as LBS
-import qualified Data.DList as DL
-import qualified Data.Map as M
+import Data.Aeson qualified as JSON
+import Data.Aeson.Key qualified as JSON
+import Data.Aeson.KeyMap qualified as JSON
+import Data.ByteString.Char8 qualified as SBS
+import Data.ByteString.Lazy.Char8 qualified as LBS
+import Data.DList qualified as DL
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Time.Clock
-import qualified Data.Vector.Unboxed as U
+import Data.Vector.Unboxed qualified as U
 import Futhark.Server
 import Futhark.Test
 import Statistics.Autocorrelation (autocorrelation)
diff --git a/src/Futhark/Builder.hs b/src/Futhark/Builder.hs
--- a/src/Futhark/Builder.hs
+++ b/src/Futhark/Builder.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -37,7 +31,7 @@
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Control.Monad.Writer
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.Builder.Class
 import Futhark.IR
 
@@ -108,9 +102,9 @@
       Nothing -> do
         known <- BuilderT $ gets $ M.keys . snd
         error . unlines $
-          [ "BuilderT.lookupType: unknown variable " ++ pretty name,
+          [ "BuilderT.lookupType: unknown variable " ++ prettyString name,
             "Known variables: ",
-            unwords $ map pretty known
+            unwords $ map prettyString known
           ]
       Just t' -> pure $ typeOf t'
   askScope = BuilderT $ gets snd
diff --git a/src/Futhark/Builder/Class.hs b/src/Futhark/Builder/Class.hs
--- a/src/Futhark/Builder/Class.hs
+++ b/src/Futhark/Builder/Class.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module defines a convenience typeclass for creating
@@ -22,7 +21,7 @@
   )
 where
 
-import qualified Data.Kind
+import Data.Kind qualified
 import Futhark.IR
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CLI/Autotune.hs b/src/Futhark/CLI/Autotune.hs
--- a/src/Futhark/CLI/Autotune.hs
+++ b/src/Futhark/CLI/Autotune.hs
@@ -1,17 +1,14 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark autotune@
 module Futhark.CLI.Autotune (main) where
 
 import Control.Monad
-import qualified Data.ByteString.Char8 as SBS
+import Data.ByteString.Char8 qualified as SBS
 import Data.Function (on)
 import Data.List (elemIndex, intersect, isPrefixOf, minimumBy, sort, sortOn)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Text as T
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Data.Tree
 import Futhark.Bench
 import Futhark.Server
diff --git a/src/Futhark/CLI/Bench.hs b/src/Futhark/CLI/Bench.hs
--- a/src/Futhark/CLI/Bench.hs
+++ b/src/Futhark/CLI/Bench.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark bench@
 module Futhark.CLI.Bench (main) where
 
@@ -8,26 +5,25 @@
 import Control.Exception
 import Control.Monad
 import Control.Monad.Except hiding (throwError)
-import qualified Data.ByteString.Char8 as SBS
-import qualified Data.ByteString.Lazy.Char8 as LBS
+import Data.ByteString.Char8 qualified as SBS
+import Data.ByteString.Lazy.Char8 qualified as LBS
 import Data.Either
 import Data.Function ((&))
 import Data.IORef
 import Data.List (sortBy)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Data.Ord
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime)
-import qualified Data.Vector.Unboxed as U
+import Data.Vector.Unboxed qualified as U
 import Futhark.Bench
 import Futhark.Server
 import Futhark.Test
 import Futhark.Util (atMostChars, fancyTerminal, pmapIO)
-import Futhark.Util.Console
 import Futhark.Util.Options
-import Futhark.Util.Pretty (prettyText)
+import Futhark.Util.Pretty (AnsiStyle, Color (..), annotate, bold, color, line, pretty, prettyText, putDoc)
 import Futhark.Util.ProgressBar
 import Statistics.Resampling (Estimator (..), resample)
 import Statistics.Resampling.Bootstrap (bootstrapBCA)
@@ -42,6 +38,14 @@
 import Text.Printf
 import Text.Regex.TDFA
 
+putStyleLn :: AnsiStyle -> T.Text -> IO ()
+putStyleLn s t = putDoc $ annotate s (pretty t <> line)
+
+putRedLn, putBoldRedLn, putBoldLn :: T.Text -> IO ()
+putRedLn = putStyleLn (color Red)
+putBoldRedLn = putStyleLn (color Red <> bold)
+putBoldLn = putStyleLn bold
+
 data BenchOptions = BenchOptions
   { optBackend :: String,
     optFuthark :: Maybe String,
@@ -185,7 +189,7 @@
 
               case res of
                 Left (err, errstr) -> do
-                  putStrLn $ inRed err
+                  putRedLn $ T.pack err
                   maybe (pure ()) SBS.putStrLn errstr
                   pure $ Left FailedToCompile
                 Right () ->
@@ -211,8 +215,8 @@
   where
     onError :: SomeException -> IO (Maybe a)
     onError e = do
-      putStrLn $ inBold $ inRed $ "\nFailed to run " ++ program
-      putStrLn $ inRed $ show e
+      putBoldRedLn $ "\nFailed to run " <> T.pack program
+      putRedLn $ T.pack $ show e
       pure Nothing
 
 -- Truncate dataset name display after this many characters.
@@ -227,7 +231,7 @@
     mapM (forInputOutputs server tuning_desc) $ filter relevant cases
   where
     forInputOutputs server tuning_desc (InputOutputs entry_name runs) = do
-      putStr $ inBold $ "\n" ++ program' ++ tuning_desc ++ ":\n"
+      putBoldLn $ "\n" <> T.pack program' <> T.pack tuning_desc <> ":"
       BenchResult program' . catMaybes
         <$> mapM (runBenchmarkCase server opts futhark program entry_name pad_to) runs
       where
@@ -392,7 +396,7 @@
   case res of
     Left err -> liftIO $ do
       putStrLn ""
-      putStrLn $ inRed $ T.unpack err
+      putRedLn err
       pure $ Just $ DataResult dataset_desc $ Left err
     Right (runtimes, errout) -> do
       let vec_runtimes = U.fromList $ map (fromIntegral . runMicroseconds) runtimes
@@ -421,7 +425,7 @@
   foldMap matchMap $ T.lines t
   where
     mem_regex = "Peak memory usage for space '([^']+)': ([0-9]+) bytes." :: T.Text
-    matchMap line = case (line =~ mem_regex :: (T.Text, T.Text, T.Text, [T.Text])) of
+    matchMap l = case (l =~ mem_regex :: (T.Text, T.Text, T.Text, [T.Text])) of
       (_, _, _, [device, bytes]) -> M.singleton device (read $ T.unpack bytes)
       _ -> mempty
 
diff --git a/src/Futhark/CLI/C.hs b/src/Futhark/CLI/C.hs
--- a/src/Futhark/CLI/C.hs
+++ b/src/Futhark/CLI/C.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark c@
 module Futhark.CLI.C (main) where
 
diff --git a/src/Futhark/CLI/CUDA.hs b/src/Futhark/CLI/CUDA.hs
--- a/src/Futhark/CLI/CUDA.hs
+++ b/src/Futhark/CLI/CUDA.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark cuda@
 module Futhark.CLI.CUDA (main) where
 
diff --git a/src/Futhark/CLI/Check.hs b/src/Futhark/CLI/Check.hs
--- a/src/Futhark/CLI/Check.hs
+++ b/src/Futhark/CLI/Check.hs
@@ -5,7 +5,7 @@
 import Control.Monad.IO.Class
 import Futhark.Compiler
 import Futhark.Util.Options
-import Futhark.Util.Pretty (pretty)
+import Futhark.Util.Pretty (hPutDoc)
 import Language.Futhark.Warnings
 import System.IO
 
@@ -31,6 +31,6 @@
       (warnings, _, _) <- readProgramOrDie file
       when (checkWarn cfg && anyWarnings warnings) $
         liftIO $
-          hPutStrLn stderr $
-            pretty warnings
+          hPutDoc stderr $
+            prettyWarnings warnings
     _ -> Nothing
diff --git a/src/Futhark/CLI/Datacmp.hs b/src/Futhark/CLI/Datacmp.hs
--- a/src/Futhark/CLI/Datacmp.hs
+++ b/src/Futhark/CLI/Datacmp.hs
@@ -1,10 +1,8 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark datacmp@
 module Futhark.CLI.Datacmp (main) where
 
 import Control.Exception
-import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.ByteString.Lazy.Char8 qualified as BS
 import Futhark.Data.Compare
 import Futhark.Data.Reader
 import Futhark.Util.Options
diff --git a/src/Futhark/CLI/Dataset.hs b/src/Futhark/CLI/Dataset.hs
--- a/src/Futhark/CLI/Dataset.hs
+++ b/src/Futhark/CLI/Dataset.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -7,15 +6,15 @@
 
 import Control.Monad
 import Control.Monad.ST
-import qualified Data.Binary as Bin
-import qualified Data.ByteString.Lazy.Char8 as BS
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Binary qualified as Bin
+import Data.ByteString.Lazy.Char8 qualified as BS
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Vector.Generic (freeze)
-import qualified Data.Vector.Storable as SVec
-import qualified Data.Vector.Storable.Mutable as USVec
+import Data.Vector.Storable qualified as SVec
+import Data.Vector.Storable.Mutable qualified as USVec
 import Data.Word
-import qualified Futhark.Data as V
+import Futhark.Data qualified as V
 import Futhark.Data.Reader (readValues)
 import Futhark.Util (convFloat)
 import Futhark.Util.Options
@@ -26,7 +25,6 @@
   ( FloatValue (..),
     IntValue (..),
     PrimValue (..),
-    Value,
     ValueType,
   )
 import System.Exit
@@ -108,7 +106,7 @@
                       }
                 Left err ->
                   Left $ do
-                    hPutStrLn stderr err
+                    T.hPutStrLn stderr err
                     exitFailure
           )
           "TYPE"
@@ -116,9 +114,9 @@
       "Generate a random value of this type.",
     Option
       []
-      ["text"]
+      ["pretty"]
       (NoArg $ Right $ \opts -> opts {format = Text})
-      "Output data in text format (must precede --generate).",
+      "Output data in pretty format (must precede --generate).",
     Option
       "b"
       ["binary"]
@@ -172,7 +170,7 @@
 
 tryMakeGenerator ::
   String ->
-  Either String (RandomConfiguration -> OutputFormat -> Word64 -> IO ())
+  Either T.Text (RandomConfiguration -> OutputFormat -> Word64 -> IO ())
 tryMakeGenerator t
   | Just vs <- readValues $ BS.pack t =
       pure $ \_ fmt _ -> mapM_ (outValue fmt) vs
@@ -187,7 +185,7 @@
     outValue Binary = BS.putStr . Bin.encode
     outValue Type = T.putStrLn . V.valueTypeText . V.valueType
 
-toValueType :: UncheckedTypeExp -> Either String V.ValueType
+toValueType :: UncheckedTypeExp -> Either T.Text V.ValueType
 toValueType TETuple {} = Left "Cannot handle tuples yet."
 toValueType TERecord {} = Left "Cannot handle records yet."
 toValueType TEApply {} = Left "Cannot handle type applications yet."
@@ -208,7 +206,7 @@
     m = map f [minBound .. maxBound]
     f t = (nameFromText (V.primTypeText t), t)
 toValueType (TEVar v _) =
-  Left $ "Unknown type " ++ pretty v
+  Left $ "Unknown type " <> prettyText v
 
 -- | Closed interval, as in @System.Random@.
 type Range a = (a, a)
diff --git a/src/Futhark/CLI/Defs.hs b/src/Futhark/CLI/Defs.hs
--- a/src/Futhark/CLI/Defs.hs
+++ b/src/Futhark/CLI/Defs.hs
@@ -1,11 +1,9 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark defs@
 module Futhark.CLI.Defs (main) where
 
-import qualified Data.Sequence as Seq
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Sequence qualified as Seq
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Futhark.Compiler
 import Futhark.Util.Loc
 import Futhark.Util.Options
diff --git a/src/Futhark/CLI/Dev.hs b/src/Futhark/CLI/Dev.hs
--- a/src/Futhark/CLI/Dev.hs
+++ b/src/Futhark/CLI/Dev.hs
@@ -1,30 +1,28 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE RankNTypes #-}
-
 -- | Futhark Compiler Driver
 module Futhark.CLI.Dev (main) where
 
 import Control.Category (id)
 import Control.Monad
 import Control.Monad.State
+import Data.Kind qualified
 import Data.List (intersperse)
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Futhark.Actions
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.Metrics (OpMetrics)
 import Futhark.Compiler.CLI hiding (compilerMain)
-import Futhark.IR (ASTRep, Op, Prog, pretty)
-import qualified Futhark.IR.GPU as GPU
-import qualified Futhark.IR.GPUMem as GPUMem
-import qualified Futhark.IR.MC as MC
-import qualified Futhark.IR.MCMem as MCMem
+import Futhark.IR (ASTRep, Op, Prog, prettyString)
+import Futhark.IR.GPU qualified as GPU
+import Futhark.IR.GPUMem qualified as GPUMem
+import Futhark.IR.MC qualified as MC
+import Futhark.IR.MCMem qualified as MCMem
 import Futhark.IR.Parse
 import Futhark.IR.Prop.Aliases (CanBeAliased)
-import qualified Futhark.IR.SOACS as SOACS
-import qualified Futhark.IR.Seq as Seq
-import qualified Futhark.IR.SeqMem as SeqMem
+import Futhark.IR.SOACS qualified as SOACS
+import Futhark.IR.Seq qualified as Seq
+import Futhark.IR.SeqMem qualified as SeqMem
 import Futhark.IR.TypeCheck (Checkable, checkProg)
 import Futhark.Internalise.Defunctionalise as Defunctionalise
 import Futhark.Internalise.Defunctorise as Defunctorise
@@ -36,7 +34,7 @@
 import Futhark.Optimise.HistAccs
 import Futhark.Optimise.InPlaceLowering
 import Futhark.Optimise.InliningDeadFun
-import qualified Futhark.Optimise.MemoryBlockMerging as MemoryBlockMerging
+import Futhark.Optimise.MemoryBlockMerging qualified as MemoryBlockMerging
 import Futhark.Optimise.ReduceDeviceSyncs (reduceDeviceSyncs)
 import Futhark.Optimise.Sink
 import Futhark.Optimise.TileLoops
@@ -44,8 +42,8 @@
 import Futhark.Pass
 import Futhark.Pass.AD
 import Futhark.Pass.ExpandAllocations
-import qualified Futhark.Pass.ExplicitAllocations.GPU as GPU
-import qualified Futhark.Pass.ExplicitAllocations.Seq as Seq
+import Futhark.Pass.ExplicitAllocations.GPU qualified as GPU
+import Futhark.Pass.ExplicitAllocations.Seq qualified as Seq
 import Futhark.Pass.ExtractKernels
 import Futhark.Pass.ExtractMulticore
 import Futhark.Pass.FirstOrderTransform
@@ -54,7 +52,7 @@
 import Futhark.Passes
 import Futhark.Util.Log
 import Futhark.Util.Options
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty qualified as PP
 import Language.Futhark.Core (locStr, nameFromString)
 import Language.Futhark.Parser (SyntaxError (..), parseFuthark)
 import System.Exit
@@ -127,13 +125,13 @@
   representation (SeqMem _) = "SeqMem"
 
 instance PP.Pretty UntypedPassState where
-  ppr (SOACS prog) = PP.ppr prog
-  ppr (GPU prog) = PP.ppr prog
-  ppr (MC prog) = PP.ppr prog
-  ppr (Seq prog) = PP.ppr prog
-  ppr (SeqMem prog) = PP.ppr prog
-  ppr (MCMem prog) = PP.ppr prog
-  ppr (GPUMem prog) = PP.ppr prog
+  pretty (SOACS prog) = PP.pretty prog
+  pretty (GPU prog) = PP.pretty prog
+  pretty (MC prog) = PP.pretty prog
+  pretty (Seq prog) = PP.pretty prog
+  pretty (SeqMem prog) = PP.pretty prog
+  pretty (MCMem prog) = PP.pretty prog
+  pretty (GPUMem prog) = PP.pretty prog
 
 newtype UntypedPass
   = UntypedPass
@@ -151,7 +149,7 @@
   | MCMemAction (BackendAction MCMem.MCMem)
   | SeqMemAction (BackendAction SeqMem.SeqMem)
   | PolyAction
-      ( forall rep.
+      ( forall (rep :: Data.Kind.Type).
         ( ASTRep rep,
           (CanBeAliased (Op rep)),
           (OpMetrics (Op rep))
@@ -400,12 +398,12 @@
       "Disable type-checking.",
     Option
       []
-      ["pretty-print"]
+      ["prettyString-print"]
       ( NoArg $
           Right $ \opts ->
             opts {futharkPipeline = PrettyPrint}
       )
-      "Parse and pretty-print the AST of the given program.",
+      "Parse and prettyString-print the AST of the given program.",
     Option
       []
       ["backend"]
@@ -656,7 +654,7 @@
           p =
             mapM_ putStrLn
               . intersperse ""
-              . map (if futharkPrintAST config then show else pretty)
+              . map (if futharkPrintAST config then show else prettyString)
 
           readProgram' = readProgramFile (futharkEntryPoints (futharkConfig config)) file
 
@@ -665,10 +663,10 @@
           maybe_prog <- parseFuthark file <$> T.readFile file
           case maybe_prog of
             Left (SyntaxError loc err) ->
-              fail $ "Syntax error at " <> locStr loc <> ":\n" <> err
+              fail $ "Syntax error at " <> locStr loc <> ":\n" <> T.unpack err
             Right prog
               | futharkPrintAST config -> print prog
-              | otherwise -> putStrLn $ pretty prog
+              | otherwise -> putStrLn $ prettyString prog
         TypeCheck -> do
           (_, imports, _) <- readProgram'
           liftIO $
@@ -676,7 +674,7 @@
               putStrLn $
                 if futharkPrintAST config
                   then show $ fileProg fm
-                  else pretty $ fileProg fm
+                  else prettyString $ fileProg fm
         Defunctorise -> do
           (_, imports, src) <- readProgram'
           liftIO $ p $ evalState (Defunctorise.transformProg imports) src
diff --git a/src/Futhark/CLI/Doc.hs b/src/Futhark/CLI/Doc.hs
--- a/src/Futhark/CLI/Doc.hs
+++ b/src/Futhark/CLI/Doc.hs
@@ -1,7 +1,5 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | @futhark doc@
 module Futhark.CLI.Doc (main) where
@@ -9,8 +7,8 @@
 import Control.Monad.State
 import Data.FileEmbed
 import Data.List (nubBy)
-import qualified Data.Text.Lazy as T
-import qualified Data.Text.Lazy.IO as T
+import Data.Text.Lazy qualified as T
+import Data.Text.Lazy.IO qualified as T
 import Futhark.Compiler (Imports, dumpError, fileProg, newFutharkConfig, readProgramFiles)
 import Futhark.Doc.Generator
 import Futhark.Pipeline (FutharkM, Verbosity (..), runFutharkM)
diff --git a/src/Futhark/CLI/LSP.hs b/src/Futhark/CLI/LSP.hs
--- a/src/Futhark/CLI/LSP.hs
+++ b/src/Futhark/CLI/LSP.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE ExplicitNamespaces #-}
 
 -- | @futhark lsp@
diff --git a/src/Futhark/CLI/Literate.hs b/src/Futhark/CLI/Literate.hs
--- a/src/Futhark/CLI/Literate.hs
+++ b/src/Futhark/CLI/Literate.hs
@@ -1,29 +1,25 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark literate@
 module Futhark.CLI.Literate (main) where
 
-import qualified Codec.BMP as BMP
+import Codec.BMP qualified as BMP
 import Control.Monad.Except
 import Control.Monad.State hiding (State)
 import Data.Bifunctor (first, second)
 import Data.Bits
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as LBS
+import Data.ByteString qualified as BS
+import Data.ByteString.Lazy qualified as LBS
 import Data.Char
 import Data.Functor
 import Data.Int (Int64)
 import Data.List (foldl', transpose)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.IO as T
-import qualified Data.Vector.Storable as SVec
-import qualified Data.Vector.Storable.ByteString as SVec
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.IO qualified as T
+import Data.Vector.Storable qualified as SVec
+import Data.Vector.Storable.ByteString qualified as SVec
 import Data.Void
 import Data.Word (Word32, Word8)
 import Futhark.Data
@@ -41,7 +37,7 @@
   )
 import Futhark.Util.Options
 import Futhark.Util.Pretty (prettyText, prettyTextOneLine)
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty qualified as PP
 import Futhark.Util.ProgressBar
 import System.Directory
   ( copyFile,
@@ -106,78 +102,73 @@
 varsInDirective (DirectiveGnuplot e _) = varsInExp e
 varsInDirective (DirectiveVideo e _) = varsInExp e
 
-pprDirective :: Bool -> Directive -> PP.Doc
+pprDirective :: Bool -> Directive -> PP.Doc a
 pprDirective _ (DirectiveRes e) =
-  "> " <> PP.align (PP.ppr e)
+  "> " <> PP.align (PP.pretty e)
 pprDirective _ (DirectiveBrief f) =
   pprDirective False f
 pprDirective _ (DirectiveCovert f) =
   pprDirective False f
 pprDirective _ (DirectiveImg e params) =
-  "> :img "
-    <> PP.align (PP.ppr e)
-    <> if null params' then mempty else PP.stack $ ";" : params'
+  ("> :img " <> PP.align (PP.pretty e))
+    <> if null params' then mempty else ";" <> PP.hardline <> PP.stack params'
   where
-    params' =
-      catMaybes
-        [ p "file" imgFile PP.ppr
-        ]
-    p s f ppr = do
+    params' = catMaybes [p "file" imgFile PP.pretty]
+    p s f pretty = do
       x <- f params
-      Just $ s <> ": " <> ppr x
+      Just $ s <> ": " <> pretty x
 pprDirective True (DirectivePlot e (Just (h, w))) =
   PP.stack
-    [ "> :plot2d " <> PP.ppr e <> ";",
-      "size: (" <> PP.ppr w <> "," <> PP.ppr h <> ")"
+    [ "> :plot2d " <> PP.pretty e <> ";",
+      "size: (" <> PP.pretty w <> "," <> PP.pretty h <> ")"
     ]
 pprDirective _ (DirectivePlot e _) =
-  "> :plot2d " <> PP.align (PP.ppr e)
+  "> :plot2d " <> PP.align (PP.pretty e)
 pprDirective True (DirectiveGnuplot e script) =
   PP.stack $
-    "> :gnuplot " <> PP.align (PP.ppr e) <> ";"
-      : map PP.strictText (T.lines script)
+    "> :gnuplot " <> PP.align (PP.pretty e) <> ";"
+      : map PP.pretty (T.lines script)
 pprDirective False (DirectiveGnuplot e _) =
-  "> :gnuplot " <> PP.align (PP.ppr e)
+  "> :gnuplot " <> PP.align (PP.pretty e)
 pprDirective False (DirectiveVideo e _) =
-  "> :video " <> PP.align (PP.ppr e)
+  "> :video " <> PP.align (PP.pretty e)
 pprDirective True (DirectiveVideo e params) =
-  "> :video "
-    <> PP.ppr e
-    <> if null params' then mempty else PP.stack $ ";" : params'
+  ("> :video " <> PP.pretty e)
+    <> if null params' then mempty else ";" <> PP.hardline <> PP.stack params'
   where
     params' =
       catMaybes
-        [ p "fps" videoFPS PP.ppr,
+        [ p "fps" videoFPS PP.pretty,
           p "loop" videoLoop ppBool,
           p "autoplay" videoAutoplay ppBool,
-          p "format" videoFormat PP.strictText,
-          p "file" videoFile PP.ppr
+          p "format" videoFormat PP.pretty,
+          p "file" videoFile PP.pretty
         ]
     ppBool b = if b then "true" else "false"
-    p s f ppr = do
+    p s f pretty = do
       x <- f params
-      Just $ s <> ": " <> ppr x
+      Just $ s <> ": " <> pretty x
 
 instance PP.Pretty Directive where
-  ppr = pprDirective True
+  pretty = pprDirective True
 
 data Block
   = BlockCode T.Text
   | BlockComment T.Text
-  | BlockDirective Directive
+  | BlockDirective Directive T.Text
   deriving (Show)
 
 varsInScripts :: [Block] -> S.Set EntryName
 varsInScripts = foldMap varsInBlock
   where
-    varsInBlock (BlockDirective d) = varsInDirective d
+    varsInBlock (BlockDirective d _) = varsInDirective d
     varsInBlock BlockCode {} = mempty
     varsInBlock BlockComment {} = mempty
 
 type Parser = Parsec Void T.Text
 
 postlexeme :: Parser ()
-postlexeme = void $ hspace *> optional (try $ eol *> "-- " *> postlexeme)
+postlexeme = void $ hspace *> optional (try $ eol *> "--" *> postlexeme)
 
 lexeme :: Parser a -> Parser a
 lexeme p = p <* postlexeme
@@ -289,10 +280,26 @@
 afterExp :: Parser ()
 afterExp = choice [atStartOfLine, void eol]
 
+withParsedSource :: Parser a -> (a -> T.Text -> b) -> Parser b
+withParsedSource p f = do
+  s <- getInput
+  bef <- getOffset
+  x <- p
+  aft <- getOffset
+  pure $ f x $ T.take (aft - bef) s
+
+stripCommentPrefix :: T.Text -> T.Text
+stripCommentPrefix = T.unlines . map onLine . T.lines
+  where
+    onLine s
+      | "-- " `T.isPrefixOf` s = T.drop 3 s
+      | otherwise = T.drop 2 s
+
 parseBlock :: Parser Block
 parseBlock =
   choice
-    [ token "-- >" $> BlockDirective <*> parseDirective,
+    [ withParsedSource (token "-- >" *> parseDirective) $ \d s ->
+        BlockDirective d $ stripCommentPrefix s,
       BlockCode <$> parseTestBlock,
       BlockCode <$> parseBlockCode,
       BlockComment <$> parseBlockComment
@@ -860,18 +867,18 @@
 processBlock _ (BlockCode code)
   | T.null code = pure (Success, "\n", mempty)
   | otherwise = pure (Success, "\n```futhark\n" <> code <> "```\n\n", mempty)
-processBlock _ (BlockComment text) =
-  pure (Success, text, mempty)
-processBlock env (BlockDirective directive) = do
+processBlock _ (BlockComment pretty) =
+  pure (Success, pretty, mempty)
+processBlock env (BlockDirective directive text) = do
   when (scriptVerbose (envOpts env) > 0) $
-    T.hPutStrLn stderr . prettyText $
-      "Processing " <> PP.align (PP.ppr directive) <> "..."
+    T.hPutStrLn stderr . PP.docText $
+      "Processing " <> PP.align (PP.pretty directive) <> "..."
   let prompt = case directive of
         DirectiveCovert _ -> mempty
         DirectiveBrief _ ->
-          "```\n" <> prettyText (pprDirective False directive) <> "\n```\n"
+          "```\n" <> PP.docText (pprDirective False directive) <> "\n```\n"
         _ ->
-          "```\n" <> prettyText (pprDirective True directive) <> "\n```\n"
+          "```\n" <> text <> "```\n"
       env' = env {envHash = hashText (envHash env <> prettyText directive)}
   (r, files) <- runScriptM $ processDirective env' directive
   case r of
diff --git a/src/Futhark/CLI/Main.hs b/src/Futhark/CLI/Main.hs
--- a/src/Futhark/CLI/Main.hs
+++ b/src/Futhark/CLI/Main.hs
@@ -1,38 +1,36 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | The main function for the @futhark@ command line program.
 module Futhark.CLI.Main (main) where
 
 import Control.Exception
 import Data.List (sortOn)
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
-import qualified Futhark.CLI.Autotune as Autotune
-import qualified Futhark.CLI.Bench as Bench
-import qualified Futhark.CLI.C as C
-import qualified Futhark.CLI.CUDA as CCUDA
-import qualified Futhark.CLI.Check as Check
-import qualified Futhark.CLI.Datacmp as Datacmp
-import qualified Futhark.CLI.Dataset as Dataset
-import qualified Futhark.CLI.Defs as Defs
-import qualified Futhark.CLI.Dev as Dev
-import qualified Futhark.CLI.Doc as Doc
-import qualified Futhark.CLI.LSP as LSP
-import qualified Futhark.CLI.Literate as Literate
-import qualified Futhark.CLI.Misc as Misc
-import qualified Futhark.CLI.Multicore as Multicore
-import qualified Futhark.CLI.MulticoreISPC as MulticoreISPC
-import qualified Futhark.CLI.MulticoreWASM as MulticoreWASM
-import qualified Futhark.CLI.OpenCL as OpenCL
-import qualified Futhark.CLI.Pkg as Pkg
-import qualified Futhark.CLI.PyOpenCL as PyOpenCL
-import qualified Futhark.CLI.Python as Python
-import qualified Futhark.CLI.Query as Query
-import qualified Futhark.CLI.REPL as REPL
-import qualified Futhark.CLI.Run as Run
-import qualified Futhark.CLI.Test as Test
-import qualified Futhark.CLI.WASM as WASM
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
+import Futhark.CLI.Autotune qualified as Autotune
+import Futhark.CLI.Bench qualified as Bench
+import Futhark.CLI.C qualified as C
+import Futhark.CLI.CUDA qualified as CCUDA
+import Futhark.CLI.Check qualified as Check
+import Futhark.CLI.Datacmp qualified as Datacmp
+import Futhark.CLI.Dataset qualified as Dataset
+import Futhark.CLI.Defs qualified as Defs
+import Futhark.CLI.Dev qualified as Dev
+import Futhark.CLI.Doc qualified as Doc
+import Futhark.CLI.LSP qualified as LSP
+import Futhark.CLI.Literate qualified as Literate
+import Futhark.CLI.Misc qualified as Misc
+import Futhark.CLI.Multicore qualified as Multicore
+import Futhark.CLI.MulticoreISPC qualified as MulticoreISPC
+import Futhark.CLI.MulticoreWASM qualified as MulticoreWASM
+import Futhark.CLI.OpenCL qualified as OpenCL
+import Futhark.CLI.Pkg qualified as Pkg
+import Futhark.CLI.PyOpenCL qualified as PyOpenCL
+import Futhark.CLI.Python qualified as Python
+import Futhark.CLI.Query qualified as Query
+import Futhark.CLI.REPL qualified as REPL
+import Futhark.CLI.Run qualified as Run
+import Futhark.CLI.Test qualified as Test
+import Futhark.CLI.WASM qualified as WASM
 import Futhark.Error
 import Futhark.Util (maxinum)
 import Futhark.Util.Options
diff --git a/src/Futhark/CLI/Misc.hs b/src/Futhark/CLI/Misc.hs
--- a/src/Futhark/CLI/Misc.hs
+++ b/src/Futhark/CLI/Misc.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Various small subcommands that are too simple to deserve their own file.
 module Futhark.CLI.Misc
   ( mainImports,
@@ -12,11 +10,11 @@
 where
 
 import Control.Monad.State
-import qualified Data.ByteString.Lazy as BS
+import Data.ByteString.Lazy qualified as BS
 import Data.Function (on)
 import Data.List (isInfixOf, nubBy)
 import Data.Loc (L (..), startPos)
-import qualified Data.Text.IO as T
+import Data.Text.IO qualified as T
 import Futhark.Compiler
 import Futhark.Test
 import Futhark.Util (hashText, interactWithFileSafely)
diff --git a/src/Futhark/CLI/Multicore.hs b/src/Futhark/CLI/Multicore.hs
--- a/src/Futhark/CLI/Multicore.hs
+++ b/src/Futhark/CLI/Multicore.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark multicore@
 module Futhark.CLI.Multicore (main) where
 
diff --git a/src/Futhark/CLI/MulticoreISPC.hs b/src/Futhark/CLI/MulticoreISPC.hs
--- a/src/Futhark/CLI/MulticoreISPC.hs
+++ b/src/Futhark/CLI/MulticoreISPC.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark multicore@
 module Futhark.CLI.MulticoreISPC (main) where
 
diff --git a/src/Futhark/CLI/MulticoreWASM.hs b/src/Futhark/CLI/MulticoreWASM.hs
--- a/src/Futhark/CLI/MulticoreWASM.hs
+++ b/src/Futhark/CLI/MulticoreWASM.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark wasm-multicore@
 module Futhark.CLI.MulticoreWASM (main) where
 
diff --git a/src/Futhark/CLI/OpenCL.hs b/src/Futhark/CLI/OpenCL.hs
--- a/src/Futhark/CLI/OpenCL.hs
+++ b/src/Futhark/CLI/OpenCL.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark opencl@
 module Futhark.CLI.OpenCL (main) where
 
diff --git a/src/Futhark/CLI/Pkg.hs b/src/Futhark/CLI/Pkg.hs
--- a/src/Futhark/CLI/Pkg.hs
+++ b/src/Futhark/CLI/Pkg.hs
@@ -1,20 +1,17 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | @futhark pkg@
 module Futhark.CLI.Pkg (main) where
 
-import qualified Codec.Archive.Zip as Zip
+import Codec.Archive.Zip qualified as Zip
 import Control.Monad.IO.Class
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.ByteString.Lazy as LBS
+import Data.ByteString.Lazy qualified as LBS
 import Data.List (intercalate, isPrefixOf)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Data.Monoid
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Futhark.Pkg.Info
 import Futhark.Pkg.Solve
 import Futhark.Pkg.Types
@@ -25,7 +22,7 @@
 import System.Environment
 import System.Exit
 import System.FilePath
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 import System.IO
 import Prelude
 
diff --git a/src/Futhark/CLI/PyOpenCL.hs b/src/Futhark/CLI/PyOpenCL.hs
--- a/src/Futhark/CLI/PyOpenCL.hs
+++ b/src/Futhark/CLI/PyOpenCL.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark pyopencl@
 module Futhark.CLI.PyOpenCL (main) where
 
diff --git a/src/Futhark/CLI/Python.hs b/src/Futhark/CLI/Python.hs
--- a/src/Futhark/CLI/Python.hs
+++ b/src/Futhark/CLI/Python.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark py@
 module Futhark.CLI.Python (main) where
 
diff --git a/src/Futhark/CLI/Query.hs b/src/Futhark/CLI/Query.hs
--- a/src/Futhark/CLI/Query.hs
+++ b/src/Futhark/CLI/Query.hs
@@ -21,12 +21,12 @@
         case atPos imports $ Pos file line' col' 0 of
           Nothing -> putStrLn "No information available."
           Just (AtName qn def loc) -> do
-            putStrLn $ "Name: " ++ pretty qn
+            putStrLn $ "Name: " ++ prettyString qn
             putStrLn $ "Position: " ++ locStr (srclocOf loc)
             case def of
               Nothing -> pure ()
               Just (BoundTerm t defloc) -> do
-                putStrLn $ "Type: " ++ pretty t
+                putStrLn $ "Type: " ++ prettyString t
                 putStrLn $ "Definition: " ++ locStr (srclocOf defloc)
               Just (BoundType defloc) ->
                 putStrLn $ "Definition: " ++ locStr (srclocOf defloc)
diff --git a/src/Futhark/CLI/REPL.hs b/src/Futhark/CLI/REPL.hs
--- a/src/Futhark/CLI/REPL.hs
+++ b/src/Futhark/CLI/REPL.hs
@@ -1,8 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 
 -- | @futhark repl@
 module Futhark.CLI.REPL (main) where
@@ -13,37 +9,41 @@
 import Control.Monad.Free.Church
 import Control.Monad.State
 import Data.Char
-import Data.List (intercalate, intersperse)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as M
+import Data.List (intersperse)
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Version
 import Futhark.Compiler
 import Futhark.MonadFreshNames
 import Futhark.Util (fancyTerminal)
 import Futhark.Util.Options
+import Futhark.Util.Pretty (AnsiStyle, Color (..), Doc, annotate, bgColorDull, bold, brackets, color, docText, docTextForHandle, hardline, pretty, putDoc, putDocLn, (<+>))
 import Futhark.Version
 import Language.Futhark
-import qualified Language.Futhark.Interpreter as I
+import Language.Futhark.Interpreter qualified as I
 import Language.Futhark.Parser
-import qualified Language.Futhark.Semantic as T
-import qualified Language.Futhark.TypeChecker as T
+import Language.Futhark.Semantic qualified as T
+import Language.Futhark.TypeChecker qualified as T
 import NeatInterpolation (text)
-import qualified System.Console.Haskeline as Haskeline
+import System.Console.Haskeline qualified as Haskeline
 import System.Directory
 import System.FilePath
+import System.IO (stdout)
 import Text.Read (readMaybe)
 
-banner :: String
+banner :: Doc AnsiStyle
 banner =
-  unlines
-    [ "|// |\\    |   |\\  |\\   /",
-      "|/  | \\   |\\  |\\  |/  /",
-      "|   |  \\  |/  |   |\\  \\",
-      "|   |   \\ |   |   | \\  \\"
+  mconcat . map ((<> hardline) . decorate . pretty) $
+    [ "┃╱╱ ┃╲    ┃   ┃╲  ┃╲   ╱" :: T.Text,
+      "┃╱  ┃ ╲   ┃╲  ┃╲  ┃╱  ╱ ",
+      "┃   ┃  ╲  ┃╱  ┃   ┃╲  ╲ ",
+      "┃   ┃   ╲ ┃   ┃   ┃ ╲  ╲"
     ]
+  where
+    decorate = annotate (bgColorDull Red <> bold <> color White)
 
 -- | Run @futhark repl@.
 main :: String -> [String] -> IO ()
@@ -58,11 +58,11 @@
 repl :: Maybe FilePath -> IO ()
 repl maybe_prog = do
   when fancyTerminal $ do
-    putStr banner
+    putDoc banner
     putStrLn $ "Version " ++ showVersion version ++ "."
     putStrLn "Copyright (C) DIKU, University of Copenhagen, released under the ISC license."
     putStrLn ""
-    putStrLn "Run :help for a list of commands."
+    putDoc $ "Run" <+> annotate bold ":help" <+> "for a list of commands."
     putStrLn ""
 
   let toploop s = do
@@ -85,7 +85,7 @@
             case maybe_new_state of
               Right new_state -> toploop new_state
               Left err -> do
-                liftIO $ putStrLn err
+                liftIO $ putDocLn err
                 toploop s'
           Right _ -> pure ()
 
@@ -99,9 +99,9 @@
       noprog_init_state <- liftIO $ newFutharkiState 0 noLoadedProg Nothing
       case noprog_init_state of
         Left err ->
-          error $ "Failed to initialise interpreter state: " ++ err
+          error $ "Failed to initialise interpreter state: " <> T.unpack (docText err)
         Right s -> do
-          liftIO $ putStrLn prog_err
+          liftIO $ putDoc prog_err
           pure s {futharkiLoaded = maybe_prog}
     Right s ->
       pure s
@@ -148,7 +148,7 @@
     t_imports = filter ((`elem` opens) . fst) $ lpImports prog
     i_envs = map snd $ filter ((`elem` opens) . fst) $ M.toList $ I.ctxImports ictx
 
-newFutharkiState :: Int -> LoadedProg -> Maybe FilePath -> IO (Either String FutharkiState)
+newFutharkiState :: Int -> LoadedProg -> Maybe FilePath -> IO (Either (Doc AnsiStyle) FutharkiState)
 newFutharkiState count prev_prog maybe_file = runExceptT $ do
   (prog, tenv, ienv) <- case maybe_file of
     Nothing -> do
@@ -158,7 +158,7 @@
       -- Then into the interpreter.
       ienv <-
         foldM
-          (\ctx -> badOnLeft show <=< runInterpreter' . I.interpretImport ctx)
+          (\ctx -> badOnLeft (pretty . show) <=< runInterpreter' . I.interpretImport ctx)
           I.initialCtx
           $ map (fmap fileProg) (lpImports prog)
 
@@ -168,11 +168,11 @@
       pure (prog, tenv, ienv')
     Just file -> do
       prog <- badOnLeft prettyProgErrors =<< liftIO (reloadProg prev_prog [file] M.empty)
-      liftIO $ putStrLn $ pretty $ lpWarnings prog
+      liftIO $ putDoc $ prettyWarnings $ lpWarnings prog
 
       ienv <-
         foldM
-          (\ctx -> badOnLeft show <=< runInterpreter' . I.interpretImport ctx)
+          (\ctx -> badOnLeft (pretty . show) <=< runInterpreter' . I.interpretImport ctx)
           I.initialCtx
           $ map (fmap fileProg) (lpImports prog)
 
@@ -192,16 +192,14 @@
         futharkiLoaded = maybe_file
       }
   where
-    badOnLeft :: (err -> String) -> Either err a -> ExceptT String IO a
+    badOnLeft :: (err -> err') -> Either err a -> ExceptT err' IO a
     badOnLeft _ (Right x) = pure x
     badOnLeft p (Left err) = throwError $ p err
 
-    prettyProgErrors = pretty . pprProgErrors
-
 getPrompt :: FutharkiM String
 getPrompt = do
   i <- gets futharkiCount
-  pure $ "[" ++ show i ++ "]> "
+  fmap T.unpack $ liftIO $ docTextForHandle stdout $ annotate bold $ brackets (pretty i) <> "> "
 
 -- The ExceptT part is more of a continuation, really.
 newtype FutharkiM a = FutharkiM {runFutharkiM :: ExceptT StopReason (StateT FutharkiState (Haskeline.InputT IO)) a}
@@ -238,7 +236,7 @@
       maybe_dec_or_e <- parseDecOrExpIncrM (inputLine "  ") prompt line
 
       case maybe_dec_or_e of
-        Left (SyntaxError _ err) -> liftIO $ putStrLn err
+        Left (SyntaxError _ err) -> liftIO $ T.putStrLn err
         Right (Left d) -> onDec d
         Right (Right e) -> onExp e
   modify $ \s -> s {futharkiCount = futharkiCount s + 1}
@@ -266,14 +264,14 @@
   cur_prog <- gets futharkiProg
   imp_r <- liftIO $ extendProg cur_prog files M.empty
   case imp_r of
-    Left e -> liftIO $ T.putStrLn $ prettyText $ pprProgErrors e
+    Left e -> liftIO $ putDoc $ prettyProgErrors e
     Right prog -> do
       env <- gets futharkiEnv
       let (tenv, ienv) = extendEnvs prog env (map fst $ decImports d)
           imports = lpImports prog
           src = lpNameSource prog
       case T.checkDec imports src tenv cur_import d of
-        (_, Left e) -> liftIO $ putStrLn $ pretty e
+        (_, Left e) -> liftIO $ putDoc $ T.prettyTypeErrorNoLoc e
         (_, Right (tenv', d', src')) -> do
           let new_imports = filter ((`notElem` map fst old_imports) . fst) imports
           int_r <- runInterpreter $ do
@@ -293,22 +291,22 @@
 onExp e = do
   (imports, src, tenv, ienv) <- getIt
   case T.checkExp imports src tenv e of
-    (_, Left err) -> liftIO $ putStrLn $ pretty err
+    (_, Left err) -> liftIO $ putDoc $ T.prettyTypeErrorNoLoc err
     (_, Right (tparams, e'))
       | null tparams -> do
           r <- runInterpreter $ I.interpretExp ienv e'
           case r of
             Left err -> liftIO $ print err
-            Right v -> liftIO $ putStrLn $ pretty v
+            Right v -> liftIO $ putDoc $ I.prettyValue v <> hardline
       | otherwise -> liftIO $ do
-          putStrLn $ "Inferred type of expression: " ++ pretty (typeOf e')
-          putStrLn $
+          T.putStrLn $ "Inferred type of expression: " <> prettyText (typeOf e')
+          T.putStrLn $
             "The following types are ambiguous: "
-              ++ intercalate ", " (map (prettyName . typeParamName) tparams)
+              <> T.intercalate ", " (map (nameToText . toName . typeParamName) tparams)
 
-prettyBreaking :: Breaking -> String
+prettyBreaking :: Breaking -> T.Text
 prettyBreaking b =
-  prettyStacktrace (breakingAt b) $ map locStr $ NE.toList $ breakingStack b
+  prettyStacktrace (breakingAt b) $ map locText $ NE.toList $ breakingStack b
 
 -- Are we currently willing to break for this reason?  Among othe
 -- things, we do not want recursive breakpoints.  It could work fine
@@ -341,9 +339,9 @@
 
       -- Are we supposed to respect this breakpoint?
       when (breakForReason s top why) $ do
-        liftIO $ putStrLn $ why' <> " at " ++ locStr w
-        liftIO $ putStrLn $ prettyBreaking breaking
-        liftIO $ putStrLn "<Enter> to continue."
+        liftIO $ T.putStrLn $ why' <> " at " <> locText w
+        liftIO $ T.putStrLn $ prettyBreaking breaking
+        liftIO $ T.putStrLn "<Enter> to continue."
 
         -- Note the cleverness to preserve the Haskeline session (for
         -- line history and such).
@@ -400,22 +398,22 @@
 genTypeCommand f g h e = do
   prompt <- getPrompt
   case f prompt e of
-    Left (SyntaxError _ err) -> liftIO $ putStrLn err
+    Left (SyntaxError _ err) -> liftIO $ T.putStrLn err
     Right e' -> do
       (imports, src, tenv, _) <- getIt
       case snd $ g imports src tenv e' of
-        Left err -> liftIO $ putStrLn $ pretty err
+        Left err -> liftIO $ putDoc $ T.prettyTypeErrorNoLoc err
         Right x -> liftIO $ putStrLn $ h x
 
 typeCommand :: Command
 typeCommand = genTypeCommand parseExp T.checkExp $ \(ps, e) ->
-  pretty e
-    <> concatMap ((" " <>) . pretty) ps
+  prettyString e
+    <> concatMap ((" " <>) . prettyString) ps
     <> " : "
-    <> pretty (typeOf e)
+    <> prettyString (typeOf e)
 
 mtypeCommand :: Command
-mtypeCommand = genTypeCommand parseModExp T.checkModExp $ pretty . fst
+mtypeCommand = genTypeCommand parseModExp T.checkModExp $ prettyString . fst
 
 unbreakCommand :: Command
 unbreakCommand _ = do
@@ -450,7 +448,7 @@
               { futharkiEnv = (tenv, ctx),
                 futharkiBreaking = Just breaking
               }
-          liftIO $ putStrLn $ prettyBreaking breaking
+          liftIO $ T.putStrLn $ prettyBreaking breaking
     (Just _, _) ->
       liftIO $ putStrLn $ "Invalid stack index: " ++ T.unpack which
     (Nothing, _) ->
@@ -470,8 +468,8 @@
 helpCommand :: Command
 helpCommand _ = liftIO $
   forM_ commands $ \(cmd, (_, desc)) -> do
-    T.putStrLn $ ":" <> cmd
-    T.putStrLn $ T.replicate (1 + T.length cmd) "-"
+    putDoc $ annotate bold $ ":" <> pretty cmd <> hardline
+    T.putStrLn $ T.replicate (1 + T.length cmd) "─"
     T.putStr desc
     T.putStrLn ""
     T.putStrLn ""
diff --git a/src/Futhark/CLI/Run.hs b/src/Futhark/CLI/Run.hs
--- a/src/Futhark/CLI/Run.hs
+++ b/src/Futhark/CLI/Run.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 -- | @futhark run@
 module Futhark.CLI.Run (main) where
 
@@ -9,18 +5,20 @@
 import Control.Monad
 import Control.Monad.Except
 import Control.Monad.Free.Church
-import qualified Data.Map as M
+import Data.ByteString.Lazy qualified as BS
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text.IO as T
+import Data.Text.IO qualified as T
 import Futhark.Compiler
+import Futhark.Data.Reader (readValues)
 import Futhark.Pipeline
 import Futhark.Util (toPOSIX)
 import Futhark.Util.Options
+import Futhark.Util.Pretty (AnsiStyle, Doc, hPutDoc)
 import Language.Futhark
-import qualified Language.Futhark.Interpreter as I
-import Language.Futhark.Parser
-import qualified Language.Futhark.Semantic as T
-import qualified Language.Futhark.TypeChecker as T
+import Language.Futhark.Interpreter qualified as I
+import Language.Futhark.Semantic qualified as T
+import Language.Futhark.TypeChecker qualified as T
 import System.Exit
 import System.FilePath
 import System.IO
@@ -38,19 +36,19 @@
   pr <- newFutharkiState config fp
   (tenv, ienv) <- case pr of
     Left err -> do
-      hPutStrLn stderr err
+      hPutDoc stderr err
       exitFailure
     Right env -> pure env
 
   let entry = interpreterEntryPoint config
-  vr <- parseValues "stdin" <$> T.getContents
+  vr <- readValues <$> BS.getContents
 
   inps <-
     case vr of
-      Left (SyntaxError loc err) -> do
-        hPutStrLn stderr $ "Input syntax error at " <> locStr loc <> ":\n" <> err
+      Nothing -> do
+        T.hPutStrLn stderr "Incorrectly formatted input data."
         exitFailure
-      Right vs ->
+      Just vs ->
         pure vs
 
   (fname, ret) <-
@@ -59,12 +57,12 @@
         | Just (T.BoundV _ t) <- M.lookup (qualLeaf fname) $ T.envVtable tenv ->
             pure (fname, toStructural $ snd $ unfoldFunType t)
       _ -> do
-        hPutStrLn stderr $ "Invalid entry point: " ++ pretty entry
+        T.hPutStrLn stderr $ "Invalid entry point: " <> prettyText entry
         exitFailure
 
   case I.interpretFunction ienv (qualLeaf fname) inps of
     Left err -> do
-      hPutStrLn stderr err
+      T.hPutStrLn stderr err
       exitFailure
     Right run -> do
       run' <- runInterpreter' run
@@ -79,8 +77,8 @@
 
 putValue :: I.Value -> TypeBase () () -> IO ()
 putValue v t
-  | I.isEmptyArray v = putStrLn $ I.prettyEmptyArray t v
-  | otherwise = putStrLn $ pretty v
+  | I.isEmptyArray v = T.putStrLn $ I.prettyEmptyArray t v
+  | otherwise = T.putStrLn $ I.valueText v
 
 data InterpreterConfig = InterpreterConfig
   { interpreterEntryPoint :: Name,
@@ -112,10 +110,10 @@
 newFutharkiState ::
   InterpreterConfig ->
   FilePath ->
-  IO (Either String (T.Env, I.Ctx))
+  IO (Either (Doc AnsiStyle) (T.Env, I.Ctx))
 newFutharkiState cfg file = runExceptT $ do
   (ws, imports, src) <-
-    badOnLeft show
+    badOnLeft prettyCompilerError
       =<< liftIO
         ( runExceptT (readProgramFile [] file)
             `catch` \(err :: IOException) ->
@@ -123,30 +121,28 @@
         )
   when (interpreterPrintWarnings cfg) $
     liftIO $
-      hPutStr stderr $
-        pretty ws
+      hPutDoc stderr $
+        prettyWarnings ws
 
   let imp = T.mkInitialImport "."
   ienv1 <-
-    foldM (\ctx -> badOnLeft show <=< runInterpreter' . I.interpretImport ctx) I.initialCtx $
+    foldM (\ctx -> badOnLeft I.prettyInterpreterError <=< runInterpreter' . I.interpretImport ctx) I.initialCtx $
       map (fmap fileProg) imports
   (tenv1, d1, src') <-
-    badOnLeft pretty $
-      snd $
-        T.checkDec imports src T.initialEnv imp $
-          mkOpen "/prelude/prelude"
+    badOnLeft T.prettyTypeError . snd $
+      T.checkDec imports src T.initialEnv imp $
+        mkOpen "/prelude/prelude"
   (tenv2, d2, _) <-
-    badOnLeft pretty $
-      snd $
-        T.checkDec imports src' tenv1 imp $
-          mkOpen $
-            toPOSIX $
-              dropExtension file
-  ienv2 <- badOnLeft show =<< runInterpreter' (I.interpretDec ienv1 d1)
-  ienv3 <- badOnLeft show =<< runInterpreter' (I.interpretDec ienv2 d2)
+    badOnLeft T.prettyTypeError . snd $
+      T.checkDec imports src' tenv1 imp $
+        mkOpen $
+          toPOSIX $
+            dropExtension file
+  ienv2 <- badOnLeft I.prettyInterpreterError =<< runInterpreter' (I.interpretDec ienv1 d1)
+  ienv3 <- badOnLeft I.prettyInterpreterError =<< runInterpreter' (I.interpretDec ienv2 d2)
   pure (tenv2, ienv3)
   where
-    badOnLeft :: (err -> String) -> Either err a -> ExceptT String IO a
+    badOnLeft :: (err -> err') -> Either err a -> ExceptT err' IO a
     badOnLeft _ (Right x) = pure x
     badOnLeft p (Left err) = throwError $ p err
 
diff --git a/src/Futhark/CLI/Test.hs b/src/Futhark/CLI/Test.hs
--- a/src/Futhark/CLI/Test.hs
+++ b/src/Futhark/CLI/Test.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
 
 -- | @futhark test@
 module Futhark.CLI.Test (main) where
@@ -10,22 +8,22 @@
 import Control.Exception
 import Control.Monad
 import Control.Monad.Except hiding (throwError)
-import qualified Control.Monad.Except as E
-import qualified Data.ByteString as SBS
-import qualified Data.ByteString.Lazy as LBS
+import Control.Monad.Except qualified as E
+import Data.ByteString qualified as SBS
+import Data.ByteString.Lazy qualified as LBS
 import Data.List (delete, partition)
-import qualified Data.Map.Strict as M
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
+import Data.Map.Strict qualified as M
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
 import Futhark.Analysis.Metrics.Type
 import Futhark.Server
 import Futhark.Test
 import Futhark.Util (atMostChars, fancyTerminal)
-import Futhark.Util.Console
 import Futhark.Util.Options
+import Futhark.Util.Pretty (annotate, bold, hardline, pretty, putDoc, vsep)
 import Futhark.Util.Table
-import System.Console.ANSI
-import qualified System.Console.Terminal.Size as Terminal
+import System.Console.ANSI (clearFromCursorToScreenEnd, clearLine, cursorUpLine)
+import System.Console.Terminal.Size qualified as Terminal
 import System.Environment
 import System.Exit
 import System.FilePath
@@ -454,16 +452,16 @@
       InputOutputs entry $ filter (not . any excluded . runTags) runs
     excluded = (`elem` configExclude config) . T.pack
 
-statusTable :: TestStatus -> String
-statusTable ts = buildTable rows 1
+putStatusTable :: TestStatus -> IO ()
+putStatusTable ts = hPutTable stdout rows 1
   where
     rows =
-      [ [mkEntry "", passed, failed, mkEntry "remaining"],
-        map mkEntry ["programs", passedProgs, failedProgs, remainProgs'],
-        map mkEntry ["runs", passedRuns, failedRuns, remainRuns']
+      [ [mkEntry "" mempty, passed, failed, mkEntry "remaining" mempty],
+        map (`mkEntry` mempty) ["programs", passedProgs, failedProgs, remainProgs'],
+        map (`mkEntry` mempty) ["runs", passedRuns, failedRuns, remainRuns']
       ]
-    passed = ("passed", [SetColor Foreground Vivid Green])
-    failed = ("failed", [SetColor Foreground Vivid Red])
+    passed = mkEntry "passed" $ color Green
+    failed = mkEntry "failed" $ color Red
     passedProgs = show $ testStatusPass ts
     failedProgs = show $ testStatusFail ts
     totalProgs = show $ testStatusTotal ts
@@ -476,9 +474,7 @@
     remainRuns' = remainRuns ++ "/" ++ totalRuns
 
 tableLines :: Int
-tableLines = 1 + (length . lines $ blankTable)
-  where
-    blankTable = statusTable $ TestStatus [] [] 0 0 0 0 0 0 0
+tableLines = 8
 
 spaceTable :: IO ()
 spaceTable = putStr $ replicate tableLines '\n'
@@ -486,7 +482,7 @@
 reportTable :: TestStatus -> IO ()
 reportTable ts = do
   moveCursorToTableTop
-  putStrLn $ statusTable ts
+  putStatusTable ts
   clearLine
   w <- maybe 80 Terminal.width <$> Terminal.size
   putStrLn $ atMostChars (w - length labelstr) running
@@ -561,7 +557,11 @@
                   Failure s -> do
                     when fancy moveCursorToTableTop
                     clear
-                    putStr $ inBold (testCaseProgram test <> ":\n") <> T.unpack (T.unlines s)
+                    putDoc $
+                      annotate bold (pretty (testCaseProgram test) <> ":")
+                        <> hardline
+                        <> vsep (map pretty s)
+                        <> hardline
                     when fancy spaceTable
                     getResults $
                       ts'
@@ -592,7 +592,9 @@
         }
 
   -- Removes "Now testing" output.
-  when fancy $ cursorUpLine 1 >> clearLine
+  if fancy
+    then cursorUpLine 1 >> clearLine
+    else putStrLn $ show (testStatusPass ts) <> "/" <> show (testStatusTotal ts) <> " passed."
 
   unless (null excluded) . putStrLn $
     show (length excluded) ++ " program(s) excluded."
diff --git a/src/Futhark/CLI/WASM.hs b/src/Futhark/CLI/WASM.hs
--- a/src/Futhark/CLI/WASM.hs
+++ b/src/Futhark/CLI/WASM.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | @futhark wasm@
 module Futhark.CLI.WASM (main) where
 
diff --git a/src/Futhark/CodeGen/Backends/CCUDA.hs b/src/Futhark/CodeGen/Backends/CCUDA.hs
--- a/src/Futhark/CodeGen/Backends/CCUDA.hs
+++ b/src/Futhark/CodeGen/Backends/CCUDA.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Code generation for CUDA.
 module Futhark.CodeGen.Backends.CCUDA
@@ -14,22 +12,22 @@
 
 import Control.Monad
 import Data.Maybe (catMaybes)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.CCUDA.Boilerplate
 import Futhark.CodeGen.Backends.COpenCL.Boilerplate (commonOptions, sizeLoggingCode)
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericC.Options
 import Futhark.CodeGen.Backends.SimpleRep (primStorageType, toStorage)
 import Futhark.CodeGen.ImpCode.OpenCL
-import qualified Futhark.CodeGen.ImpGen.CUDA as ImpGen
+import Futhark.CodeGen.ImpGen.CUDA qualified as ImpGen
 import Futhark.IR.GPUMem hiding
   ( CmpSizeLe,
     GetSize,
     GetSizeMax,
   )
 import Futhark.MonadFreshNames
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import NeatInterpolation (untrimming)
 
 -- | Compile the program to C with calls to CUDA.
@@ -298,7 +296,7 @@
     cudaSizeClass SizeTile = "tile_size"
     cudaSizeClass SizeRegTile = "reg_tile_size"
     cudaSizeClass SizeLocalMemory = "shared_memory"
-    cudaSizeClass (SizeBespoke x _) = pretty x
+    cudaSizeClass (SizeBespoke x _) = prettyString x
 callKernel (LaunchKernel safety kernel_name args num_blocks block_size) = do
   args_arr <- newVName "kernel_args"
   time_start <- newVName "time_start"
@@ -359,7 +357,7 @@
       typename int64_t $id:time_start = 0, $id:time_end = 0;
       if (ctx->debugging) {
         fprintf(ctx->log, "Launching %s with grid size [%ld, %ld, %ld] and block size [%ld, %ld, %ld]; shared memory: %d bytes.\n",
-                $string:(pretty kernel_name),
+                $string:(prettyString kernel_name),
                 (long int)$exp:grid_x, (long int)$exp:grid_y, (long int)$exp:grid_z,
                 (long int)$exp:block_x, (long int)$exp:block_y, (long int)$exp:block_z,
                 (int)$exp:shared_tot);
@@ -377,7 +375,7 @@
         CUDA_SUCCEED_FATAL(cuCtxSynchronize());
         $id:time_end = get_wall_time();
         fprintf(ctx->log, "Kernel %s runtime: %ldus\n",
-                $string:(pretty kernel_name), $id:time_end - $id:time_start);
+                $string:(prettyString kernel_name), $id:time_end - $id:time_start);
       }
     }|]
 
diff --git a/src/Futhark/CodeGen/Backends/CCUDA/Boilerplate.hs b/src/Futhark/CodeGen/Backends/CCUDA/Boilerplate.hs
--- a/src/Futhark/CodeGen/Backends/CCUDA/Boilerplate.hs
+++ b/src/Futhark/CodeGen/Backends/CCUDA/Boilerplate.hs
@@ -8,9 +8,9 @@
   )
 where
 
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.COpenCL.Boilerplate
   ( copyDevToDev,
     copyDevToHost,
@@ -22,12 +22,13 @@
     kernelRuns,
     kernelRuntime,
   )
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Futhark.CodeGen.Backends.GenericC qualified as GC
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.ImpCode.OpenCL
 import Futhark.CodeGen.RTS.C (cudaH, freeListH)
 import Futhark.Util (chunk, zEncodeString)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 errorMsgNumArgs :: ErrorMsg a -> Int
 errorMsgNumArgs = length . errorMsgArgTypes
@@ -87,9 +88,9 @@
 
 generateSizeFuns :: M.Map Name SizeClass -> GC.CompilerM OpenCL () ()
 generateSizeFuns sizes = do
-  let size_name_inits = map (\k -> [C.cinit|$string:(pretty k)|]) $ M.keys sizes
-      size_var_inits = map (\k -> [C.cinit|$string:(zEncodeString (pretty k))|]) $ M.keys sizes
-      size_class_inits = map (\c -> [C.cinit|$string:(pretty c)|]) $ M.elems sizes
+  let size_name_inits = map (\k -> [C.cinit|$string:(prettyString k)|]) $ M.keys sizes
+      size_var_inits = map (\k -> [C.cinit|$string:(zEncodeString (prettyString k))|]) $ M.keys sizes
+      size_class_inits = map (\c -> [C.cinit|$string:(prettyString c)|]) $ M.elems sizes
 
   GC.earlyDecl [C.cedecl|static const char *tuning_param_names[] = { $inits:size_name_inits };|]
   GC.earlyDecl [C.cedecl|static const char *tuning_param_vars[] = { $inits:size_var_inits };|]
@@ -316,7 +317,7 @@
           [C.cstm|CUDA_SUCCEED_FATAL(cuModuleGetFunction(
                                      &ctx->$id:name,
                                      ctx->cuda.module,
-                                     $string:(pretty (C.toIdent name mempty))));|]
+                                     $string:(T.unpack (idText (C.toIdent name mempty)))));|]
         )
           : forCostCentre name
 
diff --git a/src/Futhark/CodeGen/Backends/COpenCL.hs b/src/Futhark/CodeGen/Backends/COpenCL.hs
--- a/src/Futhark/CodeGen/Backends/COpenCL.hs
+++ b/src/Futhark/CodeGen/Backends/COpenCL.hs
@@ -1,7 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Code generation for C with OpenCL.
 module Futhark.CodeGen.Backends.COpenCL
@@ -15,21 +12,21 @@
 
 import Control.Monad hiding (mapM)
 import Data.List (intercalate)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.COpenCL.Boilerplate
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericC.Options
 import Futhark.CodeGen.Backends.SimpleRep (primStorageType, toStorage)
 import Futhark.CodeGen.ImpCode.OpenCL
-import qualified Futhark.CodeGen.ImpGen.OpenCL as ImpGen
+import Futhark.CodeGen.ImpGen.OpenCL qualified as ImpGen
 import Futhark.IR.GPUMem hiding
   ( CmpSizeLe,
     GetSize,
     GetSizeMax,
   )
 import Futhark.MonadFreshNames
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import NeatInterpolation (untrimming)
 
 -- | Compile the program to C with calls to OpenCL.
@@ -337,7 +334,7 @@
   GC.stm [C.cstm|$id:v = *ctx->tuning_params.$id:key <= $exp:x';|]
   sizeLoggingCode v key x'
 callKernel (GetSizeMax v size_class) =
-  let field = "max_" ++ pretty size_class
+  let field = "max_" ++ prettyString size_class
    in GC.stm [C.cstm|$id:v = ctx->opencl.$id:field;|]
 callKernel (LaunchKernel safety name args num_workgroups workgroup_size) = do
   -- The other failure args are set automatically when the kernel is
@@ -428,7 +425,7 @@
         $id:time_end = get_wall_time();
         long int $id:time_diff = $id:time_end - $id:time_start;
         fprintf(ctx->log, "kernel %s runtime: %ldus\n",
-                $string:(pretty kernel_name), $id:time_diff);
+                $string:(prettyString kernel_name), $id:time_diff);
       }
     }|]
   where
@@ -449,7 +446,7 @@
           ++ " and local work size "
           ++ dims
           ++ "; local memory: %d bytes.\n",
-        [C.cexp|$string:(pretty kernel_name)|]
+        [C.cexp|$string:(prettyString kernel_name)|]
           : map (kernelDim global_work_size) [0 .. kernel_rank - 1]
           ++ map (kernelDim local_work_size) [0 .. kernel_rank - 1]
           ++ [[C.cexp|(int)$exp:local_bytes|]]
diff --git a/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs b/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
--- a/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
+++ b/src/Futhark/CodeGen/Backends/COpenCL/Boilerplate.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 module Futhark.CodeGen.Backends.COpenCL.Boilerplate
@@ -20,18 +18,19 @@
 where
 
 import Control.Monad.State
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericC.Options
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.ImpCode.OpenCL
 import Futhark.CodeGen.OpenCL.Heuristics
 import Futhark.CodeGen.RTS.C (freeListH, openclH)
 import Futhark.Util (chunk, zEncodeString)
-import Futhark.Util.Pretty (prettyOneLine)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Futhark.Util.Pretty (prettyTextOneLine)
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 errorMsgNumArgs :: ErrorMsg a -> Int
 errorMsgNumArgs = length . errorMsgArgTypes
@@ -42,7 +41,7 @@
         let escapeChar '%' = "%%"
             escapeChar c = [c]
          in concatMap escapeChar
-      onPart (ErrorString s) = printfEscape s
+      onPart (ErrorString s) = printfEscape $ T.unpack s
       -- FIXME: bogus for non-ints.
       onPart ErrorVal {} = "%lld"
       onFailure i (FailureMsg emsg@(ErrorMsg parts) backtrace) =
@@ -86,9 +85,9 @@
 
   mapM_ GC.earlyDecl top_decls
 
-  let size_name_inits = map (\k -> [C.cinit|$string:(pretty k)|]) $ M.keys sizes
-      size_var_inits = map (\k -> [C.cinit|$string:(zEncodeString (pretty k))|]) $ M.keys sizes
-      size_class_inits = map (\c -> [C.cinit|$string:(pretty c)|]) $ M.elems sizes
+  let size_name_inits = map (\k -> [C.cinit|$string:(prettyString k)|]) $ M.keys sizes
+      size_var_inits = map (\k -> [C.cinit|$string:(zEncodeString (prettyString k))|]) $ M.keys sizes
+      size_class_inits = map (\c -> [C.cinit|$string:(prettyString c)|]) $ M.elems sizes
       num_sizes = M.size sizes
 
   GC.earlyDecl [C.cedecl|static const char *tuning_param_names[] = { $inits:size_name_inits };|]
@@ -583,11 +582,11 @@
 loadKernel :: (KernelName, KernelSafety) -> C.Stm
 loadKernel (name, safety) =
   [C.cstm|{
-  ctx->$id:name = clCreateKernel(prog, $string:(pretty (C.toIdent name mempty)), &error);
+  ctx->$id:name = clCreateKernel(prog, $string:(T.unpack (idText (C.toIdent name mempty))), &error);
   OPENCL_SUCCEED_FATAL(error);
   $items:set_args
   if (ctx->debugging) {
-    fprintf(ctx->log, "Created kernel %s.\n", $string:(pretty name));
+    fprintf(ctx->log, "Created kernel %s.\n", $string:(prettyString name));
   }
   }|]
   where
@@ -616,7 +615,7 @@
 costCentreReport :: [Name] -> [C.BlockItem]
 costCentreReport names = report_kernels ++ [report_total]
   where
-    longest_name = foldl max 0 $ map (length . pretty) names
+    longest_name = foldl max 0 $ map (length . prettyString) names
     report_kernels = concatMap reportKernel names
     format_string name =
       let padding = replicate (longest_name - length name) ' '
@@ -629,7 +628,7 @@
           total_runtime = kernelRuntime name
        in [ [C.citem|
                str_builder(&builder,
-                           $string:(format_string (pretty name)),
+                           $string:(format_string (prettyString name)),
                            ctx->$id:runs,
                            (long int) ctx->$id:total_runtime / (ctx->$id:runs != 0 ? ctx->$id:runs : 1),
                            (long int) ctx->$id:total_runtime);
@@ -697,7 +696,7 @@
 sizeLoggingCode v key x' = do
   GC.stm
     [C.cstm|if (ctx->logging) {
-    fprintf(ctx->log, "Compared %s <= %ld: %s.\n", $string:(prettyOneLine key), (long)$exp:x', $id:v ? "true" : "false");
+    fprintf(ctx->log, "Compared %s <= %ld: %s.\n", $string:(T.unpack (prettyTextOneLine key)), (long)$exp:x', $id:v ? "true" : "false");
     }|]
 
 -- Options that are common to multiple GPU-like backends.
diff --git a/src/Futhark/CodeGen/Backends/GenericC.hs b/src/Futhark/CodeGen/Backends/GenericC.hs
--- a/src/Futhark/CodeGen/Backends/GenericC.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | C code generation for whole programs, built on
 -- "Futhark.CodeGen.Backends.GenericC.Monad".  Most of this module is
@@ -11,7 +6,6 @@
 module Futhark.CodeGen.Backends.GenericC
   ( compileProg,
     compileProg',
-    compileFun,
     defaultOperations,
     CParts (..),
     asLibrary,
@@ -24,25 +18,26 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.DList as DL
+import Data.DList qualified as DL
 import Data.Loc
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.CLI (cliDefs)
 import Futhark.CodeGen.Backends.GenericC.Code
 import Futhark.CodeGen.Backends.GenericC.EntryPoints
+import Futhark.CodeGen.Backends.GenericC.Fun
 import Futhark.CodeGen.Backends.GenericC.Monad
 import Futhark.CodeGen.Backends.GenericC.Options
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.Backends.GenericC.Server (serverDefs)
 import Futhark.CodeGen.Backends.GenericC.Types
 import Futhark.CodeGen.ImpCode
 import Futhark.CodeGen.RTS.C (cacheH, contextH, contextPrototypesH, errorsH, halfH, lockH, timingH, utilH)
-import qualified Futhark.Manifest as Manifest
+import Futhark.Manifest qualified as Manifest
 import Futhark.MonadFreshNames
-import Futhark.Util.Pretty (prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import NeatInterpolation (untrimming)
 
 defCall :: CallCompiler op s
@@ -99,76 +94,9 @@
     defCompiler _ =
       error "The default compiler cannot compile extended operations"
 
-compileFunBody :: [C.Exp] -> [Param] -> Code op -> CompilerM op s ()
-compileFunBody output_ptrs outputs code = do
-  mapM_ declareOutput outputs
-  compileCode code
-  zipWithM_ setRetVal' output_ptrs outputs
-  where
-    declareOutput (MemParam name space) =
-      declMem name space
-    declareOutput (ScalarParam name pt) = do
-      let ctp = primTypeToCType pt
-      decl [C.cdecl|$ty:ctp $id:name;|]
-
-    setRetVal' p (MemParam name space) = do
-      resetMem [C.cexp|*$exp:p|] space
-      setMem [C.cexp|*$exp:p|] name space
-    setRetVal' p (ScalarParam name _) =
-      stm [C.cstm|*$exp:p = $id:name;|]
-
-compileFun :: [C.BlockItem] -> [C.Param] -> (Name, Function op) -> CompilerM op s (C.Definition, C.Func)
-compileFun get_constants extra (fname, func@(Function _ outputs inputs body)) = inNewFunction $ do
-  (outparams, out_ptrs) <- unzip <$> mapM compileOutput outputs
-  inparams <- mapM compileInput inputs
-
-  cachingMemory (lexicalMemoryUsage func) $ \decl_cached free_cached -> do
-    body' <- collect $ compileFunBody out_ptrs outputs body
-    decl_mem <- declAllocatedMem
-    free_mem <- freeAllocatedMem
-
-    pure
-      ( [C.cedecl|static int $id:(funName fname)($params:extra, $params:outparams, $params:inparams);|],
-        [C.cfun|static int $id:(funName fname)($params:extra, $params:outparams, $params:inparams) {
-               $stms:ignores
-               int err = 0;
-               $items:decl_cached
-               $items:decl_mem
-               $items:get_constants
-               $items:body'
-              cleanup:
-               {
-               $stms:free_cached
-               $items:free_mem
-               }
-               return err;
-  }|]
-      )
-  where
-    -- Ignore all the boilerplate parameters, just in case we don't
-    -- actually need to use them.
-    ignores = [[C.cstm|(void)$id:p;|] | C.Param (Just p) _ _ _ <- extra]
-
-    compileInput (ScalarParam name bt) = do
-      let ctp = primTypeToCType bt
-      pure [C.cparam|$ty:ctp $id:name|]
-    compileInput (MemParam name space) = do
-      ty <- memToCType name space
-      pure [C.cparam|$ty:ty $id:name|]
-
-    compileOutput (ScalarParam name bt) = do
-      let ctp = primTypeToCType bt
-      p_name <- newVName $ "out_" ++ baseString name
-      pure ([C.cparam|$ty:ctp *$id:p_name|], [C.cexp|$id:p_name|])
-    compileOutput (MemParam name space) = do
-      ty <- memToCType name space
-      p_name <- newVName $ baseString name ++ "_p"
-      pure ([C.cparam|$ty:ty *$id:p_name|], [C.cexp|$id:p_name|])
-
 declsCode :: (HeaderSection -> Bool) -> CompilerState s -> T.Text
 declsCode p =
-  T.unlines
-    . map prettyText
+  definitionsText
     . concatMap (DL.toList . snd)
     . filter (p . fst)
     . M.toList
@@ -477,8 +405,8 @@
 $timingH
 |]
 
-  let early_decls = T.unlines $ map prettyText $ DL.toList $ compEarlyDecls endstate
-      lib_decls = T.unlines $ map prettyText $ DL.toList $ compLibDecls endstate
+  let early_decls = definitionsText $ DL.toList $ compEarlyDecls endstate
+      lib_decls = definitionsText $ DL.toList $ compLibDecls endstate
       clidefs = cliDefs options manifest
       serverdefs = serverDefs options manifest
       libdefs =
@@ -551,18 +479,12 @@
       generateCommonLibFuns memreport
 
       pure
-        ( T.unlines $ map prettyText prototypes,
-          T.unlines $ map (prettyText . funcToDef) functions,
-          T.unlines $ map prettyText entry_points,
+        ( definitionsText prototypes,
+          funcsText functions,
+          definitionsText entry_points,
           Manifest.Manifest (M.fromList entry_points_manifest) type_funs backend version
         )
 
-    funcToDef func = C.FuncDef func loc
-      where
-        loc = case func of
-          C.OldFunc _ _ _ _ _ _ l -> l
-          C.Func _ _ _ _ _ l -> l
-
 -- | Compile imperative program to a C program.  Always uses the
 -- function named "main" as entry point, so make sure it is defined.
 compileProg ::
@@ -727,7 +649,7 @@
 
     constMacro p = ([C.citem|$escstm:def|], [C.citem|$escstm:undef|])
       where
-        p' = pretty (C.toIdent (paramName p) mempty)
+        p' = T.unpack $ idText (C.toIdent (paramName p) mempty)
         def = "#define " ++ p' ++ " (" ++ "ctx->constants." ++ p' ++ ")"
         undef = "#undef " ++ p'
 
diff --git a/src/Futhark/CodeGen/Backends/GenericC/CLI.hs b/src/Futhark/CodeGen/Backends/GenericC/CLI.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/CLI.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/CLI.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Code generation for standalone executables.
 module Futhark.CodeGen.Backends.GenericC.CLI
@@ -12,9 +7,10 @@
 where
 
 import Data.List (unzip5)
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Options
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.Backends.SimpleRep
   ( cproduct,
     primAPIType,
@@ -23,9 +19,9 @@
   )
 import Futhark.CodeGen.RTS.C (tuningH, valuesH)
 import Futhark.Manifest
-import Futhark.Util.Pretty (pretty, prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Futhark.Util.Pretty (prettyString)
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 genericOptions :: [Option]
 genericOptions =
@@ -270,7 +266,11 @@
       let info = tname <> "_info"
        in [C.cstm|write_scalar(stdout, binary_output, &$id:info, &$exp:e);|]
     Just (TypeOpaque desc _ _) ->
-      [C.cstm|printf("#<opaque %s>", $string:(T.unpack desc));|]
+      [C.cstm|{
+         fprintf(stderr, "Values of type \"%s\" have no external representation.\n", $string:(T.unpack desc));
+         retval = 1;
+         goto print_end;
+       }|]
     Just (TypeArray _ et rank ops) ->
       let et' = uncurry primAPIType $ scalarToPrim et
           values_array = arrayValues ops
@@ -349,9 +349,10 @@
                 $stms:free_input
               |]
    in ( [C.cedecl|
-   static void $id:cli_entry_point_function_name($ty:ctx_ty *ctx) {
+   static int $id:cli_entry_point_function_name($ty:ctx_ty *ctx) {
      typename int64_t t_start, t_end;
      int time_runs = 0, profile_run = 0;
+     int retval = 0;
 
      // We do not want to profile all the initialisation.
      $id:pause_profiling(ctx);
@@ -361,7 +362,7 @@
      $items:(mconcat input_items)
 
      if (end_of_input(stdin) != 0) {
-       futhark_panic(1, "Expected EOF on stdin after reading input for \"%s\".\n", $string:(pretty entry_point_name));
+       futhark_panic(1, "Expected EOF on stdin after reading input for \"%s\".\n", $string:(prettyString entry_point_name));
      }
 
      $items:output_decls
@@ -392,11 +393,12 @@
        }
        $stms:printstms
      }
-
+     print_end: {}
      $stms:free_outputs
+     return retval;
    }|],
         [C.cinit|{ .name = $string:(T.unpack entry_point_name),
-                       .fun = $id:cli_entry_point_function_name }|]
+                   .fun = $id:cli_entry_point_function_name }|]
       )
 
 {-# NOINLINE cliDefs #-}
@@ -411,7 +413,7 @@
           map (uncurry (cliEntryPoint manifest)) $
             M.toList $
               manifestEntryPoints manifest
-   in prettyText
+   in definitionsText
         [C.cunit|
 $esc:("#include <getopt.h>")
 $esc:("#include <ctype.h>")
@@ -435,7 +437,7 @@
 
 $edecls:cli_entry_point_decls
 
-typedef void entry_point_fun(struct futhark_context*);
+typedef int entry_point_fun(struct futhark_context*);
 
 struct entry_point_entry {
   const char *name;
@@ -443,6 +445,7 @@
 };
 
 int main(int argc, char** argv) {
+  int retval = 0;
   fut_progname = argv[0];
 
   struct futhark_context_config *cfg = futhark_context_config_new();
@@ -492,7 +495,7 @@
       fprintf(stderr, "Send EOF (CTRL-d) after typing all input values.\n");
     }
 
-    entry_point_fun(ctx);
+    retval = entry_point_fun(ctx);
 
     if (runtime_file != NULL) {
       fclose(runtime_file);
@@ -507,5 +510,5 @@
 
   futhark_context_free(ctx);
   futhark_context_config_free(cfg);
-  return 0;
+  return retval;
 }|]
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Code.hs b/src/Futhark/CodeGen/Backends/GenericC/Code.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Code.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Code.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Translation of ImpCode Exp and Code to C.
 module Futhark.CodeGen.Backends.GenericC.Code
@@ -19,19 +14,20 @@
 import Control.Monad.Reader
 import Data.Loc
 import Data.Maybe
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Monad
 import Futhark.CodeGen.ImpCode
 import Futhark.IR.Prop (isBuiltInFunction)
 import Futhark.MonadFreshNames
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 errorMsgString :: ErrorMsg Exp -> CompilerM op s (String, [C.Exp])
 errorMsgString (ErrorMsg parts) = do
   let boolStr e = [C.cexp|($exp:e) ? "true" : "false"|]
       asLongLong e = [C.cexp|(long long int)$exp:e|]
       asDouble e = [C.cexp|(double)$exp:e|]
-      onPart (ErrorString s) = pure ("%s", [C.cexp|$string:s|])
+      onPart (ErrorString s) = pure ("%s", [C.cexp|$string:(T.unpack s)|])
       onPart (ErrorVal Bool x) = ("%s",) . boolStr <$> compileExp x
       onPart (ErrorVal Unit _) = pure ("%s", [C.cexp|"()"|])
       onPart (ErrorVal (IntType Int8) x) = ("%hhd",) <$> compileExp x
@@ -82,7 +78,7 @@
   pure [C.cexp|($exp:x' > 0 ? 1 : 0) - ($exp:x' < 0 ? 1 : 0) != 0|]
 compilePrimExp f (UnOpExp op x) = do
   x' <- compilePrimExp f x
-  pure [C.cexp|$id:(pretty op)($exp:x')|]
+  pure [C.cexp|$id:(prettyString op)($exp:x')|]
 compilePrimExp f (CmpOpExp cmp x y) = do
   x' <- compilePrimExp f x
   y' <- compilePrimExp f y
@@ -92,10 +88,10 @@
     FCmpLe {} -> [C.cexp|$exp:x' <= $exp:y'|]
     CmpLlt {} -> [C.cexp|$exp:x' < $exp:y'|]
     CmpLle {} -> [C.cexp|$exp:x' <= $exp:y'|]
-    _ -> [C.cexp|$id:(pretty cmp)($exp:x', $exp:y')|]
+    _ -> [C.cexp|$id:(prettyString cmp)($exp:x', $exp:y')|]
 compilePrimExp f (ConvOpExp conv x) = do
   x' <- compilePrimExp f x
-  pure [C.cexp|$id:(pretty conv)($exp:x')|]
+  pure [C.cexp|$id:(prettyString conv)($exp:x')|]
 compilePrimExp f (BinOpExp bop x y) = do
   x' <- compilePrimExp f x
   y' <- compilePrimExp f y
@@ -117,7 +113,7 @@
     Or {} -> [C.cexp|$exp:x' | $exp:y'|]
     LogAnd {} -> [C.cexp|$exp:x' && $exp:y'|]
     LogOr {} -> [C.cexp|$exp:x' || $exp:y'|]
-    _ -> [C.cexp|$id:(pretty bop)($exp:x', $exp:y')|]
+    _ -> [C.cexp|$id:(prettyString bop)($exp:x', $exp:y')|]
 compilePrimExp f (FunExp h args _) = do
   args' <- mapM (compilePrimExp f) args
   pure [C.cexp|$id:(funName (nameFromString h))($args:args')|]
@@ -172,7 +168,7 @@
 compileCode Skip = pure ()
 compileCode (Comment s code) = do
   xs <- collect $ compileCode code
-  let comment = "// " ++ s
+  let comment = "// " ++ T.unpack s
   stm
     [C.cstm|$comment:comment
               { $items:xs }
@@ -229,7 +225,7 @@
       asks (opsError . envOperations) <*> pure msg <*> pure stacktrace
   stm [C.cstm|if (!$exp:e') { $items:err }|]
   where
-    stacktrace = prettyStacktrace 0 $ map locStr $ loc : locs
+    stacktrace = T.unpack $ prettyStacktrace 0 $ map locText $ loc : locs
 compileCode (Allocate _ _ ScalarSpace {}) =
   -- Handled by the declaration of the memory block, which is
   -- translated to an actual array.
@@ -330,7 +326,7 @@
   let ct = primTypeToCType t
   decl [C.cdecl|$tyquals:(volQuals vol) $ty:ct $id:name;|]
 compileCode (DeclareArray name ScalarSpace {} _ _) =
-  error $ "Cannot declare array " ++ pretty name ++ " in scalar space."
+  error $ "Cannot declare array " ++ prettyString name ++ " in scalar space."
 compileCode (DeclareArray name DefaultSpace t vs) = do
   name_realtype <- newVName $ baseString name ++ "_realtype"
   let ct = primTypeToCType t
@@ -348,7 +344,7 @@
       [C.cexp|(struct memblock){NULL,
                                 (unsigned char*)$id:name_realtype,
                                 0,
-                                $string:(pretty name)}|]
+                                $string:(prettyString name)}|]
   item [C.citem|struct memblock $id:name = ctx->$id:name;|]
 compileCode (DeclareArray name (Space space) t vs) =
   join $
diff --git a/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs b/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/EntryPoints.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Generate the entry point packing/unpacking code.
 module Futhark.CodeGen.Backends.GenericC.EntryPoints
@@ -14,14 +9,14 @@
 import Control.Monad.Reader
 import Data.Char (isAlpha, isAlphaNum)
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Monad
 import Futhark.CodeGen.Backends.GenericC.Types (opaqueToCType, valueTypeToCType)
 import Futhark.CodeGen.ImpCode
-import qualified Futhark.Manifest as Manifest
+import Futhark.Manifest qualified as Manifest
 import Futhark.Util (zEncodeString)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 valueDescToType :: ValueDesc -> ValueType
 valueDescToType (ScalarValue pt signed _) =
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Fun.hs b/src/Futhark/CodeGen/Backends/GenericC/Fun.hs
new file mode 100644
--- /dev/null
+++ b/src/Futhark/CodeGen/Backends/GenericC/Fun.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+-- | C code generation for functions.
+module Futhark.CodeGen.Backends.GenericC.Fun
+  ( compileFun,
+    module Futhark.CodeGen.Backends.GenericC.Monad,
+    module Futhark.CodeGen.Backends.GenericC.Code,
+  )
+where
+
+import Control.Monad.Reader
+import Futhark.CodeGen.Backends.GenericC.Code
+import Futhark.CodeGen.Backends.GenericC.Monad
+import Futhark.CodeGen.ImpCode
+import Futhark.MonadFreshNames
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
+
+compileFunBody :: [C.Exp] -> [Param] -> Code op -> CompilerM op s ()
+compileFunBody output_ptrs outputs code = do
+  mapM_ declareOutput outputs
+  compileCode code
+  zipWithM_ setRetVal' output_ptrs outputs
+  where
+    declareOutput (MemParam name space) =
+      declMem name space
+    declareOutput (ScalarParam name pt) = do
+      let ctp = primTypeToCType pt
+      decl [C.cdecl|$ty:ctp $id:name;|]
+
+    setRetVal' p (MemParam name space) = do
+      resetMem [C.cexp|*$exp:p|] space
+      setMem [C.cexp|*$exp:p|] name space
+    setRetVal' p (ScalarParam name _) =
+      stm [C.cstm|*$exp:p = $id:name;|]
+
+compileFun :: [C.BlockItem] -> [C.Param] -> (Name, Function op) -> CompilerM op s (C.Definition, C.Func)
+compileFun get_constants extra (fname, func@(Function _ outputs inputs body)) = inNewFunction $ do
+  (outparams, out_ptrs) <- unzip <$> mapM compileOutput outputs
+  inparams <- mapM compileInput inputs
+
+  cachingMemory (lexicalMemoryUsage func) $ \decl_cached free_cached -> do
+    body' <- collect $ compileFunBody out_ptrs outputs body
+    decl_mem <- declAllocatedMem
+    free_mem <- freeAllocatedMem
+
+    pure
+      ( [C.cedecl|static int $id:(funName fname)($params:extra, $params:outparams, $params:inparams);|],
+        [C.cfun|static int $id:(funName fname)($params:extra, $params:outparams, $params:inparams) {
+               $stms:ignores
+               int err = 0;
+               $items:decl_cached
+               $items:decl_mem
+               $items:get_constants
+               $items:body'
+              cleanup:
+               {
+               $stms:free_cached
+               $items:free_mem
+               }
+               return err;
+  }|]
+      )
+  where
+    -- Ignore all the boilerplate parameters, just in case we don't
+    -- actually need to use them.
+    ignores = [[C.cstm|(void)$id:p;|] | C.Param (Just p) _ _ _ <- extra]
+
+    compileInput (ScalarParam name bt) = do
+      let ctp = primTypeToCType bt
+      pure [C.cparam|$ty:ctp $id:name|]
+    compileInput (MemParam name space) = do
+      ty <- memToCType name space
+      pure [C.cparam|$ty:ty $id:name|]
+
+    compileOutput (ScalarParam name bt) = do
+      let ctp = primTypeToCType bt
+      p_name <- newVName $ "out_" ++ baseString name
+      pure ([C.cparam|$ty:ctp *$id:p_name|], [C.cexp|$id:p_name|])
+    compileOutput (MemParam name space) = do
+      ty <- memToCType name space
+      p_name <- newVName $ baseString name ++ "_p"
+      pure ([C.cparam|$ty:ty *$id:p_name|], [C.cexp|$id:p_name|])
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Monad.hs b/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Monad.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | C code generator framework.
 module Futhark.CodeGen.Backends.GenericC.Monad
@@ -89,16 +84,18 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Bifunctor (first)
-import qualified Data.DList as DL
+import Data.DList qualified as DL
 import Data.List (unzip4)
 import Data.Loc
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.Backends.SimpleRep
 import Futhark.CodeGen.ImpCode
 import Futhark.MonadFreshNames
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 -- How public an array type definition sould be.  Public types show up
 -- in the generated API, while private types are used only to
@@ -522,7 +519,7 @@
 setMem :: (C.ToExp a, C.ToExp b) => a -> b -> Space -> CompilerM op s ()
 setMem dest src space = do
   refcount <- fatMemory space
-  let src_s = pretty $ C.toExp src noLoc
+  let src_s = T.unpack $ expText $ C.toExp src noLoc
   if refcount
     then
       stm
@@ -547,7 +544,7 @@
 unRefMem mem space = do
   refcount <- fatMemory space
   cached <- isJust <$> cacheMem mem
-  let mem_s = pretty $ C.toExp mem noLoc
+  let mem_s = T.unpack $ expText $ C.toExp mem noLoc
   when (refcount && not cached) $
     stm
       [C.cstm|if ($id:(fatMemUnRef space)(ctx, &$exp:mem, $string:mem_s) != 0) {
@@ -563,7 +560,7 @@
   CompilerM op s ()
 allocMem mem size space on_failure = do
   refcount <- fatMemory space
-  let mem_s = pretty $ C.toExp mem noLoc
+  let mem_s = T.unpack $ expText $ C.toExp mem noLoc
   if refcount
     then
       stm
@@ -603,7 +600,7 @@
   let cached = M.keys $ M.filter (== DefaultSpace) lexical
 
   cached' <- forM cached $ \mem -> do
-    size <- newVName $ pretty mem <> "_cached_size"
+    size <- newVName $ prettyString mem <> "_cached_size"
     pure (mem, size)
 
   let lexMem env =
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Options.hs b/src/Futhark/CodeGen/Backends/GenericC/Options.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Options.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Options.hs
@@ -15,8 +15,8 @@
 import Data.Function ((&))
 import Data.List (intercalate)
 import Data.Maybe
-import qualified Language.C.Quote.C as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.C qualified as C
+import Language.C.Syntax qualified as C
 
 -- | Specification if a single command line option.  The option must
 -- have a long name, and may also have a short name.
@@ -34,7 +34,7 @@
 -- | Whether an option accepts an argument.
 data OptionArgument
   = NoArgument
-  | -- | The 'String' becomes part of the help text.
+  | -- | The 'String' becomes part of the help pretty.
     RequiredArgument String
   | OptionalArgument
 
@@ -114,7 +114,7 @@
       [C.cinit| { $string:(optionLongName option), $id:arg, NULL, $int:i } |]
       where
         arg = case optionArgument option of
-          NoArgument -> "no_argument"
+          NoArgument -> "no_argument" :: String
           RequiredArgument _ -> "required_argument"
           OptionalArgument -> "optional_argument"
 
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Pretty.hs b/src/Futhark/CodeGen/Backends/GenericC/Pretty.hs
new file mode 100644
--- /dev/null
+++ b/src/Futhark/CodeGen/Backends/GenericC/Pretty.hs
@@ -0,0 +1,33 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+-- | Compatibility shims for mainland-pretty; the prettyprinting
+-- library used by language-c-quote.
+module Futhark.CodeGen.Backends.GenericC.Pretty
+  ( expText,
+    definitionsText,
+    typeText,
+    idText,
+    funcsText,
+  )
+where
+
+import Data.Text qualified as T
+import Language.C.Pretty ()
+import Language.C.Syntax qualified as C
+import Text.PrettyPrint.Mainland qualified as MPP
+import Text.PrettyPrint.Mainland.Class qualified as MPP
+
+expText :: C.Exp -> T.Text
+expText = T.pack . MPP.pretty 8000 . MPP.ppr
+
+definitionsText :: [C.Definition] -> T.Text
+definitionsText = T.unlines . map (T.pack . MPP.pretty 8000 . MPP.ppr)
+
+typeText :: C.Type -> T.Text
+typeText = T.pack . MPP.pretty 8000 . MPP.ppr
+
+idText :: C.Id -> T.Text
+idText = T.pack . MPP.pretty 8000 . MPP.ppr
+
+funcsText :: [C.Func] -> T.Text
+funcsText = T.unlines . map (T.pack . MPP.pretty 8000 . MPP.ppr)
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Server.hs b/src/Futhark/CodeGen/Backends/GenericC/Server.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Server.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Server.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Code generation for server executables.
 module Futhark.CodeGen.Backends.GenericC.Server
@@ -12,16 +7,16 @@
 where
 
 import Data.Bifunctor (first, second)
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Options
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.Backends.SimpleRep
 import Futhark.CodeGen.RTS.C (serverH, tuningH, valuesH)
 import Futhark.Manifest
 import Futhark.Util (zEncodeString)
-import Futhark.Util.Pretty (prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import Language.Futhark.Core (nameFromText)
 
 genericOptions :: [Option]
@@ -321,7 +316,7 @@
         generateOptionParser "parse_options" $ genericOptions ++ options
       (boilerplate_defs, type_inits, entry_point_inits) =
         mkBoilerplate manifest
-   in prettyText
+   in definitionsText
         [C.cunit|
 $esc:("#include <getopt.h>")
 $esc:("#include <ctype.h>")
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Types.hs b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Types.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
@@ -1,9 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Code generation for public API types.
 module Futhark.CodeGen.Backends.GenericC.Types
@@ -16,16 +11,16 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Char (isDigit)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericC.Monad
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.ImpCode
-import qualified Futhark.Manifest as Manifest
+import Futhark.Manifest qualified as Manifest
 import Futhark.Util (chunks, mapAccumLM)
-import Futhark.Util.Pretty (prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 opaqueToCType :: String -> CompilerM op s C.Type
 opaqueToCType desc = do
@@ -545,7 +540,7 @@
       pure $
         Just
           ( pretty_name,
-            Manifest.TypeArray (prettyText arr_type) pt_name rank ops
+            Manifest.TypeArray (typeText arr_type) pt_name rank ops
           )
     Private ->
       pure Nothing
@@ -560,7 +555,7 @@
   libDecl [C.cedecl|struct $id:name { $sdecls:members };|]
   (ops, record) <- opaqueLibraryFunctions types desc ot
   let opaque_type = [C.cty|struct $id:name*|]
-  pure (T.pack desc, Manifest.TypeOpaque (prettyText opaque_type) ops record)
+  pure (T.pack desc, Manifest.TypeOpaque (typeText opaque_type) ops record)
   where
     field vt@(ValueType _ (Rank r) _) i = do
       ct <- valueTypeToCType Private vt
diff --git a/src/Futhark/CodeGen/Backends/GenericPython.hs b/src/Futhark/CodeGen/Backends/GenericPython.hs
--- a/src/Futhark/CodeGen/Backends/GenericPython.hs
+++ b/src/Futhark/CodeGen/Backends/GenericPython.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | A generic Python code generator which is polymorphic in the type
 -- of the operations.  Concretely, we use this to handle both
 -- sequential and PyOpenCL Python code.
@@ -48,19 +44,19 @@
 
 import Control.Monad.Identity
 import Control.Monad.RWS
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.Backends.GenericPython.AST
 import Futhark.CodeGen.Backends.GenericPython.Options
-import qualified Futhark.CodeGen.ImpCode as Imp
+import Futhark.CodeGen.ImpCode qualified as Imp
 import Futhark.CodeGen.RTS.Python
 import Futhark.Compiler.Config (CompilerMode (..))
 import Futhark.IR.Prop (isBuiltInFunction, subExpVars)
 import Futhark.IR.Syntax.Core (Space (..))
 import Futhark.MonadFreshNames
 import Futhark.Util (zEncodeString)
-import Futhark.Util.Pretty (pretty, prettyText)
+import Futhark.Util.Pretty (prettyString, prettyText)
 import Language.Futhark.Primitive hiding (Bool)
 
 -- | A substitute expression compiler, tried before the main
@@ -498,7 +494,7 @@
         Index (Var "self.constants") $
           IdxExp $
             String $
-              pretty $
+              prettyString $
                 Imp.paramName p
 
 compileConstants :: Imp.Constants op -> CompilerM op s ()
@@ -525,7 +521,7 @@
 simpleCall fname = Call (Var fname) . map Arg
 
 compileName :: VName -> String
-compileName = zEncodeString . pretty
+compileName = zEncodeString . prettyString
 
 compileDim :: Imp.DimSize -> CompilerM op s PyExp
 compileDim (Imp.Constant v) = pure $ compilePrimValue v
@@ -553,7 +549,7 @@
 
 entryPointOutput :: Imp.ExternalValue -> CompilerM op s PyExp
 entryPointOutput (Imp.OpaqueValue desc vs) =
-  simpleCall "opaque" . (String (pretty desc) :)
+  simpleCall "opaque" . (String (prettyString desc) :)
     <$> mapM (entryPointOutput . Imp.TransparentValue) vs
 entryPointOutput (Imp.TransparentValue (Imp.ScalarValue bt ept name)) = do
   name' <- compileVar name
@@ -721,7 +717,7 @@
 extValueDescName (Imp.TransparentValue v) = extName $ valueDescName v
 extValueDescName (Imp.OpaqueValue desc []) = extName $ zEncodeString desc
 extValueDescName (Imp.OpaqueValue desc (v : _)) =
-  extName $ zEncodeString desc ++ "_" ++ pretty (baseTag (valueDescVName v))
+  extName $ zEncodeString desc ++ "_" ++ prettyString (baseTag (valueDescVName v))
 
 extName :: String -> String
 extName = (++ "_ext")
@@ -782,7 +778,7 @@
         ]
     printValue' (Imp.TransparentValue (Imp.ArrayValue mem (Space _) bt ept shape)) e =
       printValue' (Imp.TransparentValue (Imp.ArrayValue mem DefaultSpace bt ept shape)) $
-        simpleCall (pretty e ++ ".get") []
+        simpleCall (prettyString e ++ ".get") []
     printValue' (Imp.TransparentValue _) e =
       pure
         [ Exp $
@@ -907,10 +903,10 @@
   (map descArg args, map desc res)
   where
     descArg ((_, u), d) = desc (u, d)
-    desc (u, Imp.OpaqueValue d _) = pretty u <> d
-    desc (u, Imp.TransparentValue (Imp.ScalarValue pt s _)) = pretty u <> readTypeEnum pt s
+    desc (u, Imp.OpaqueValue d _) = prettyString u <> d
+    desc (u, Imp.TransparentValue (Imp.ScalarValue pt s _)) = prettyString u <> readTypeEnum pt s
     desc (u, Imp.TransparentValue (Imp.ArrayValue _ _ pt s dims)) =
-      pretty u <> concat (replicate (length dims) "[]") <> readTypeEnum pt s
+      prettyString u <> concat (replicate (length dims) "[]") <> readTypeEnum pt s
 
 callEntryFun ::
   [PyStmt] ->
@@ -922,7 +918,7 @@
   (_, prepare_in, body_bin, _, res) <- prepareEntry entry fun
 
   let str_input = map (readInput . snd) decl_args
-      end_of_input = [Exp $ simpleCall "end_of_input" [String $ pretty fname]]
+      end_of_input = [Exp $ simpleCall "end_of_input" [String $ prettyString fname]]
 
       exitcall = [Exp $ simpleCall "sys.exit" [Field (String "Assertion.{} failed") "format(e)"]]
       except' = Catch (Var "AssertionError") exitcall
@@ -1139,10 +1135,10 @@
     Shl {} -> simple "<<"
     LogAnd {} -> simple "and"
     LogOr {} -> simple "or"
-    _ -> pure $ simpleCall (pretty op) [x', y']
+    _ -> pure $ simpleCall (prettyString op) [x', y']
 compilePrimExp f (Imp.ConvOpExp conv x) = do
   x' <- compilePrimExp f x
-  pure $ simpleCall (pretty conv) [x']
+  pure $ simpleCall (prettyString conv) [x']
 compilePrimExp f (Imp.CmpOpExp cmp x y) = do
   (x', y', simple) <- compileBinOpLike f x y
   case cmp of
@@ -1151,18 +1147,18 @@
     FCmpLe {} -> simple "<="
     CmpLlt -> simple "<"
     CmpLle -> simple "<="
-    _ -> pure $ simpleCall (pretty cmp) [x', y']
+    _ -> pure $ simpleCall (prettyString cmp) [x', y']
 compilePrimExp f (Imp.UnOpExp op exp1) =
   UnOp (compileUnOp op) <$> compilePrimExp f exp1
 compilePrimExp f (Imp.FunExp h args _) =
-  simpleCall (futharkFun (pretty h)) <$> mapM (compilePrimExp f) args
+  simpleCall (futharkFun (prettyString h)) <$> mapM (compilePrimExp f) args
 
 compileExp :: Imp.Exp -> CompilerM op s PyExp
 compileExp = compilePrimExp compileVar
 
 errorMsgString :: Imp.ErrorMsg Imp.Exp -> CompilerM op s (String, [PyExp])
 errorMsgString (Imp.ErrorMsg parts) = do
-  let onPart (Imp.ErrorString s) = pure ("%s", String s)
+  let onPart (Imp.ErrorString s) = pure ("%s", String $ T.unpack s)
       onPart (Imp.ErrorVal IntType {} x) = ("%d",) <$> compileExp x
       onPart (Imp.ErrorVal FloatType {} x) = ("%f",) <$> compileExp x
       onPart (Imp.ErrorVal Imp.Bool x) = ("%r",) <$> compileExp x
@@ -1193,8 +1189,8 @@
   bound' <- compileExp bound
   let i' = compileName i
   body' <- collect $ compileCode body
-  counter <- pretty <$> newVName "counter"
-  one <- pretty <$> newVName "one"
+  counter <- prettyString <$> newVName "counter"
+  one <- prettyString <$> newVName "one"
   stm $ Assign (Var i') $ simpleCall (compilePrimToNp (Imp.primExpType bound)) [Integer 0]
   stm $ Assign (Var one) $ simpleCall (compilePrimToNp (Imp.primExpType bound)) [Integer 1]
   stm $
@@ -1241,7 +1237,7 @@
   stm $ Assign name' $ Field (Var "self") (compileName name)
 compileCode (Imp.Comment s code) = do
   code' <- collect $ compileCode code
-  stm $ Comment s code'
+  stm $ Comment (T.unpack s) code'
 compileCode (Imp.Assert e msg (loc, locs)) = do
   e' <- compileExp e
   (formatstr, formatargs) <- errorMsgString msg
@@ -1254,13 +1250,13 @@
           (Tuple formatargs)
       )
   where
-    stacktrace = prettyStacktrace 0 $ map locStr $ loc : locs
+    stacktrace = T.unpack $ prettyStacktrace 0 $ map locText $ loc : locs
 compileCode (Imp.Call dests fname args) = do
   args' <- mapM compileArg args
   dests' <- tupleOrSingle <$> mapM compileVar dests
   let fname'
-        | isBuiltInFunction fname = futharkFun (pretty fname)
-        | otherwise = "self." ++ futharkFun (pretty fname)
+        | isBuiltInFunction fname = futharkFun (prettyString fname)
+        | otherwise = "self." ++ futharkFun (prettyString fname)
       call' = simpleCall fname' args'
   -- If the function returns nothing (is called only for side
   -- effects), take care not to assign to an empty tuple.
diff --git a/src/Futhark/CodeGen/Backends/GenericPython/AST.hs b/src/Futhark/CodeGen/Backends/GenericPython/AST.hs
--- a/src/Futhark/CodeGen/Backends/GenericPython/AST.hs
+++ b/src/Futhark/CodeGen/Backends/GenericPython/AST.hs
@@ -11,7 +11,7 @@
   )
 where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Util.Pretty
 import Language.Futhark.Core
 
@@ -92,116 +92,116 @@
   deriving (Eq, Show)
 
 instance Pretty PyIdx where
-  ppr (IdxExp e) = ppr e
-  ppr (IdxRange from to) = ppr from <> text ":" <> ppr to
+  pretty (IdxExp e) = pretty e
+  pretty (IdxRange from to) = pretty from <> ":" <> pretty to
 
 instance Pretty PyArg where
-  ppr (ArgKeyword k e) = text k <> equals <> ppr e
-  ppr (Arg e) = ppr e
+  pretty (ArgKeyword k e) = pretty k <> equals <> pretty e
+  pretty (Arg e) = pretty e
 
 instance Pretty PyExp where
-  ppr (Integer x) = ppr x
-  ppr (Bool x) = ppr x
-  ppr (Float x)
-    | isInfinite x = text $ if x > 0 then "float('inf')" else "float('-inf')"
-    | otherwise = ppr x
-  ppr (String x) = text $ show x
-  ppr (RawStringLiteral s) = text "\"\"\"" <> strictText s <> text "\"\"\""
-  ppr (Var n) = text $ map (\x -> if x == '\'' then 'm' else x) n
-  ppr (Field e s) = ppr e <> text "." <> text s
-  ppr (BinOp s e1 e2) = parens (ppr e1 <+> text s <+> ppr e2)
-  ppr (UnOp s e) = text s <> parens (ppr e)
-  ppr (Cond e1 e2 e3) = ppr e2 <+> text "if" <+> ppr e1 <+> text "else" <+> ppr e3
-  ppr (Cast src bt) =
-    text "ct.cast"
+  pretty (Integer x) = pretty x
+  pretty (Bool x) = pretty x
+  pretty (Float x)
+    | isInfinite x = if x > 0 then "float('inf')" else "float('-inf')"
+    | otherwise = pretty x
+  pretty (String x) = pretty $ show x
+  pretty (RawStringLiteral s) = "\"\"\"" <> pretty s <> "\"\"\""
+  pretty (Var n) = pretty $ map (\x -> if x == '\'' then 'm' else x) n
+  pretty (Field e s) = pretty e <> "." <> pretty s
+  pretty (BinOp s e1 e2) = parens (pretty e1 <+> pretty s <+> pretty e2)
+  pretty (UnOp s e) = pretty s <> parens (pretty e)
+  pretty (Cond e1 e2 e3) = pretty e2 <+> "if" <+> pretty e1 <+> "else" <+> pretty e3
+  pretty (Cast src bt) =
+    "ct.cast"
       <> parens
-        ( ppr src <> text ","
-            <+> text "ct.POINTER" <> parens (text bt)
+        ( pretty src <> ","
+            <+> "ct.POINTER" <> parens (pretty bt)
         )
-  ppr (Index src idx) = ppr src <> brackets (ppr idx)
-  ppr (Call fun exps) = ppr fun <> parens (commasep $ map ppr exps)
-  ppr (Tuple [dim]) = parens (ppr dim <> text ",")
-  ppr (Tuple dims) = parens (commasep $ map ppr dims)
-  ppr (List es) = brackets $ commasep $ map ppr es
-  ppr (Dict kvs) = braces $ commasep $ map ppElem kvs
+  pretty (Index src idx) = pretty src <> brackets (pretty idx)
+  pretty (Call fun exps) = pretty fun <> parens (commasep $ map pretty exps)
+  pretty (Tuple [dim]) = parens (pretty dim <> ",")
+  pretty (Tuple dims) = parens (commasep $ map pretty dims)
+  pretty (List es) = brackets $ commasep $ map pretty es
+  pretty (Dict kvs) = braces $ commasep $ map ppElem kvs
     where
-      ppElem (k, v) = ppr k <> colon <+> ppr v
-  ppr (Lambda p e) = text "lambda" <+> text p <> text ":" <+> ppr e
-  ppr None = text "None"
+      ppElem (k, v) = pretty k <> colon <+> pretty v
+  pretty (Lambda p e) = "lambda" <+> pretty p <> ":" <+> pretty e
+  pretty None = "None"
 
 instance Pretty PyStmt where
-  ppr (If cond [] []) =
-    text "if"
-      <+> ppr cond <> text ":"
-      </> indent 2 (text "pass")
-  ppr (If cond [] fbranch) =
-    text "if"
-      <+> ppr cond <> text ":"
-      </> indent 2 (text "pass")
-      </> text "else:"
-      </> indent 2 (stack $ map ppr fbranch)
-  ppr (If cond tbranch []) =
-    text "if"
-      <+> ppr cond <> text ":"
-      </> indent 2 (stack $ map ppr tbranch)
-  ppr (If cond tbranch fbranch) =
-    text "if"
-      <+> ppr cond <> text ":"
-      </> indent 2 (stack $ map ppr tbranch)
-      </> text "else:"
-      </> indent 2 (stack $ map ppr fbranch)
-  ppr (Try pystms pyexcepts) =
-    text "try:"
-      </> indent 2 (stack $ map ppr pystms)
-      </> stack (map ppr pyexcepts)
-  ppr (While cond body) =
-    text "while"
-      <+> ppr cond <> text ":"
-      </> indent 2 (stack $ map ppr body)
-  ppr (For i what body) =
-    text "for"
-      <+> ppr i
-      <+> text "in"
-      <+> ppr what <> text ":"
-      </> indent 2 (stack $ map ppr body)
-  ppr (With what body) =
-    text "with"
-      <+> ppr what <> text ":"
-      </> indent 2 (stack $ map ppr body)
-  ppr (Assign e1 e2) = ppr e1 <+> text "=" <+> ppr e2
-  ppr (AssignOp op e1 e2) = ppr e1 <+> text (op ++ "=") <+> ppr e2
-  ppr (Comment s body) = text "#" <> text s </> stack (map ppr body)
-  ppr (Assert e1 e2) = text "assert" <+> ppr e1 <> text "," <+> ppr e2
-  ppr (Raise e) = text "raise" <+> ppr e
-  ppr (Exp c) = ppr c
-  ppr (Return e) = text "return" <+> ppr e
-  ppr Pass = text "pass"
-  ppr (Import from (Just as)) =
-    text "import" <+> text from <+> text "as" <+> text as
-  ppr (Import from Nothing) =
-    text "import" <+> text from
-  ppr (FunDef d) = ppr d
-  ppr (ClassDef d) = ppr d
-  ppr (Escape s) = stack $ map strictText $ T.lines s
+  pretty (If cond [] []) =
+    "if"
+      <+> pretty cond <> ":"
+      </> indent 2 "pass"
+  pretty (If cond [] fbranch) =
+    "if"
+      <+> pretty cond <> ":"
+      </> indent 2 "pass"
+      </> "else:"
+      </> indent 2 (stack $ map pretty fbranch)
+  pretty (If cond tbranch []) =
+    "if"
+      <+> pretty cond <> ":"
+      </> indent 2 (stack $ map pretty tbranch)
+  pretty (If cond tbranch fbranch) =
+    "if"
+      <+> pretty cond <> ":"
+      </> indent 2 (stack $ map pretty tbranch)
+      </> "else:"
+      </> indent 2 (stack $ map pretty fbranch)
+  pretty (Try pystms pyexcepts) =
+    "try:"
+      </> indent 2 (stack $ map pretty pystms)
+      </> stack (map pretty pyexcepts)
+  pretty (While cond body) =
+    "while"
+      <+> pretty cond <> ":"
+      </> indent 2 (stack $ map pretty body)
+  pretty (For i what body) =
+    "for"
+      <+> pretty i
+      <+> "in"
+      <+> pretty what <> ":"
+      </> indent 2 (stack $ map pretty body)
+  pretty (With what body) =
+    "with"
+      <+> pretty what <> ":"
+      </> indent 2 (stack $ map pretty body)
+  pretty (Assign e1 e2) = pretty e1 <+> "=" <+> pretty e2
+  pretty (AssignOp op e1 e2) = pretty e1 <+> pretty (op ++ "=") <+> pretty e2
+  pretty (Comment s body) = "#" <> pretty s </> stack (map pretty body)
+  pretty (Assert e1 e2) = "assert" <+> pretty e1 <> "," <+> pretty e2
+  pretty (Raise e) = "raise" <+> pretty e
+  pretty (Exp c) = pretty c
+  pretty (Return e) = "return" <+> pretty e
+  pretty Pass = "pass"
+  pretty (Import from (Just as)) =
+    "import" <+> pretty from <+> "as" <+> pretty as
+  pretty (Import from Nothing) =
+    "import" <+> pretty from
+  pretty (FunDef d) = pretty d
+  pretty (ClassDef d) = pretty d
+  pretty (Escape s) = stack $ map pretty $ T.lines s
 
 instance Pretty PyFunDef where
-  ppr (Def fname params body) =
-    text "def"
-      <+> text fname <> parens (commasep $ map ppr params) <> text ":"
-      </> indent 2 (stack (map ppr body))
+  pretty (Def fname params body) =
+    "def"
+      <+> pretty fname <> parens (commasep $ map pretty params) <> ":"
+      </> indent 2 (stack (map pretty body))
 
 instance Pretty PyClassDef where
-  ppr (Class cname body) =
-    text "class"
-      <+> text cname <> text ":"
-      </> indent 2 (stack (map ppr body))
+  pretty (Class cname body) =
+    "class"
+      <+> pretty cname <> ":"
+      </> indent 2 (stack (map pretty body))
 
 instance Pretty PyExcept where
-  ppr (Catch pyexp stms) =
-    text "except"
-      <+> ppr pyexp
-      <+> text "as e:"
-      </> indent 2 (stack $ map ppr stms)
+  pretty (Catch pyexp stms) =
+    "except"
+      <+> pretty pyexp
+      <+> "as e:"
+      </> indent 2 (vsep $ map pretty stms)
 
 instance Pretty PyProg where
-  ppr (PyProg stms) = stack (map ppr stms)
+  pretty (PyProg stms) = vsep (map pretty stms)
diff --git a/src/Futhark/CodeGen/Backends/GenericWASM.hs b/src/Futhark/CodeGen/Backends/GenericWASM.hs
--- a/src/Futhark/CodeGen/Backends/GenericWASM.hs
+++ b/src/Futhark/CodeGen/Backends/GenericWASM.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 module Futhark.CodeGen.Backends.GenericWASM
@@ -17,10 +16,10 @@
 where
 
 import Data.List (intercalate, nub)
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.SimpleRep (opaqueName)
-import qualified Futhark.CodeGen.ImpCode.Sequential as Imp
+import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
 import Futhark.CodeGen.RTS.JavaScript
 import Language.Futhark.Primitive
 import NeatInterpolation (text)
diff --git a/src/Futhark/CodeGen/Backends/MulticoreC.hs b/src/Futhark/CodeGen/Backends/MulticoreC.hs
--- a/src/Futhark/CodeGen/Backends/MulticoreC.hs
+++ b/src/Futhark/CodeGen/Backends/MulticoreC.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 -- | C code generator.  This module can convert a correct ImpCode
@@ -31,19 +30,19 @@
 
 import Control.Monad
 import Data.Loc
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericC.Options
 import Futhark.CodeGen.Backends.SimpleRep
 import Futhark.CodeGen.ImpCode.Multicore hiding (ValueType)
-import qualified Futhark.CodeGen.ImpGen.Multicore as ImpGen
+import Futhark.CodeGen.ImpGen.Multicore qualified as ImpGen
 import Futhark.CodeGen.RTS.C (schedulerH)
 import Futhark.IR.MCMem (MCMem, Prog)
 import Futhark.MonadFreshNames
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 
 -- | Compile the program to ImpCode with multicore operations.
 compileProg ::
@@ -272,11 +271,11 @@
 
 closureFreeStructField :: VName -> Name
 closureFreeStructField v =
-  nameFromString "free_" <> nameFromString (pretty v)
+  nameFromString "free_" <> nameFromString (prettyString v)
 
 closureRetvalStructField :: VName -> Name
 closureRetvalStructField v =
-  nameFromString "retval_" <> nameFromString (pretty v)
+  nameFromString "retval_" <> nameFromString (prettyString v)
 
 data ValueType = Prim PrimType | MemBlock | RawMem
 
@@ -337,7 +336,7 @@
        in [C.cdecl|$ty:ty $id:name = $exp:(fromStorage pt inner);|]
     field name (ty, _) =
       [C.cdecl|$ty:ty $id:name =
-                 {.desc = $string:(pretty name),
+                 {.desc = $string:(prettyString name),
                  .mem = $id:struct->$id:(closureRetvalStructField name),
                  .size = 0, .references = NULL};|]
 
@@ -354,7 +353,7 @@
        in [C.cdecl|$ty:ty $id:name = $exp:(fromStorage pt inner);|]
     field name (ty, _) =
       [C.cdecl|$ty:ty $id:name =
-                 {.desc = $string:(pretty name),
+                 {.desc = $string:(prettyString name),
                   .mem = $id:struct->$id:(closureFreeStructField name),
                   .size = 0, .references = NULL};|]
 
diff --git a/src/Futhark/CodeGen/Backends/MulticoreISPC.hs b/src/Futhark/CodeGen/Backends/MulticoreISPC.hs
--- a/src/Futhark/CodeGen/Backends/MulticoreISPC.hs
+++ b/src/Futhark/CodeGen/Backends/MulticoreISPC.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 -- | C code generator.  This module can convert a correct ImpCode
@@ -20,24 +18,24 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.Bifunctor
-import qualified Data.DList as DL
+import Data.DList qualified as DL
 import Data.List (unzip4)
 import Data.Loc (noLoc)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
-import qualified Futhark.CodeGen.Backends.MulticoreC as MC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
+import Futhark.CodeGen.Backends.GenericC.Pretty
+import Futhark.CodeGen.Backends.MulticoreC qualified as MC
 import Futhark.CodeGen.Backends.SimpleRep
 import Futhark.CodeGen.ImpCode.Multicore
-import qualified Futhark.CodeGen.ImpGen.Multicore as ImpGen
+import Futhark.CodeGen.ImpGen.Multicore qualified as ImpGen
 import Futhark.CodeGen.RTS.C (errorsH, ispcUtilH, uniformH)
 import Futhark.IR.MCMem (MCMem, Prog)
 import Futhark.IR.Prop (isBuiltInFunction)
 import Futhark.MonadFreshNames
-import Futhark.Util.Pretty (prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import NeatInterpolation (untrimming)
 
 type ISPCCompilerM a = GC.CompilerM Multicore ISPCState a
@@ -85,7 +83,7 @@
       )
       (ws, defs)
 
-  let ispc_decls = T.unlines $ map prettyText $ DL.toList $ sDefs $ GC.compUserState endstate
+  let ispc_decls = definitionsText $ DL.toList $ sDefs $ GC.compUserState endstate
 
   -- The bool #define is a workaround around an ISPC bug, stdbool doesn't get included.
   let ispcdefs =
@@ -180,7 +178,7 @@
 -- | Set memory in ISPC
 setMem :: (C.ToExp a, C.ToExp b) => a -> b -> Space -> ISPCCompilerM ()
 setMem dest src space = do
-  let src_s = pretty $ C.toExp src noLoc
+  let src_s = T.unpack $ expText $ C.toExp src noLoc
   strlit <- makeStringLiteral src_s
   GC.stm
     [C.cstm|if ($id:(GC.fatMemSet space)(ctx, &$exp:dest, &$exp:src,
@@ -192,7 +190,7 @@
 unRefMem :: C.ToExp a => a -> Space -> ISPCCompilerM ()
 unRefMem mem space = do
   cached <- isJust <$> GC.cacheMem mem
-  let mem_s = pretty $ C.toExp mem noLoc
+  let mem_s = T.unpack $ expText $ C.toExp mem noLoc
   strlit <- makeStringLiteral mem_s
   unless cached $
     GC.stm
@@ -209,13 +207,12 @@
   C.Stm ->
   ISPCCompilerM ()
 allocMem mem size space on_failure = do
-  let mem_s = pretty $ C.toExp mem noLoc
+  let mem_s = T.unpack $ expText $ C.toExp mem noLoc
   strlit <- makeStringLiteral mem_s
   GC.stm
-    [C.cstm|if ($id:(GC.fatMemAlloc space)(ctx, &$exp:mem, $exp:size,
-                                              $id:strlit())) {
+    [C.cstm|if ($id:(GC.fatMemAlloc space)(ctx, &$exp:mem, $exp:size, $id:strlit())) {
                     $stm:on_failure
-                  }|]
+            }|]
 
 -- | Free memory in ISPC
 freeAllocatedMem :: ISPCCompilerM [C.BlockItem]
@@ -388,7 +385,7 @@
   let args' = map (\x -> [C.cexp|extract($exp:x, $id:uni)|]) args
   GC.items
     [C.citems|
-      $escstm:("foreach_active(" <> pretty uni <> ")")
+      $escstm:("foreach_active(" <> prettyString uni <> ")")
       {
         $id:shim(ctx, $args:args');
         err = FUTHARK_PROGRAM_ERROR;
@@ -422,11 +419,11 @@
 compileExp e@(ValueExp (FloatValue (Float64Value v))) =
   if isInfinite v || isNaN v
     then GC.compileExp e
-    else pure [C.cexp|$esc:(pretty v <> "d")|]
+    else pure [C.cexp|$esc:(prettyString v <> "d")|]
 compileExp e@(ValueExp (FloatValue (Float16Value v))) =
   if isInfinite v || isNaN v
     then GC.compileExp e
-    else pure [C.cexp|$esc:(pretty v <> "f16")|]
+    else pure [C.cexp|$esc:(prettyString v <> "f16")|]
 compileExp (ValueExp val) =
   pure $ C.toExp val mempty
 compileExp (LeafExp v _) =
@@ -451,7 +448,7 @@
   pure [C.cexp|($exp:x' > 0 ? 1 : 0) - ($exp:x' < 0 ? 1 : 0) != 0|]
 compileExp (UnOpExp op x) = do
   x' <- compileExp x
-  pure [C.cexp|$id:(pretty op)($exp:x')|]
+  pure [C.cexp|$id:(prettyString op)($exp:x')|]
 compileExp (CmpOpExp cmp x y) = do
   x' <- compileExp x
   y' <- compileExp y
@@ -461,10 +458,10 @@
     FCmpLe {} -> [C.cexp|$exp:x' <= $exp:y'|]
     CmpLlt {} -> [C.cexp|$exp:x' < $exp:y'|]
     CmpLle {} -> [C.cexp|$exp:x' <= $exp:y'|]
-    _ -> [C.cexp|$id:(pretty cmp)($exp:x', $exp:y')|]
+    _ -> [C.cexp|$id:(prettyString cmp)($exp:x', $exp:y')|]
 compileExp (ConvOpExp conv x) = do
   x' <- compileExp x
-  pure [C.cexp|$id:(pretty conv)($exp:x')|]
+  pure [C.cexp|$id:(prettyString conv)($exp:x')|]
 compileExp (BinOpExp bop x y) = do
   x' <- compileExp x
   y' <- compileExp y
@@ -481,7 +478,7 @@
     Or {} -> [C.cexp|$exp:x' | $exp:y'|]
     LogAnd {} -> [C.cexp|$exp:x' && $exp:y'|]
     LogOr {} -> [C.cexp|$exp:x' || $exp:y'|]
-    _ -> [C.cexp|$id:(pretty bop)($exp:x', $exp:y')|]
+    _ -> [C.cexp|$id:(prettyString bop)($exp:x', $exp:y')|]
 compileExp (FunExp h args _) = do
   args' <- mapM compileExp args
   pure [C.cexp|$id:(funName (nameFromString h))($args:args')|]
@@ -493,7 +490,7 @@
 compileCode :: MCCode -> ISPCCompilerM ()
 compileCode (Comment s code) = do
   xs <- GC.collect $ compileCode code
-  let comment = "// " ++ s
+  let comment = "// " ++ T.unpack s
   GC.stm
     [C.cstm|$comment:comment
               { $items:xs }
@@ -654,7 +651,7 @@
   err <- GC.collect $ handleError msg stacktrace
   GC.stm [C.cstm|if (!$exp:e') { $items:err }|]
   where
-    stacktrace = prettyStacktrace 0 $ map locStr $ loc : locs
+    stacktrace = T.unpack $ prettyStacktrace 0 $ map locText $ loc : locs
 compileCode code =
   GC.compileCode code
 
@@ -724,7 +721,7 @@
       let inner = [C.cexp|$id:struct'->$id:(MC.closureFreeStructField name)|]
       pure [C.citems|$tyqual:uniform $ty:ty $id:name = $exp:(fromStorage pt inner);|]
     field name (_, _) = do
-      strlit <- makeStringLiteral $ pretty name
+      strlit <- makeStringLiteral $ prettyString name
       pure
         [C.citems|$tyqual:uniform struct memblock $id:name;
                      $id:name.desc = $id:strlit();
@@ -928,7 +925,7 @@
     else
       GC.stms
         [C.cstms|
-      $escstm:("foreach (" <> pretty i <> " = " <> pretty from' <> " ... " <> pretty bound' <> ")") {
+      $escstm:(T.unpack ("foreach (" <> prettyText i <> " = " <> expText from' <> " ... " <> expText bound' <> ")")) {
         $items:body'
       }|]
 compileOp (ForEachActive name body) = do
@@ -961,7 +958,7 @@
   let cached = M.keys $ M.filter (== DefaultSpace) lexical
 
   cached' <- forM cached $ \mem -> do
-    size <- newVName $ pretty mem <> "_cached_size"
+    size <- newVName $ prettyString mem <> "_cached_size"
     pure (mem, size)
 
   let lexMem env =
diff --git a/src/Futhark/CodeGen/Backends/MulticoreWASM.hs b/src/Futhark/CodeGen/Backends/MulticoreWASM.hs
--- a/src/Futhark/CodeGen/Backends/MulticoreWASM.hs
+++ b/src/Futhark/CodeGen/Backends/MulticoreWASM.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | C code generator.  This module can convert a correct ImpCode
 -- program to an equivalent C program.  This C program is expected to
 -- be converted to WebAssembly, so we also produce the intended
@@ -16,12 +14,12 @@
 where
 
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericWASM
-import qualified Futhark.CodeGen.Backends.MulticoreC as MC
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
-import qualified Futhark.CodeGen.ImpGen.Multicore as ImpGen
+import Futhark.CodeGen.Backends.MulticoreC qualified as MC
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
+import Futhark.CodeGen.ImpGen.Multicore qualified as ImpGen
 import Futhark.IR.MCMem
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CodeGen/Backends/PyOpenCL.hs b/src/Futhark/CodeGen/Backends/PyOpenCL.hs
--- a/src/Futhark/CodeGen/Backends/PyOpenCL.hs
+++ b/src/Futhark/CodeGen/Backends/PyOpenCL.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Code generation for Python with OpenCL.
 module Futhark.CodeGen.Backends.PyOpenCL
   ( compileProg,
@@ -8,19 +5,19 @@
 where
 
 import Control.Monad
-import qualified Data.Map as M
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericPython as Py
+import Data.Map qualified as M
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericPython qualified as Py
 import Futhark.CodeGen.Backends.GenericPython.AST
 import Futhark.CodeGen.Backends.GenericPython.Options
 import Futhark.CodeGen.Backends.PyOpenCL.Boilerplate
-import qualified Futhark.CodeGen.ImpCode.OpenCL as Imp
-import qualified Futhark.CodeGen.ImpGen.OpenCL as ImpGen
+import Futhark.CodeGen.ImpCode.OpenCL qualified as Imp
+import Futhark.CodeGen.ImpGen.OpenCL qualified as ImpGen
 import Futhark.CodeGen.RTS.Python (openclPy)
 import Futhark.IR.GPUMem (GPUMem, Prog)
 import Futhark.MonadFreshNames
 import Futhark.Util (zEncodeString)
-import Futhark.Util.Pretty (pretty)
+import Futhark.Util.Pretty (prettyString)
 
 -- | Compile the program to Python with calls to OpenCL.
 compileProg ::
@@ -46,7 +43,7 @@
         unlines
           $ map
             ( \x ->
-                pretty $
+                prettyString $
                   Assign
                     (Var ("self." ++ zEncodeString (nameToString x) ++ "_var"))
                     (Var $ "program." ++ zEncodeString (nameToString x))
@@ -56,6 +53,7 @@
   let defines =
         [ Assign (Var "synchronous") $ Bool False,
           Assign (Var "preferred_platform") None,
+          Assign (Var "build_options") $ List [],
           Assign (Var "preferred_device") None,
           Assign (Var "default_threshold") None,
           Assign (Var "default_group_size") None,
@@ -77,6 +75,7 @@
   let constructor =
         Py.Constructor
           [ "self",
+            "build_options=build_options",
             "command_queue=None",
             "interactive=False",
             "platform_pref=preferred_platform",
@@ -105,6 +104,16 @@
                 [Assign (Var "preferred_device") $ Var "optarg"]
             },
           Option
+            { optionLongName = "build-option",
+              optionShortName = Nothing,
+              optionArgument = RequiredArgument "str",
+              optionAction =
+                [ Assign (Var "build_options") $
+                    BinOp "+" (Var "build_options") $
+                      List [Var "optarg"]
+                ]
+            },
+          Option
             { optionLongName = "default-threshold",
               optionShortName = Nothing,
               optionArgument = RequiredArgument "int",
@@ -195,19 +204,19 @@
   v' <- Py.compileVar v
   Py.stm $
     Assign v' $
-      Index (Var "self.sizes") (IdxExp $ String $ pretty key)
+      Index (Var "self.sizes") (IdxExp $ String $ prettyString key)
 callKernel (Imp.CmpSizeLe v key x) = do
   v' <- Py.compileVar v
   x' <- Py.compileExp x
   Py.stm $
     Assign v' $
-      BinOp "<=" (Index (Var "self.sizes") (IdxExp $ String $ pretty key)) x'
+      BinOp "<=" (Index (Var "self.sizes") (IdxExp $ String $ prettyString key)) x'
 callKernel (Imp.GetSizeMax v size_class) = do
   v' <- Py.compileVar v
   Py.stm $
     Assign v' $
       Var $
-        "self.max_" ++ pretty size_class
+        "self.max_" ++ prettyString size_class
 callKernel (Imp.LaunchKernel safety name args num_workgroups workgroup_size) = do
   num_workgroups' <- mapM (fmap asLong . Py.compileExp) num_workgroups
   workgroup_size' <- mapM (fmap asLong . Py.compileExp) workgroup_size
@@ -285,7 +294,7 @@
 readOpenCLScalar :: Py.ReadScalar Imp.OpenCL ()
 readOpenCLScalar mem i bt "device" = do
   val <- newVName "read_res"
-  let val' = Var $ pretty val
+  let val' = Var $ prettyString val
   let nparr =
         Call
           (Var "np.empty")
@@ -312,7 +321,7 @@
 allocateOpenCLBuffer mem size "device" =
   Py.stm $
     Assign mem $
-      Py.simpleCall "opencl_alloc" [Var "self", size, String $ pretty mem]
+      Py.simpleCall "opencl_alloc" [Var "self", size, String $ prettyString mem]
 allocateOpenCLBuffer _ _ space =
   error $ "Cannot allocate in '" ++ space ++ "' space"
 
diff --git a/src/Futhark/CodeGen/Backends/PyOpenCL/Boilerplate.hs b/src/Futhark/CodeGen/Backends/PyOpenCL/Boilerplate.hs
--- a/src/Futhark/CodeGen/Backends/PyOpenCL/Boilerplate.hs
+++ b/src/Futhark/CodeGen/Backends/PyOpenCL/Boilerplate.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 
 -- | Various boilerplate definitions for the PyOpenCL backend.
@@ -8,9 +7,9 @@
 where
 
 import Control.Monad.Identity
-import qualified Data.Map as M
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericPython as Py
+import Data.Map qualified as M
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericPython qualified as Py
 import Futhark.CodeGen.Backends.GenericPython.AST
 import Futhark.CodeGen.ImpCode.OpenCL
   ( ErrorMsg (..),
@@ -23,7 +22,7 @@
     untyped,
   )
 import Futhark.CodeGen.OpenCL.Heuristics
-import Futhark.Util.Pretty (pretty, prettyText)
+import Futhark.Util.Pretty (prettyString, prettyText)
 import NeatInterpolation (text)
 
 errorMsgNumArgs :: ErrorMsg a -> Int
@@ -40,6 +39,7 @@
 self.failure_msgs=$failure_msgs
 program = initialise_opencl_object(self,
                                    program_src=fut_opencl_src,
+                                   build_options=build_options,
                                    command_queue=command_queue,
                                    interactive=interactive,
                                    platform_pref=platform_pref,
@@ -58,7 +58,7 @@
   where
     assign' = T.pack assign
     size_heuristics = prettyText $ sizeHeuristicsToPython sizeHeuristicsTable
-    types' = prettyText $ map (show . pretty) types -- Looks enough like Python.
+    types' = prettyText $ map (show . prettyString) types -- Looks enough like Python.
     sizes' = prettyText $ sizeClassesToPython sizes
     max_num_args = prettyText $ foldl max 0 $ map (errorMsgNumArgs . failureError) failures
     failure_msgs = prettyText $ List $ map formatFailure failures
@@ -73,16 +73,16 @@
           escapeChar c = [c]
        in concatMap escapeChar
 
-    onPart (ErrorString s) = formatEscape s
+    onPart (ErrorString s) = formatEscape $ T.unpack s
     onPart ErrorVal {} = "{}"
 
 sizeClassesToPython :: M.Map Name SizeClass -> PyExp
 sizeClassesToPython = Dict . map f . M.toList
   where
     f (size_name, size_class) =
-      ( String $ pretty size_name,
+      ( String $ prettyString size_name,
         Dict
-          [ (String "class", String $ pretty size_class),
+          [ (String "class", String $ prettyString size_class),
             ( String "value",
               maybe None (Integer . fromIntegral) $
                 sizeDefault size_class
diff --git a/src/Futhark/CodeGen/Backends/SequentialC.hs b/src/Futhark/CodeGen/Backends/SequentialC.hs
--- a/src/Futhark/CodeGen/Backends/SequentialC.hs
+++ b/src/Futhark/CodeGen/Backends/SequentialC.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | C code generator.  This module can convert a correct ImpCode
 -- program to an equivalent C program. The C code is strictly
 -- sequential, but can handle the full Futhark language.
@@ -13,11 +11,11 @@
 where
 
 import Control.Monad
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.SequentialC.Boilerplate
-import qualified Futhark.CodeGen.ImpCode.Sequential as Imp
-import qualified Futhark.CodeGen.ImpGen.Sequential as ImpGen
+import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
+import Futhark.CodeGen.ImpGen.Sequential qualified as ImpGen
 import Futhark.IR.SeqMem
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CodeGen/Backends/SequentialC/Boilerplate.hs b/src/Futhark/CodeGen/Backends/SequentialC/Boilerplate.hs
--- a/src/Futhark/CodeGen/Backends/SequentialC/Boilerplate.hs
+++ b/src/Futhark/CodeGen/Backends/SequentialC/Boilerplate.hs
@@ -3,8 +3,8 @@
 -- | Boilerplate for sequential C code.
 module Futhark.CodeGen.Backends.SequentialC.Boilerplate (generateBoilerplate) where
 
-import qualified Futhark.CodeGen.Backends.GenericC as GC
-import qualified Language.C.Quote.OpenCL as C
+import Futhark.CodeGen.Backends.GenericC qualified as GC
+import Language.C.Quote.OpenCL qualified as C
 
 -- | Generate the necessary boilerplate.
 generateBoilerplate :: GC.CompilerM op s ()
diff --git a/src/Futhark/CodeGen/Backends/SequentialPython.hs b/src/Futhark/CodeGen/Backends/SequentialPython.hs
--- a/src/Futhark/CodeGen/Backends/SequentialPython.hs
+++ b/src/Futhark/CodeGen/Backends/SequentialPython.hs
@@ -5,11 +5,11 @@
 where
 
 import Control.Monad
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericPython as GenericPython
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericPython qualified as GenericPython
 import Futhark.CodeGen.Backends.GenericPython.AST
-import qualified Futhark.CodeGen.ImpCode.Sequential as Imp
-import qualified Futhark.CodeGen.ImpGen.Sequential as ImpGen
+import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
+import Futhark.CodeGen.ImpGen.Sequential qualified as ImpGen
 import Futhark.IR.SeqMem
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CodeGen/Backends/SequentialWASM.hs b/src/Futhark/CodeGen/Backends/SequentialWASM.hs
--- a/src/Futhark/CodeGen/Backends/SequentialWASM.hs
+++ b/src/Futhark/CodeGen/Backends/SequentialWASM.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | C code generator.  This module can convert a correct ImpCode
 -- program to an equivalent C program.  This C program is expected to
 -- be converted to WebAssembly, so we also produce the intended
@@ -16,12 +14,12 @@
 where
 
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC qualified as GC
 import Futhark.CodeGen.Backends.GenericWASM
 import Futhark.CodeGen.Backends.SequentialC.Boilerplate
-import qualified Futhark.CodeGen.ImpCode.Sequential as Imp
-import qualified Futhark.CodeGen.ImpGen.Sequential as ImpGen
+import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
+import Futhark.CodeGen.ImpGen.Sequential qualified as ImpGen
 import Futhark.IR.SeqMem
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CodeGen/Backends/SimpleRep.hs b/src/Futhark/CodeGen/Backends/SimpleRep.hs
--- a/src/Futhark/CodeGen/Backends/SimpleRep.hs
+++ b/src/Futhark/CodeGen/Backends/SimpleRep.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE Trustworthy #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 -- | Simple C runtime representation.
@@ -38,12 +36,12 @@
 
 import Data.Bits (shiftR, xor)
 import Data.Char (isAlphaNum, isDigit, ord)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.CodeGen.ImpCode
 import Futhark.CodeGen.RTS.C (scalarF16H, scalarH)
 import Futhark.Util (zEncodeString)
-import qualified Language.C.Quote.C as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.C qualified as C
+import Language.C.Syntax qualified as C
 import Text.Printf
 
 -- | The C type corresponding to a signed integer type.
@@ -178,7 +176,7 @@
   toIdent = C.toIdent . T.unpack
 
 instance C.ToIdent VName where
-  toIdent = C.toIdent . zEncodeString . pretty
+  toIdent = C.toIdent . zEncodeString . prettyString
 
 instance C.ToExp VName where
   toExp v _ = [C.cexp|$id:v|]
diff --git a/src/Futhark/CodeGen/ImpCode.hs b/src/Futhark/CodeGen/ImpCode.hs
--- a/src/Futhark/CodeGen/ImpCode.hs
+++ b/src/Futhark/CodeGen/ImpCode.hs
@@ -1,7 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | ImpCode is an imperative intermediate language used as a stepping
 -- stone in code generation.  The functional core IR
@@ -86,7 +83,8 @@
     withElemType,
 
     -- * Re-exports from other modules.
-    pretty,
+    prettyText,
+    prettyString,
     module Futhark.IR.Syntax.Core,
     module Language.Futhark.Core,
     module Language.Futhark.Primitive,
@@ -98,8 +96,9 @@
 where
 
 import Data.List (intersperse)
-import qualified Data.Map as M
-import qualified Data.Set as S
+import Data.Map qualified as M
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Data.Traversable
 import Futhark.Analysis.PrimExp
 import Futhark.Analysis.PrimExp.Convert
@@ -307,7 +306,7 @@
   | -- | Has the same semantics as the contained code, but
     -- the comment should show up in generated code for ease
     -- of inspection.
-    Comment String (Code a)
+    Comment T.Text (Code a)
   | -- | Print the given value to the screen, somehow
     -- annotated with the given string as a description.  If
     -- no type/value pair, just print the string.  This has
@@ -425,174 +424,174 @@
 -- Prettyprinting definitions.
 
 instance Pretty op => Pretty (Definitions op) where
-  ppr (Definitions types consts funs) =
-    ppr types </> ppr consts </> ppr funs
+  pretty (Definitions types consts funs) =
+    pretty types </> pretty consts </> pretty funs
 
 instance Pretty op => Pretty (Functions op) where
-  ppr (Functions funs) = stack $ intersperse mempty $ map ppFun funs
+  pretty (Functions funs) = stack $ intersperse mempty $ map ppFun funs
     where
       ppFun (name, fun) =
-        "Function " <> ppr name <> colon </> indent 2 (ppr fun)
+        "Function " <> pretty name <> colon </> indent 2 (pretty fun)
 
 instance Pretty op => Pretty (Constants op) where
-  ppr (Constants decls code) =
+  pretty (Constants decls code) =
     "Constants:"
-      </> indent 2 (stack $ map ppr decls)
+      </> indent 2 (stack $ map pretty decls)
       </> mempty
       </> "Initialisation:"
-      </> indent 2 (ppr code)
+      </> indent 2 (pretty code)
 
 instance Pretty EntryPoint where
-  ppr (EntryPoint name results args) =
+  pretty (EntryPoint name results args) =
     "Name:"
-      </> indent 2 (pquote (ppr name))
+      </> indent 2 (dquotes (pretty name))
       </> "Arguments:"
       </> indent 2 (stack $ map ppArg args)
       </> "Results:"
       </> indent 2 (stack $ map ppRes results)
     where
-      ppArg ((p, u), t) = ppr p <+> ":" <+> ppRes (u, t)
-      ppRes (u, t) = ppr u <> ppr t
+      ppArg ((p, u), t) = pretty p <+> ":" <+> ppRes (u, t)
+      ppRes (u, t) = pretty u <> pretty t
 
 instance Pretty op => Pretty (FunctionT op) where
-  ppr (Function entry outs ins body) =
+  pretty (Function entry outs ins body) =
     "Inputs:"
-      </> indent 2 (stack $ map ppr ins)
+      </> indent 2 (stack $ map pretty ins)
       </> "Outputs:"
-      </> indent 2 (stack $ map ppr outs)
+      </> indent 2 (stack $ map pretty outs)
       </> "Entry:"
-      </> indent 2 (ppr entry)
+      </> indent 2 (pretty entry)
       </> "Body:"
-      </> indent 2 (ppr body)
+      </> indent 2 (pretty body)
 
 instance Pretty Param where
-  ppr (ScalarParam name ptype) = ppr ptype <+> ppr name
-  ppr (MemParam name space) = "mem" <> ppr space <> " " <> ppr name
+  pretty (ScalarParam name ptype) = pretty ptype <+> pretty name
+  pretty (MemParam name space) = "mem" <> pretty space <> " " <> pretty name
 
 instance Pretty ValueDesc where
-  ppr (ScalarValue t ept name) =
-    ppr t <+> ppr name <> ept'
+  pretty (ScalarValue t ept name) =
+    pretty t <+> pretty name <> ept'
     where
       ept' = case ept of
         Unsigned -> " (unsigned)"
         Signed -> mempty
-  ppr (ArrayValue mem space et ept shape) =
-    foldr f (ppr et) shape <+> "at" <+> ppr mem <> ppr space <+> ept'
+  pretty (ArrayValue mem space et ept shape) =
+    foldr f (pretty et) shape <+> "at" <+> pretty mem <> pretty space <+> ept'
     where
-      f e s = brackets $ s <> comma <> ppr e
+      f e s = brackets $ s <> comma <> pretty e
       ept' = case ept of
         Unsigned -> " (unsigned)"
         Signed -> mempty
 
 instance Pretty ExternalValue where
-  ppr (TransparentValue v) = ppr v
-  ppr (OpaqueValue desc vs) =
+  pretty (TransparentValue v) = pretty v
+  pretty (OpaqueValue desc vs) =
     "opaque"
-      <+> pquote (ppr desc)
-      <+> nestedBlock "{" "}" (stack $ map ppr vs)
+      <+> dquotes (pretty desc)
+      <+> nestedBlock "{" "}" (stack $ map pretty vs)
 
 instance Pretty ArrayContents where
-  ppr (ArrayValues vs) = braces (commasep $ map ppr vs)
-  ppr (ArrayZeros n) = braces "0" <+> "*" <+> ppr n
+  pretty (ArrayValues vs) = braces (commasep $ map pretty vs)
+  pretty (ArrayZeros n) = braces "0" <+> "*" <+> pretty n
 
 instance Pretty op => Pretty (Code op) where
-  ppr (Op op) = ppr op
-  ppr Skip = "skip"
-  ppr (c1 :>>: c2) = ppr c1 </> ppr c2
-  ppr (For i limit body) =
+  pretty (Op op) = pretty op
+  pretty Skip = "skip"
+  pretty (c1 :>>: c2) = pretty c1 </> pretty c2
+  pretty (For i limit body) =
     "for"
-      <+> ppr i
+      <+> pretty i
       <+> langle
-      <+> ppr limit
+      <+> pretty limit
       <+> "{"
-      </> indent 2 (ppr body)
+      </> indent 2 (pretty body)
       </> "}"
-  ppr (While cond body) =
+  pretty (While cond body) =
     "while"
-      <+> ppr cond
+      <+> pretty cond
       <+> "{"
-      </> indent 2 (ppr body)
+      </> indent 2 (pretty body)
       </> "}"
-  ppr (DeclareMem name space) =
-    "var" <+> ppr name <> ": mem" <> ppr space
-  ppr (DeclareScalar name vol t) =
-    "var" <+> ppr name <> ":" <+> vol' <> ppr t
+  pretty (DeclareMem name space) =
+    "var" <+> pretty name <> ": mem" <> pretty space
+  pretty (DeclareScalar name vol t) =
+    "var" <+> pretty name <> ":" <+> vol' <> pretty t
     where
       vol' = case vol of
         Volatile -> "volatile "
         Nonvolatile -> mempty
-  ppr (DeclareArray name space t vs) =
+  pretty (DeclareArray name space t vs) =
     "array"
-      <+> ppr name <> "@" <> ppr space
+      <+> pretty name <> "@" <> pretty space
       <+> ":"
-      <+> ppr t
+      <+> pretty t
       <+> equals
-      <+> ppr vs
-  ppr (Allocate name e space) =
-    ppr name <+> "<-" <+> "malloc" <> parens (ppr e) <> ppr space
-  ppr (Free name space) =
-    "free" <> parens (ppr name) <> ppr space
-  ppr (Write name i bt space vol val) =
-    ppr name <> langle <> vol' <> ppr bt <> ppr space <> rangle <> brackets (ppr i)
+      <+> pretty vs
+  pretty (Allocate name e space) =
+    pretty name <+> "<-" <+> "malloc" <> parens (pretty e) <> pretty space
+  pretty (Free name space) =
+    "free" <> parens (pretty name) <> pretty space
+  pretty (Write name i bt space vol val) =
+    pretty name <> langle <> vol' <> pretty bt <> pretty space <> rangle <> brackets (pretty i)
       <+> "<-"
-      <+> ppr val
+      <+> pretty val
     where
       vol' = case vol of
         Volatile -> "volatile "
         Nonvolatile -> mempty
-  ppr (Read name v is bt space vol) =
-    ppr name
+  pretty (Read name v is bt space vol) =
+    pretty name
       <+> "<-"
-      <+> ppr v <> langle <> vol' <> ppr bt <> ppr space <> rangle <> brackets (ppr is)
+      <+> pretty v <> langle <> vol' <> pretty bt <> pretty space <> rangle <> brackets (pretty is)
     where
       vol' = case vol of
         Volatile -> "volatile "
         Nonvolatile -> mempty
-  ppr (SetScalar name val) =
-    ppr name <+> "<-" <+> ppr val
-  ppr (SetMem dest from DefaultSpace) =
-    ppr dest <+> "<-" <+> ppr from
-  ppr (SetMem dest from space) =
-    ppr dest <+> "<-" <+> ppr from <+> "@" <> ppr space
-  ppr (Assert e msg _) =
-    "assert" <> parens (commasep [ppr msg, ppr e])
-  ppr (Copy t dest destoffset destspace src srcoffset srcspace size) =
+  pretty (SetScalar name val) =
+    pretty name <+> "<-" <+> pretty val
+  pretty (SetMem dest from DefaultSpace) =
+    pretty dest <+> "<-" <+> pretty from
+  pretty (SetMem dest from space) =
+    pretty dest <+> "<-" <+> pretty from <+> "@" <> pretty space
+  pretty (Assert e msg _) =
+    "assert" <> parens (commasep [pretty msg, pretty e])
+  pretty (Copy t dest destoffset destspace src srcoffset srcspace size) =
     "copy"
       <> parens
-        ( ppr t <> comma
-            </> ppMemLoc dest destoffset <> ppr destspace <> comma
-            </> ppMemLoc src srcoffset <> ppr srcspace <> comma
-            </> ppr size
+        ( pretty t <> comma
+            </> ppMemLoc dest destoffset <> pretty destspace <> comma
+            </> ppMemLoc src srcoffset <> pretty srcspace <> comma
+            </> pretty size
         )
     where
       ppMemLoc base offset =
-        ppr base <+> "+" <+> ppr offset
-  ppr (If cond tbranch fbranch) =
+        pretty base <+> "+" <+> pretty offset
+  pretty (If cond tbranch fbranch) =
     "if"
-      <+> ppr cond
+      <+> pretty cond
       <+> "then {"
-      </> indent 2 (ppr tbranch)
+      </> indent 2 (pretty tbranch)
       </> "} else"
       <+> case fbranch of
-        If {} -> ppr fbranch
+        If {} -> pretty fbranch
         _ ->
-          "{" </> indent 2 (ppr fbranch) </> "}"
-  ppr (Call dests fname args) =
-    commasep (map ppr dests)
+          "{" </> indent 2 (pretty fbranch) </> "}"
+  pretty (Call dests fname args) =
+    commasep (map pretty dests)
       <+> "<-"
-      <+> ppr fname <> parens (commasep $ map ppr args)
-  ppr (Comment s code) =
-    "--" <+> text s </> ppr code
-  ppr (DebugPrint desc (Just e)) =
-    "debug" <+> parens (commasep [text (show desc), ppr e])
-  ppr (DebugPrint desc Nothing) =
-    "debug" <+> parens (text (show desc))
-  ppr (TracePrint msg) =
-    "trace" <+> parens (ppr msg)
+      <+> pretty fname <> parens (commasep $ map pretty args)
+  pretty (Comment s code) =
+    "--" <+> pretty s </> pretty code
+  pretty (DebugPrint desc (Just e)) =
+    "debug" <+> parens (commasep [pretty (show desc), pretty e])
+  pretty (DebugPrint desc Nothing) =
+    "debug" <+> parens (pretty (show desc))
+  pretty (TracePrint msg) =
+    "trace" <+> parens (pretty msg)
 
 instance Pretty Arg where
-  ppr (MemArg m) = ppr m
-  ppr (ExpArg e) = ppr e
+  pretty (MemArg m) = pretty m
+  pretty (ExpArg e) = pretty e
 
 instance Functor Functions where
   fmap = fmapDefault
diff --git a/src/Futhark/CodeGen/ImpCode/GPU.hs b/src/Futhark/CodeGen/ImpCode/GPU.hs
--- a/src/Futhark/CodeGen/ImpCode/GPU.hs
+++ b/src/Futhark/CodeGen/ImpCode/GPU.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Variation of "Futhark.CodeGen.ImpCode" that contains the notion
 -- of a kernel invocation.
 module Futhark.CodeGen.ImpCode.GPU
@@ -83,31 +80,31 @@
   deriving (Eq, Ord, Show)
 
 instance Pretty KernelConst where
-  ppr (SizeConst key) = text "get_size" <> parens (ppr key)
+  pretty (SizeConst key) = "get_size" <> parens (pretty key)
 
 instance Pretty KernelUse where
-  ppr (ScalarUse name t) =
-    oneLine $ text "scalar_copy" <> parens (commasep [ppr name, ppr t])
-  ppr (MemoryUse name) =
-    oneLine $ text "mem_copy" <> parens (commasep [ppr name])
-  ppr (ConstUse name e) =
-    oneLine $ text "const" <> parens (commasep [ppr name, ppr e])
+  pretty (ScalarUse name t) =
+    oneLine $ "scalar_copy" <> parens (commasep [pretty name, pretty t])
+  pretty (MemoryUse name) =
+    oneLine $ "mem_copy" <> parens (commasep [pretty name])
+  pretty (ConstUse name e) =
+    oneLine $ "const" <> parens (commasep [pretty name, pretty e])
 
 instance Pretty HostOp where
-  ppr (GetSize dest key size_class) =
-    ppr dest
-      <+> text "<-"
-      <+> text "get_size" <> parens (commasep [ppr key, ppr size_class])
-  ppr (GetSizeMax dest size_class) =
-    ppr dest <+> text "<-" <+> text "get_size_max" <> parens (ppr size_class)
-  ppr (CmpSizeLe dest name size_class x) =
-    ppr dest
-      <+> text "<-"
-      <+> text "get_size" <> parens (commasep [ppr name, ppr size_class])
-      <+> text "<"
-      <+> ppr x
-  ppr (CallKernel c) =
-    ppr c
+  pretty (GetSize dest key size_class) =
+    pretty dest
+      <+> "<-"
+      <+> "get_size" <> parens (commasep [pretty key, pretty size_class])
+  pretty (GetSizeMax dest size_class) =
+    pretty dest <+> "<-" <+> "get_size_max" <> parens (pretty size_class)
+  pretty (CmpSizeLe dest name size_class x) =
+    pretty dest
+      <+> "<-"
+      <+> "get_size" <> parens (commasep [pretty name, pretty size_class])
+      <+> "<"
+      <+> pretty x
+  pretty (CallKernel c) =
+    pretty c
 
 instance FreeIn HostOp where
   freeIn' (CallKernel c) =
@@ -125,21 +122,21 @@
       <> freeIn' [kernelNumGroups kernel, kernelGroupSize kernel]
 
 instance Pretty Kernel where
-  ppr kernel =
-    text "kernel"
+  pretty kernel =
+    "kernel"
       <+> brace
-        ( text "groups"
-            <+> brace (ppr $ kernelNumGroups kernel)
-            </> text "group_size"
-            <+> brace (ppr $ kernelGroupSize kernel)
-            </> text "uses"
-            <+> brace (commasep $ map ppr $ kernelUses kernel)
-            </> text "failure_tolerant"
-            <+> brace (ppr $ kernelFailureTolerant kernel)
-            </> text "check_local_memory"
-            <+> brace (ppr $ kernelCheckLocalMemory kernel)
-            </> text "body"
-            <+> brace (ppr $ kernelBody kernel)
+        ( "groups"
+            <+> brace (pretty $ kernelNumGroups kernel)
+            </> "group_size"
+            <+> brace (pretty $ kernelGroupSize kernel)
+            </> "uses"
+            <+> brace (commasep $ map pretty $ kernelUses kernel)
+            </> "failure_tolerant"
+            <+> brace (pretty $ kernelFailureTolerant kernel)
+            </> "check_local_memory"
+            <+> brace (pretty $ kernelCheckLocalMemory kernel)
+            </> "body"
+            <+> brace (pretty $ kernelBody kernel)
         )
 
 -- | When we do a barrier or fence, is it at the local or global
@@ -197,106 +194,106 @@
   freeIn' (AtomicXchg _ _ arr i x) = freeIn' arr <> freeIn' i <> freeIn' x
 
 instance Pretty KernelOp where
-  ppr (GetGroupId dest i) =
-    ppr dest
+  pretty (GetGroupId dest i) =
+    pretty dest
       <+> "<-"
-      <+> "get_group_id" <> parens (ppr i)
-  ppr (GetLocalId dest i) =
-    ppr dest
+      <+> "get_group_id" <> parens (pretty i)
+  pretty (GetLocalId dest i) =
+    pretty dest
       <+> "<-"
-      <+> "get_local_id" <> parens (ppr i)
-  ppr (GetLocalSize dest i) =
-    ppr dest
+      <+> "get_local_id" <> parens (pretty i)
+  pretty (GetLocalSize dest i) =
+    pretty dest
       <+> "<-"
-      <+> "get_local_size" <> parens (ppr i)
-  ppr (GetLockstepWidth dest) =
-    ppr dest
+      <+> "get_local_size" <> parens (pretty i)
+  pretty (GetLockstepWidth dest) =
+    pretty dest
       <+> "<-"
       <+> "get_lockstep_width()"
-  ppr (Barrier FenceLocal) =
+  pretty (Barrier FenceLocal) =
     "local_barrier()"
-  ppr (Barrier FenceGlobal) =
+  pretty (Barrier FenceGlobal) =
     "global_barrier()"
-  ppr (MemFence FenceLocal) =
+  pretty (MemFence FenceLocal) =
     "mem_fence_local()"
-  ppr (MemFence FenceGlobal) =
+  pretty (MemFence FenceGlobal) =
     "mem_fence_global()"
-  ppr (LocalAlloc name size) =
-    ppr name <+> equals <+> "local_alloc" <> parens (ppr size)
-  ppr (ErrorSync FenceLocal) =
+  pretty (LocalAlloc name size) =
+    pretty name <+> equals <+> "local_alloc" <> parens (pretty size)
+  pretty (ErrorSync FenceLocal) =
     "error_sync_local()"
-  ppr (ErrorSync FenceGlobal) =
+  pretty (ErrorSync FenceGlobal) =
     "error_sync_global()"
-  ppr (Atomic _ (AtomicAdd t old arr ind x)) =
-    ppr old
+  pretty (Atomic _ (AtomicAdd t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_add_"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicFAdd t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicFAdd t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_fadd_"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicSMax t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicSMax t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_smax"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicSMin t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicSMin t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_smin"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicUMax t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicUMax t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_umax"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicUMin t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicUMin t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_umin"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicAnd t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicAnd t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_and"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicOr t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicOr t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_or"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicXor t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicXor t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_xor"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
-  ppr (Atomic _ (AtomicCmpXchg t old arr ind x y)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
+  pretty (Atomic _ (AtomicCmpXchg t old arr ind x y)) =
+    pretty old
       <+> "<-"
       <+> "atomic_cmp_xchg"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x, ppr y])
-  ppr (Atomic _ (AtomicXchg t old arr ind x)) =
-    ppr old
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x, pretty y])
+  pretty (Atomic _ (AtomicXchg t old arr ind x)) =
+    pretty old
       <+> "<-"
       <+> "atomic_xchg"
-        <> ppr t
-        <> parens (commasep [ppr arr <> brackets (ppr ind), ppr x])
+        <> pretty t
+        <> parens (commasep [pretty arr <> brackets (pretty ind), pretty x])
 
 instance FreeIn KernelOp where
   freeIn' (Atomic _ op) = freeIn' op
   freeIn' _ = mempty
 
-brace :: Doc -> Doc
+brace :: Doc a -> Doc a
 brace body = " {" </> indent 2 body </> "}"
diff --git a/src/Futhark/CodeGen/ImpCode/Multicore.hs b/src/Futhark/CodeGen/ImpCode/Multicore.hs
--- a/src/Futhark/CodeGen/ImpCode/Multicore.hs
+++ b/src/Futhark/CodeGen/ImpCode/Multicore.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Multicore imperative code.
 module Futhark.CodeGen.ImpCode.Multicore
   ( Program,
@@ -15,7 +13,7 @@
   )
 where
 
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.CodeGen.ImpCode
 import Futhark.Util.Pretty
 
@@ -91,63 +89,63 @@
   | Static
 
 instance Pretty Scheduling where
-  ppr Dynamic = "Dynamic"
-  ppr Static = "Static"
+  pretty Dynamic = "Dynamic"
+  pretty Static = "Static"
 
 instance Pretty SchedulerInfo where
-  ppr (SchedulerInfo i sched) =
+  pretty (SchedulerInfo i sched) =
     stack
-      [ nestedBlock "scheduling {" "}" (ppr sched),
-        nestedBlock "iter {" "}" (ppr i)
+      [ nestedBlock "scheduling {" "}" (pretty sched),
+        nestedBlock "iter {" "}" (pretty i)
       ]
 
 instance Pretty ParallelTask where
-  ppr (ParallelTask code) = ppr code
+  pretty (ParallelTask code) = pretty code
 
 instance Pretty Multicore where
-  ppr (GetLoopBounds start end) =
-    ppr (start, end) <+> "<-" <+> "get_loop_bounds()"
-  ppr (GetTaskId v) =
-    ppr v <+> "<-" <+> "get_task_id()"
-  ppr (GetNumTasks v) =
-    ppr v <+> "<-" <+> "get_num_tasks()"
-  ppr (SegOp s free seq_code par_code retval scheduler) =
-    "SegOp" <+> text s <+> nestedBlock "{" "}" ppbody
+  pretty (GetLoopBounds start end) =
+    pretty (start, end) <+> "<-" <+> "get_loop_bounds()"
+  pretty (GetTaskId v) =
+    pretty v <+> "<-" <+> "get_task_id()"
+  pretty (GetNumTasks v) =
+    pretty v <+> "<-" <+> "get_num_tasks()"
+  pretty (SegOp s free seq_code par_code retval scheduler) =
+    "SegOp" <+> pretty s <+> nestedBlock "{" "}" ppbody
     where
       ppbody =
         stack
-          [ ppr scheduler,
-            nestedBlock "free {" "}" (ppr free),
-            nestedBlock "seq {" "}" (ppr seq_code),
-            maybe mempty (nestedBlock "par {" "}" . ppr) par_code,
-            nestedBlock "retvals {" "}" (ppr retval)
+          [ pretty scheduler,
+            nestedBlock "free {" "}" (pretty free),
+            nestedBlock "seq {" "}" (pretty seq_code),
+            maybe mempty (nestedBlock "par {" "}" . pretty) par_code,
+            nestedBlock "retvals {" "}" (pretty retval)
           ]
-  ppr (ParLoop s body params) =
-    "parloop" <+> ppr s </> nestedBlock "{" "}" ppbody
+  pretty (ParLoop s body params) =
+    "parloop" <+> pretty s </> nestedBlock "{" "}" ppbody
     where
       ppbody =
         stack
-          [ nestedBlock "params {" "}" (ppr params),
-            nestedBlock "body {" "}" (ppr body)
+          [ nestedBlock "params {" "}" (pretty params),
+            nestedBlock "body {" "}" (pretty body)
           ]
-  ppr (Atomic _) =
+  pretty (Atomic _) =
     "AtomicOp"
-  ppr (ISPCKernel body _) =
-    "ispc" <+> nestedBlock "{" "}" (ppr body)
-  ppr (ForEach i from to body) =
+  pretty (ISPCKernel body _) =
+    "ispc" <+> nestedBlock "{" "}" (pretty body)
+  pretty (ForEach i from to body) =
     "foreach"
-      <+> ppr i
+      <+> pretty i
       <+> "="
-      <+> ppr from
+      <+> pretty from
       <+> "to"
-      <+> ppr to
-      <+> nestedBlock "{" "}" (ppr body)
-  ppr (ForEachActive i body) =
+      <+> pretty to
+      <+> nestedBlock "{" "}" (pretty body)
+  pretty (ForEachActive i body) =
     "foreach_active"
-      <+> ppr i
-      <+> nestedBlock "{" "}" (ppr body)
-  ppr (ExtractLane dest tar lane) =
-    ppr dest <+> "<-" <+> "extract" <+> parens (commasep $ map ppr [tar, lane])
+      <+> pretty i
+      <+> nestedBlock "{" "}" (pretty body)
+  pretty (ExtractLane dest tar lane) =
+    pretty dest <+> "<-" <+> "extract" <+> parens (commasep $ map pretty [tar, lane])
 
 instance FreeIn SchedulerInfo where
   freeIn' (SchedulerInfo iter _) = freeIn' iter
diff --git a/src/Futhark/CodeGen/ImpCode/OpenCL.hs b/src/Futhark/CodeGen/ImpCode/OpenCL.hs
--- a/src/Futhark/CodeGen/ImpCode/OpenCL.hs
+++ b/src/Futhark/CodeGen/ImpCode/OpenCL.hs
@@ -21,8 +21,8 @@
   )
 where
 
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.CodeGen.ImpCode
 import Futhark.IR.GPU.Sizes
 import Futhark.Util.Pretty
@@ -105,4 +105,4 @@
   deriving (Eq)
 
 instance Pretty OpenCL where
-  ppr = text . show
+  pretty = pretty . show
diff --git a/src/Futhark/CodeGen/ImpCode/Sequential.hs b/src/Futhark/CodeGen/ImpCode/Sequential.hs
--- a/src/Futhark/CodeGen/ImpCode/Sequential.hs
+++ b/src/Futhark/CodeGen/ImpCode/Sequential.hs
@@ -16,7 +16,7 @@
 data Sequential
 
 instance Pretty Sequential where
-  ppr _ = empty
+  pretty _ = mempty
 
 instance FreeIn Sequential where
   freeIn' _ = mempty
diff --git a/src/Futhark/CodeGen/ImpGen.hs b/src/Futhark/CodeGen/ImpGen.hs
--- a/src/Futhark/CodeGen/ImpGen.hs
+++ b/src/Futhark/CodeGen/ImpGen.hs
@@ -1,13 +1,5 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.CodeGen.ImpGen
@@ -131,14 +123,15 @@
 import Control.Monad.Writer
 import Control.Parallel.Strategies
 import Data.Bifunctor (first)
-import qualified Data.DList as DL
+import Data.DList qualified as DL
 import Data.Either
 import Data.List (find)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Data.String
+import Data.Text qualified as T
 import Futhark.CodeGen.ImpCode
   ( Bytes,
     Count,
@@ -147,15 +140,16 @@
     elements,
     withElemType,
   )
-import qualified Futhark.CodeGen.ImpCode as Imp
+import Futhark.CodeGen.ImpCode qualified as Imp
 import Futhark.CodeGen.ImpGen.Transpose
 import Futhark.Construct hiding (ToExp (..))
 import Futhark.IR.Mem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.IR.SOACS (SOACS)
 import Futhark.Util
 import Futhark.Util.IntegralExp
 import Futhark.Util.Loc (noLoc)
+import Futhark.Util.Pretty hiding (nest, space)
 import Language.Futhark.Warnings
 import Prelude hiding (mod, quot)
 
@@ -400,7 +394,7 @@
 
 -- | Execute a code generation action, wrapping the generated code
 -- within a 'Imp.Comment' with the given description.
-comment :: String -> ImpM rep r op () -> ImpM rep r op ()
+comment :: T.Text -> ImpM rep r op () -> ImpM rep r op ()
 comment desc m = do
   code <- collect m
   emit $ Imp.Comment desc code
@@ -413,9 +407,9 @@
 warnings ws = modify $ \s -> s {stateWarnings = ws <> stateWarnings s}
 
 -- | Emit a warning about something the user should be aware of.
-warn :: Located loc => loc -> [loc] -> String -> ImpM rep r op ()
+warn :: Located loc => loc -> [loc] -> T.Text -> ImpM rep r op ()
 warn loc locs problem =
-  warnings $ singleWarning' (srclocOf loc) (map srclocOf locs) (fromString problem)
+  warnings $ singleWarning' (srclocOf loc) (map srclocOf locs) (pretty problem)
 
 -- | Emit a function in the generated code.
 emitFunction :: Name -> Imp.Function op -> ImpM rep r op ()
@@ -875,12 +869,12 @@
   opc <- asks envOpCompiler
   opc pat op
 
-tracePrim :: String -> PrimType -> SubExp -> ImpM rep r op ()
+tracePrim :: T.Text -> PrimType -> SubExp -> ImpM rep r op ()
 tracePrim s t se =
   emit . Imp.TracePrint $
     ErrorMsg [ErrorString (s <> ": "), ErrorVal t (toExp' t se), ErrorString "\n"]
 
-traceArray :: String -> PrimType -> Shape -> SubExp -> ImpM rep r op ()
+traceArray :: T.Text -> PrimType -> Shape -> SubExp -> ImpM rep r op ()
 traceArray s t shape se = do
   emit . Imp.TracePrint $ ErrorMsg [ErrorString (s <> ": ")]
   sLoopNest shape $ \is -> do
@@ -907,7 +901,7 @@
         Array t shape _ -> traceArray s t shape se
         _ ->
           warn [mempty :: SrcLoc] mempty $
-            s ++ ": cannot trace value of this (core) type: " <> pretty se_t
+            s <> ": cannot trace value of this (core) type: " <> prettyText se_t
 defCompileBasicOp (Pat [pe]) (UnOp op e) = do
   e' <- toExp e
   patElemName pe <~~ Imp.UnOpExp op e'
@@ -980,7 +974,7 @@
   forM_ (x : ys) $ \y -> do
     y_dims <- arrayDims <$> lookupType y
     let rows = case drop i y_dims of
-          [] -> error $ "defCompileBasicOp Concat: empty array shape for " ++ pretty y
+          [] -> error $ "defCompileBasicOp Concat: empty array shape for " ++ prettyString y
           r : _ -> pe64 r
         skip_dims = take i y_dims
         sliceAllDim d = DimSlice 0 d 1
@@ -1054,9 +1048,9 @@
 defCompileBasicOp pat e =
   error $
     "ImpGen.defCompileBasicOp: Invalid pattern\n  "
-      ++ pretty pat
+      ++ prettyString pat
       ++ "\nfor expression\n  "
-      ++ pretty e
+      ++ prettyString e
 
 -- | Note: a hack to be used only for functions.
 addArrays :: [ArrayDecl] -> ImpM rep r op ()
@@ -1258,7 +1252,7 @@
     lookupVar v >>= \case
       ScalarVar _ (ScalarEntry pt) ->
         pure $ Imp.var v pt
-      _ -> error $ "toExp SubExp: SubExp is not a primitive type: " ++ pretty v
+      _ -> error $ "toExp SubExp: SubExp is not a primitive type: " ++ prettyString v
 
   toExp' _ (Constant v) = Imp.ValueExp v
   toExp' t (Var v) = Imp.var v t
@@ -1336,21 +1330,21 @@
   res <- gets $ M.lookup name . stateVTable
   case res of
     Just entry -> pure entry
-    _ -> error $ "Unknown variable: " ++ pretty name
+    _ -> error $ "Unknown variable: " ++ prettyString name
 
 lookupArray :: VName -> ImpM rep r op ArrayEntry
 lookupArray name = do
   res <- lookupVar name
   case res of
     ArrayVar _ entry -> pure entry
-    _ -> error $ "ImpGen.lookupArray: not an array: " ++ pretty name
+    _ -> error $ "ImpGen.lookupArray: not an array: " ++ prettyString name
 
 lookupMemory :: VName -> ImpM rep r op MemEntry
 lookupMemory name = do
   res <- lookupVar name
   case res of
     MemVar _ entry -> pure entry
-    _ -> error $ "Unknown memory block: " ++ pretty name
+    _ -> error $ "Unknown memory block: " ++ prettyString name
 
 lookupArraySpace :: VName -> ImpM rep r op Space
 lookupArraySpace =
@@ -1370,7 +1364,7 @@
       acc' <- gets $ M.lookup acc . stateAccs
       case acc' of
         Just ([], _) ->
-          error $ "Accumulator with no arrays: " ++ pretty name
+          error $ "Accumulator with no arrays: " ++ prettyString name
         Just (arrs@(arr : _), Just (op, _)) -> do
           space <- lookupArraySpace arr
           let (i_params, ps) = splitAt (length is) $ lambdaParams op
@@ -1386,8 +1380,8 @@
           space <- lookupArraySpace arr
           pure (acc, space, arrs, map pe64 (shapeDims ispace), Nothing)
         Nothing ->
-          error $ "ImpGen.lookupAcc: unlisted accumulator: " ++ pretty name
-    _ -> error $ "ImpGen.lookupAcc: not an accumulator: " ++ pretty name
+          error $ "ImpGen.lookupAcc: unlisted accumulator: " ++ prettyString name
+    _ -> error $ "ImpGen.lookupAcc: not an accumulator: " ++ prettyString name
 
 destinationFromPat :: Pat (LetDec rep) -> ImpM rep r op [ValueDestination]
 destinationFromPat = mapM inspect . patElems
@@ -1492,7 +1486,7 @@
        in (product mapped, product pretrans, product posttrans)
 
 mapTransposeName :: PrimType -> String
-mapTransposeName bt = "map_transpose_" ++ pretty bt
+mapTransposeName bt = "map_transpose_" ++ prettyString bt
 
 mapTransposeForType :: PrimType -> ImpM rep r op Name
 mapTransposeForType bt = do
@@ -1609,13 +1603,13 @@
           then
             error $
               "copyArrayDWIM: cannot copy to "
-                ++ pretty (memLocName destlocation)
+                ++ prettyString (memLocName destlocation)
                 ++ " from "
-                ++ pretty (memLocName srclocation)
+                ++ prettyString (memLocName srclocation)
                 ++ " because ranks do not match ("
-                ++ pretty destrank
+                ++ prettyString destrank
                 ++ " vs "
-                ++ pretty srcrank
+                ++ prettyString srcrank
                 ++ ")"
           else
             if destlocation' == srclocation'
@@ -1632,19 +1626,19 @@
   ImpM rep r op ()
 copyDWIMDest _ _ (Constant v) (_ : _) =
   error $
-    unwords ["copyDWIMDest: constant source", pretty v, "cannot be indexed."]
+    unwords ["copyDWIMDest: constant source", prettyString v, "cannot be indexed."]
 copyDWIMDest pat dest_slice (Constant v) [] =
   case mapM dimFix dest_slice of
     Nothing ->
       error $
-        unwords ["copyDWIMDest: constant source", pretty v, "with slice destination."]
+        unwords ["copyDWIMDest: constant source", prettyString v, "with slice destination."]
     Just dest_is ->
       case pat of
         ScalarDestination name ->
           emit $ Imp.SetScalar name $ Imp.ValueExp v
         MemoryDestination {} ->
           error $
-            unwords ["copyDWIMDest: constant source", pretty v, "cannot be written to memory destination."]
+            unwords ["copyDWIMDest: constant source", prettyString v, "cannot be written to memory destination."]
         ArrayDestination (Just dest_loc) -> do
           (dest_mem, dest_space, dest_i) <-
             fullyIndexArray' dest_loc dest_is
@@ -1661,18 +1655,18 @@
       emit $ Imp.SetMem mem src space
     (MemoryDestination {}, _) ->
       error $
-        unwords ["copyDWIMDest: cannot write", pretty src, "to memory destination."]
+        unwords ["copyDWIMDest: cannot write", prettyString src, "to memory destination."]
     (_, MemVar {}) ->
       error $
-        unwords ["copyDWIMDest: source", pretty src, "is a memory block."]
+        unwords ["copyDWIMDest: source", prettyString src, "is a memory block."]
     (_, ScalarVar _ (ScalarEntry _))
       | not $ null src_slice ->
           error $
-            unwords ["copyDWIMDest: prim-typed source", pretty src, "with slice", pretty src_slice]
+            unwords ["copyDWIMDest: prim-typed source", prettyString src, "with slice", prettyString src_slice]
     (ScalarDestination name, _)
       | not $ null dest_slice ->
           error $
-            unwords ["copyDWIMDest: prim-typed target", pretty name, "with slice", pretty dest_slice]
+            unwords ["copyDWIMDest: prim-typed target", prettyString name, "with slice", prettyString dest_slice]
     (ScalarDestination name, ScalarVar _ (ScalarEntry pt)) ->
       emit $ Imp.SetScalar name $ Imp.var src pt
     (ScalarDestination name, ArrayVar _ arr)
@@ -1687,13 +1681,13 @@
           error $
             unwords
               [ "copyDWIMDest: prim-typed target",
-                pretty name,
+                prettyString name,
                 "and array-typed source",
-                pretty src,
+                prettyString src,
                 "of shape",
-                pretty (entryArrayShape arr),
+                prettyString (entryArrayShape arr),
                 "sliced with",
-                pretty src_slice
+                prettyString src_slice
               ]
     (ArrayDestination (Just dest_loc), ArrayVar _ src_arr) -> do
       let src_loc = entryArrayLoc src_arr
@@ -1709,9 +1703,9 @@
           error $
             unwords
               [ "copyDWIMDest: array-typed target and prim-typed source",
-                pretty src,
+                prettyString src,
                 "with slice",
-                pretty dest_slice
+                prettyString dest_slice
               ]
     (ArrayDestination Nothing, _) ->
       pure () -- Nothing to do; something else set some memory
@@ -1766,7 +1760,7 @@
     Nothing -> emit $ Imp.Allocate (patElemName mem) e' space
     Just allocator' -> allocator' (patElemName mem) e'
 compileAlloc pat _ _ =
-  error $ "compileAlloc: Invalid pattern: " ++ pretty pat
+  error $ "compileAlloc: Invalid pattern: " ++ prettyString pat
 
 -- | The number of bytes needed to represent the array in a
 -- straightforward contiguous format, as an t'Int64' expression.
@@ -1798,7 +1792,7 @@
 sFor' i bound body = do
   let it = case primExpType bound of
         IntType bound_t -> bound_t
-        t -> error $ "sFor': bound " ++ pretty bound ++ " is of type " ++ pretty t
+        t -> error $ "sFor': bound " ++ prettyString bound ++ " is of type " ++ prettyString t
   addLoopVar i it
   body' <- collect body
   emit $ Imp.For i bound body'
@@ -1818,7 +1812,7 @@
   body' <- collect body
   emit $ Imp.While cond body'
 
-sComment :: String -> ImpM rep r op () -> ImpM rep r op ()
+sComment :: T.Text -> ImpM rep r op () -> ImpM rep r op ()
 sComment s code = do
   code' <- collect code
   emit $ Imp.Comment s code'
diff --git a/src/Futhark/CodeGen/ImpGen/GPU.hs b/src/Futhark/CodeGen/ImpGen/GPU.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Compile a 'GPUMem' program to imperative code with kernels.
@@ -17,12 +14,12 @@
 import Control.Monad.Except
 import Data.Bifunctor (second)
 import Data.List (foldl')
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Futhark.CodeGen.ImpCode.GPU (bytes)
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen hiding (compileProg)
-import qualified Futhark.CodeGen.ImpGen
+import Futhark.CodeGen.ImpGen qualified
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.CodeGen.ImpGen.GPU.SegHist
 import Futhark.CodeGen.ImpGen.GPU.SegMap
@@ -32,7 +29,7 @@
 import Futhark.CodeGen.SetDefaultSpace
 import Futhark.Error
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Util.IntegralExp (IntegralExp, divUp, quot, rem)
 import Prelude hiding (quot, rem)
@@ -152,9 +149,9 @@
 opCompiler pat e =
   compilerBugS $
     "opCompiler: Invalid pattern\n  "
-      ++ pretty pat
+      ++ prettyString pat
       ++ "\nfor expression\n  "
-      ++ pretty e
+      ++ prettyString e
 
 sizeClassWithEntryPoint :: Maybe Name -> Imp.SizeClass -> Imp.SizeClass
 sizeClassWithEntryPoint fname (Imp.SizeThreshold path def) =
@@ -176,7 +173,7 @@
 segOpCompiler pat (SegHist (SegThread num_groups group_size _) space ops _ kbody) =
   compileSegHist pat num_groups group_size space ops kbody
 segOpCompiler pat segop =
-  compilerBugS $ "segOpCompiler: unexpected " ++ pretty (segLevel segop) ++ " for rhs of pattern " ++ pretty pat
+  compilerBugS $ "segOpCompiler: unexpected " ++ prettyString (segLevel segop) ++ " for rhs of pattern " ++ prettyString pat
 
 -- Create boolean expression that checks whether all kernels in the
 -- enclosed code do not use more local memory than we have available.
@@ -323,7 +320,7 @@
   pure fname
 
 mapTransposeName :: PrimType -> String
-mapTransposeName bt = "gpu_map_transpose_" ++ pretty bt
+mapTransposeName bt = "gpu_map_transpose_" ++ prettyString bt
 
 mapTransposeFunction :: PrimType -> Imp.Function Imp.HostOp
 mapTransposeFunction bt =
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/Base.hs b/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/Base.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TypeFamilies #-}
 
@@ -46,13 +45,13 @@
 
 import Control.Monad.Except
 import Data.List (foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.Error
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Transform.Rename
 import Futhark.Util (dropLast, nubOrd, splitFromEnd)
@@ -128,7 +127,7 @@
 kernelAlloc (Pat [mem]) size (Space "local") =
   allocLocal (patElemName mem) $ Imp.bytes $ pe64 size
 kernelAlloc (Pat [mem]) _ _ =
-  compilerLimitationS $ "Cannot allocate memory block " ++ pretty mem ++ " in kernel."
+  compilerLimitationS $ "Cannot allocate memory block " ++ prettyString mem ++ " in kernel."
 kernelAlloc dest _ _ =
   error $ "Invalid target for in-kernel allocation: " ++ show dest
 
@@ -159,7 +158,7 @@
                         pure . (`rem` fromIntegral num_locks) . flattenIndex dims
                 f locking space arrs is'
               Nothing ->
-                error $ "Missing locks for " ++ pretty acc
+                error $ "Missing locks for " ++ prettyString acc
 
 compileThreadExp :: ExpCompiler GPUMem KernelEnv Imp.KernelOp
 compileThreadExp (Pat [pe]) (BasicOp (Opaque _ se)) =
@@ -602,7 +601,7 @@
 compileThreadOp pat (Alloc size space) =
   kernelAlloc pat size space
 compileThreadOp pat _ =
-  compilerBugS $ "compileThreadOp: cannot compile rhs of binding " ++ pretty pat
+  compilerBugS $ "compileThreadOp: cannot compile rhs of binding " ++ prettyString pat
 
 -- | Locking strategy used for an atomic update.
 data Locking = Locking
@@ -956,7 +955,7 @@
 simpleKernelGroups max_num_groups kernel_size = do
   group_size <- dPrim "group_size" int64
   fname <- askFunction
-  let group_size_key = keyWithEntryPoint fname $ nameFromString $ pretty $ tvVar group_size
+  let group_size_key = keyWithEntryPoint fname $ nameFromString $ prettyString $ tvVar group_size
   sOp $ Imp.GetSize (tvVar group_size) group_size_key Imp.SizeGroup
   virt_num_groups <- dPrimVE "virt_num_groups" $ kernel_size `divUp` tvExp group_size
   num_groups <- dPrimV "num_groups" $ virt_num_groups `sMin64` max_num_groups
@@ -1176,7 +1175,7 @@
           drop (length ds) is'
 
 replicateName :: PrimType -> String
-replicateName bt = "replicate_" ++ pretty bt
+replicateName bt = "replicate_" ++ prettyString bt
 
 replicateForType :: PrimType -> CallKernelGen Name
 replicateForType bt = do
@@ -1248,7 +1247,7 @@
         keyWithEntryPoint fname $
           nameFromString $
             "iota_"
-              ++ pretty et
+              ++ prettyString et
               ++ "_"
               ++ show (baseTag $ kernelGlobalThreadIdVar constants)
 
@@ -1265,7 +1264,7 @@
               x
 
 iotaName :: IntType -> String
-iotaName bt = "iota_" ++ pretty bt
+iotaName bt = "iota_" ++ prettyString bt
 
 iotaForType :: IntType -> CallKernelGen Name
 iotaForType bt = do
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/Group.hs b/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/Group.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Generation of kernels with group-level bodies.
@@ -18,16 +17,16 @@
 import Control.Monad.Except
 import Data.Bifunctor
 import Data.List (partition, zip4)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Data.Set qualified as S
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.Construct (fullSliceNum)
 import Futhark.Error
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Transform.Rename
 import Futhark.Util (chunks, mapAccumLM, takeLast)
@@ -554,7 +553,7 @@
 
   sOp $ Imp.ErrorSync Imp.FenceLocal
 compileGroupOp pat _ =
-  compilerBugS $ "compileGroupOp: cannot compile rhs of binding " ++ pretty pat
+  compilerBugS $ "compileGroupOp: cannot compile rhs of binding " ++ prettyString pat
 
 groupOperations :: Operations GPUMem KernelEnv Imp.KernelOp
 groupOperations =
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegHist.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Our compilation strategy for 'SegHist' is based around avoiding
@@ -42,13 +40,13 @@
 import Control.Monad.Except
 import Data.List (foldl', genericLength, zip5)
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.CodeGen.ImpGen.GPU.SegRed (compileSegRed')
 import Futhark.Construct (fullSliceNum)
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Pass.ExplicitAllocations ()
 import Futhark.Util (chunks, mapAccumLM, maxinum, splitFromEnd, takeLast)
@@ -251,7 +249,7 @@
   sOp
     $ Imp.GetSize
       (tvVar hist_L2)
-      (keyWithEntryPoint entry $ nameFromString (pretty (tvVar hist_L2)))
+      (keyWithEntryPoint entry $ nameFromString (prettyString (tvVar hist_L2)))
     $ Imp.SizeBespoke (nameFromString "L2_for_histogram") hist_L2_def
 
   let hist_L2_ln_sz = 16 * 4 -- L2 cache line size approximation
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegMap.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Code generation for 'SegMap' is quite straightforward.  The only
@@ -8,7 +7,7 @@
 module Futhark.CodeGen.ImpGen.GPU.SegMap (compileSegMap) where
 
 import Control.Monad.Except
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.CodeGen.ImpGen.GPU.Group
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegRed.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | We generate code for non-segmented/single-segment SegRed using
@@ -52,12 +51,12 @@
 import Control.Monad.Except
 import Data.List (genericLength, zip7)
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.Error
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Transform.Rename
 import Futhark.Util (chunks)
 import Futhark.Util.IntegralExp (divUp, quot, rem)
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegScan.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegScan.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegScan.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegScan.hs
@@ -3,11 +3,11 @@
 -- the scan and the chosen abckend.
 module Futhark.CodeGen.ImpGen.GPU.SegScan (compileSegScan) where
 
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen hiding (compileProg)
 import Futhark.CodeGen.ImpGen.GPU.Base
-import qualified Futhark.CodeGen.ImpGen.GPU.SegScan.SinglePass as SinglePass
-import qualified Futhark.CodeGen.ImpGen.GPU.SegScan.TwoPass as TwoPass
+import Futhark.CodeGen.ImpGen.GPU.SegScan.SinglePass qualified as SinglePass
+import Futhark.CodeGen.ImpGen.GPU.SegScan.TwoPass qualified as TwoPass
 import Futhark.IR.GPUMem
 
 -- The single-pass scan does not support multiple operators, so jam
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/SinglePass.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Code generation for segmented and non-segmented scans.  Uses a
@@ -9,11 +8,11 @@
 import Control.Monad.Except
 import Data.List (zip4)
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Transform.Rename
 import Futhark.Util (takeLast)
 import Futhark.Util.IntegralExp (IntegralExp (mod, rem), divUp, quot)
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/SegScan/TwoPass.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Code generation for segmented and non-segmented scans.  Uses a
@@ -9,11 +8,11 @@
 import Control.Monad.State
 import Data.List (delete, find, foldl', zip4)
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.GPU as Imp
+import Futhark.CodeGen.ImpCode.GPU qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.GPU.Base
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Transform.Rename
 import Futhark.Util (takeLast)
 import Futhark.Util.IntegralExp (divUp, quot, rem)
diff --git a/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs b/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
--- a/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
+++ b/src/Futhark/CodeGen/ImpGen/GPU/ToOpenCL.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | This module defines a translation from imperative code with
 -- kernels to imperative code with OpenCL or CUDA calls.
@@ -12,23 +11,23 @@
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Text as T
-import qualified Futhark.CodeGen.Backends.GenericC as GC
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Futhark.CodeGen.Backends.GenericC.Fun qualified as GC
+import Futhark.CodeGen.Backends.GenericC.Pretty
 import Futhark.CodeGen.Backends.SimpleRep
 import Futhark.CodeGen.ImpCode.GPU hiding (Program)
-import qualified Futhark.CodeGen.ImpCode.GPU as ImpGPU
+import Futhark.CodeGen.ImpCode.GPU qualified as ImpGPU
 import Futhark.CodeGen.ImpCode.OpenCL hiding (Program)
-import qualified Futhark.CodeGen.ImpCode.OpenCL as ImpOpenCL
+import Futhark.CodeGen.ImpCode.OpenCL qualified as ImpOpenCL
 import Futhark.CodeGen.RTS.C (atomicsH, halfH)
 import Futhark.Error (compilerLimitationS)
 import Futhark.MonadFreshNames
 import Futhark.Util (zEncodeString)
-import Futhark.Util.Pretty (prettyOneLine, prettyText)
-import qualified Language.C.Quote.OpenCL as C
-import qualified Language.C.Syntax as C
+import Language.C.Quote.OpenCL qualified as C
+import Language.C.Syntax qualified as C
 import NeatInterpolation (untrimming)
 
 -- | Generate CUDA host and device code.
@@ -70,8 +69,8 @@
       opencl_prelude =
         T.unlines
           [ genPrelude target used_types,
-            T.unlines $ map prettyText device_prototypes,
-            T.unlines $ map prettyText device_defs
+            definitionsText device_prototypes,
+            funcsText device_defs
           ]
    in ImpOpenCL.Program
         opencl_code
@@ -380,7 +379,7 @@
 
 useAsParam :: KernelUse -> Maybe (C.Param, [C.BlockItem])
 useAsParam (ScalarUse name pt) = do
-  let name_bits = zEncodeString (pretty name) <> "_bits"
+  let name_bits = zEncodeString (prettyString name) <> "_bits"
       ctp = case pt of
         -- OpenCL does not permit bool as a kernel parameter type.
         Bool -> [C.cty|unsigned char|]
@@ -405,18 +404,18 @@
 constDef :: KernelUse -> Maybe (C.BlockItem, C.BlockItem)
 constDef (ConstUse v e) =
   Just
-    ( [C.citem|$escstm:def|],
-      [C.citem|$escstm:undef|]
+    ( [C.citem|$escstm:(T.unpack def)|],
+      [C.citem|$escstm:(T.unpack undef)|]
     )
   where
     e' = compilePrimExp e
-    def = "#define " ++ pretty (C.toIdent v mempty) ++ " (" ++ prettyOneLine e' ++ ")"
-    undef = "#undef " ++ pretty (C.toIdent v mempty)
+    def = "#define " <> idText (C.toIdent v mempty) <> " (" <> expText e' <> ")"
+    undef = "#undef " <> idText (C.toIdent v mempty)
 constDef _ = Nothing
 
 openClCode :: [C.Func] -> T.Text
 openClCode kernels =
-  prettyText [C.cunit|$edecls:funcs|]
+  definitionsText [C.cunit|$edecls:funcs|]
   where
     funcs =
       [ [C.cedecl|$func:kernel_func|]
@@ -578,7 +577,7 @@
 compilePrimExp e = runIdentity $ GC.compilePrimExp compileKernelConst e
   where
     compileKernelConst (SizeConst key) =
-      pure [C.cexp|$id:(zEncodeString (pretty key))|]
+      pure [C.cexp|$id:(zEncodeString (prettyString key))|]
 
 kernelArgs :: Kernel -> [KernelArg]
 kernelArgs = mapMaybe useToArg . kernelUses
@@ -653,7 +652,7 @@
     kernelOps (MemFence FenceGlobal) =
       GC.stm [C.cstm|mem_fence_global();|]
     kernelOps (LocalAlloc name size) = do
-      name' <- newVName $ pretty name ++ "_backing"
+      name' <- newVName $ prettyString name ++ "_backing"
       GC.modifyUserState $ \s ->
         s {kernelLocalMemory = (name', fmap untyped size) : kernelLocalMemory s}
       GC.stm [C.cstm|$id:name = (__local unsigned char*) $id:name';|]
@@ -685,7 +684,7 @@
       cast <- atomicCast s ty
       GC.stm [C.cstm|$id:old = $id:op'(&(($ty:cast *)$id:arr)[$exp:ind'], ($ty:ty) $exp:val');|]
       where
-        op' = op ++ "_" ++ pretty t ++ "_" ++ atomicSpace s
+        op' = op ++ "_" ++ prettyString t ++ "_" ++ atomicSpace s
 
     doAtomicCmpXchg s t old arr ind cmp val ty = do
       ind' <- GC.compileExp $ untyped $ unCount ind
@@ -694,14 +693,14 @@
       cast <- atomicCast s ty
       GC.stm [C.cstm|$id:old = $id:op(&(($ty:cast *)$id:arr)[$exp:ind'], $exp:cmp', $exp:val');|]
       where
-        op = "atomic_cmpxchg_" ++ pretty t ++ "_" ++ atomicSpace s
+        op = "atomic_cmpxchg_" ++ prettyString t ++ "_" ++ atomicSpace s
     doAtomicXchg s t old arr ind val ty = do
       cast <- atomicCast s ty
       ind' <- GC.compileExp $ untyped $ unCount ind
       val' <- GC.compileExp val
       GC.stm [C.cstm|$id:old = $id:op(&(($ty:cast *)$id:arr)[$exp:ind'], $exp:val');|]
       where
-        op = "atomic_chg_" ++ pretty t ++ "_" ++ atomicSpace s
+        op = "atomic_chg_" ++ prettyString t ++ "_" ++ atomicSpace s
     -- First the 64-bit operations.
     atomicOps s (AtomicAdd Int64 old arr ind val) =
       doAtomic s Int64 old arr ind val "atomic_add" [C.cty|typename int64_t|]
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore.hs b/src/Futhark/CodeGen/ImpGen/Multicore.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Code generation for ImpCode with multicore operations.
@@ -9,8 +8,8 @@
 where
 
 import Control.Monad
-import qualified Data.Map as M
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Data.Map qualified as M
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
 import Futhark.CodeGen.ImpGen.Multicore.SegHist
@@ -81,7 +80,7 @@
                         pure . (`rem` fromIntegral num_locks) . flattenIndex dims
                 f locking arrs is'
               Nothing ->
-                error $ "Missing locks for " ++ pretty acc
+                error $ "Missing locks for " ++ prettyString acc
 
 withAcc ::
   Pat LetDecMem ->
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/Base.hs b/src/Futhark/CodeGen/ImpGen/Multicore/Base.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/Base.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/Base.hs
@@ -32,9 +32,9 @@
 
 import Control.Monad
 import Data.Bifunctor
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.Error
 import Futhark.IR.MCMem
@@ -320,7 +320,7 @@
 sForVectorized' i bound body = do
   let it = case primExpType bound of
         IntType bound_t -> bound_t
-        t -> error $ "sFor': bound " ++ pretty bound ++ " is of type " ++ pretty t
+        t -> error $ "sFor': bound " ++ prettyString bound ++ " is of type " ++ prettyString t
   addLoopVar i it
   body' <- collect body
   emit $ Imp.Op $ Imp.ForEach i (Imp.ValueExp $ blankPrimValue $ Imp.IntType Imp.Int64) bound body'
@@ -563,4 +563,4 @@
 toIntegral 16 = pure int16
 toIntegral 32 = pure int32
 toIntegral 64 = pure int64
-toIntegral b = error $ "number of bytes is not supported for CAS - " ++ pretty b
+toIntegral b = error $ "number of bytes is not supported for CAS - " ++ prettyString b
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs b/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/SegHist.hs
@@ -5,7 +5,7 @@
 
 import Control.Monad
 import Data.List (zip4)
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
 import Futhark.CodeGen.ImpGen.Multicore.SegRed (compileSegRed')
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/SegMap.hs b/src/Futhark/CodeGen/ImpGen/Multicore/SegMap.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/SegMap.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/SegMap.hs
@@ -5,7 +5,7 @@
 where
 
 import Control.Monad
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
 import Futhark.IR.MCMem
@@ -26,7 +26,7 @@
     sWhen (inBounds slice' rws') $
       copyDWIM (patElemName pe) (unSlice slice') v []
 writeResult _ _ res =
-  error $ "writeResult: cannot handle " ++ pretty res
+  error $ "writeResult: cannot handle " ++ prettyString res
 
 compileSegMapBody ::
   Pat LetDecMem ->
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/SegRed.hs b/src/Futhark/CodeGen/ImpGen/Multicore/SegRed.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/SegRed.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/SegRed.hs
@@ -6,7 +6,7 @@
 where
 
 import Control.Monad
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
 import Futhark.IR.MCMem
diff --git a/src/Futhark/CodeGen/ImpGen/Multicore/SegScan.hs b/src/Futhark/CodeGen/ImpGen/Multicore/SegScan.hs
--- a/src/Futhark/CodeGen/ImpGen/Multicore/SegScan.hs
+++ b/src/Futhark/CodeGen/ImpGen/Multicore/SegScan.hs
@@ -5,7 +5,7 @@
 
 import Control.Monad
 import Data.List (zip4)
-import qualified Futhark.CodeGen.ImpCode.Multicore as Imp
+import Futhark.CodeGen.ImpCode.Multicore qualified as Imp
 import Futhark.CodeGen.ImpGen
 import Futhark.CodeGen.ImpGen.Multicore.Base
 import Futhark.IR.MCMem
diff --git a/src/Futhark/CodeGen/ImpGen/OpenCL.hs b/src/Futhark/CodeGen/ImpGen/OpenCL.hs
--- a/src/Futhark/CodeGen/ImpGen/OpenCL.hs
+++ b/src/Futhark/CodeGen/ImpGen/OpenCL.hs
@@ -6,7 +6,7 @@
 where
 
 import Data.Bifunctor (second)
-import qualified Futhark.CodeGen.ImpCode.OpenCL as OpenCL
+import Futhark.CodeGen.ImpCode.OpenCL qualified as OpenCL
 import Futhark.CodeGen.ImpGen.GPU
 import Futhark.CodeGen.ImpGen.GPU.ToOpenCL
 import Futhark.IR.GPUMem
diff --git a/src/Futhark/CodeGen/ImpGen/Sequential.hs b/src/Futhark/CodeGen/ImpGen/Sequential.hs
--- a/src/Futhark/CodeGen/ImpGen/Sequential.hs
+++ b/src/Futhark/CodeGen/ImpGen/Sequential.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Compile Futhark to sequential imperative code.
@@ -8,8 +7,8 @@
   )
 where
 
-import qualified Futhark.CodeGen.ImpCode.Sequential as Imp
-import qualified Futhark.CodeGen.ImpGen as ImpGen
+import Futhark.CodeGen.ImpCode.Sequential qualified as Imp
+import Futhark.CodeGen.ImpGen qualified as ImpGen
 import Futhark.IR.SeqMem
 import Futhark.MonadFreshNames
 
diff --git a/src/Futhark/CodeGen/OpenCL/Heuristics.hs b/src/Futhark/CodeGen/OpenCL/Heuristics.hs
--- a/src/Futhark/CodeGen/OpenCL/Heuristics.hs
+++ b/src/Futhark/CodeGen/OpenCL/Heuristics.hs
@@ -31,7 +31,7 @@
 newtype DeviceInfo = DeviceInfo String
 
 instance Pretty DeviceInfo where
-  ppr (DeviceInfo s) = text "device_info" <> parens (ppr s)
+  pretty (DeviceInfo s) = "device_info" <> parens (pretty s)
 
 -- | A size that can be assigned a default.
 data WhichSize = LockstepWidth | NumGroups | GroupSize | TileSize | RegTileSize | Threshold
diff --git a/src/Futhark/CodeGen/RTS/C.hs b/src/Futhark/CodeGen/RTS/C.hs
--- a/src/Futhark/CodeGen/RTS/C.hs
+++ b/src/Futhark/CodeGen/RTS/C.hs
@@ -26,7 +26,7 @@
 where
 
 import Data.FileEmbed
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- We mark everything here NOINLINE so that the dependent modules
 -- don't have to be recompiled just because we change the RTS files.
diff --git a/src/Futhark/CodeGen/RTS/JavaScript.hs b/src/Futhark/CodeGen/RTS/JavaScript.hs
--- a/src/Futhark/CodeGen/RTS/JavaScript.hs
+++ b/src/Futhark/CodeGen/RTS/JavaScript.hs
@@ -9,7 +9,7 @@
 where
 
 import Data.FileEmbed
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- | @rts/javascript/server.js@
 serverJs :: T.Text
diff --git a/src/Futhark/CodeGen/RTS/Python.hs b/src/Futhark/CodeGen/RTS/Python.hs
--- a/src/Futhark/CodeGen/RTS/Python.hs
+++ b/src/Futhark/CodeGen/RTS/Python.hs
@@ -13,7 +13,7 @@
 where
 
 import Data.FileEmbed
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- | @rts/python/memory.py@
 memoryPy :: T.Text
diff --git a/src/Futhark/Compiler.hs b/src/Futhark/Compiler.hs
--- a/src/Futhark/Compiler.hs
+++ b/src/Futhark/Compiler.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
 
 -- | High-level API for invoking the Futhark compiler.
@@ -8,7 +6,7 @@
     runCompilerOnProgram,
     dumpError,
     handleWarnings,
-    pprProgErrors,
+    prettyProgErrors,
     module Futhark.Compiler.Program,
     module Futhark.Compiler.Config,
     readProgramFile,
@@ -23,22 +21,21 @@
 import Control.Monad.Except
 import Data.Bifunctor (first)
 import Data.List (sortOn)
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Loc (Loc (..), posCoff, posFile)
-import qualified Data.Text.IO as T
-import qualified Futhark.Analysis.Alias as Alias
+import Data.Text.IO qualified as T
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Compiler.Config
 import Futhark.Compiler.Program
 import Futhark.IR
-import qualified Futhark.IR.SOACS as I
-import qualified Futhark.IR.TypeCheck as I
+import Futhark.IR.SOACS qualified as I
+import Futhark.IR.TypeCheck qualified as I
 import Futhark.Internalise
 import Futhark.MonadFreshNames
 import Futhark.Pipeline
-import Futhark.Util.Console (inRed, inYellow)
 import Futhark.Util.Log
-import Futhark.Util.Pretty (Doc, line, ppr, prettyText, punctuate, stack, text, (</>))
-import qualified Language.Futhark as E
+import Futhark.Util.Pretty
+import Language.Futhark qualified as E
 import Language.Futhark.Semantic (includeToString)
 import Language.Futhark.Warnings
 import System.Exit (ExitCode (..), exitWith)
@@ -51,7 +48,7 @@
 dumpError config err =
   case err of
     ExternalError s -> do
-      T.hPutStrLn stderr $ prettyText s
+      hPutDoc stderr s
       T.hPutStrLn stderr ""
       T.hPutStrLn stderr "If you find this error message confusing, uninformative, or wrong, please open an issue:"
       T.hPutStrLn stderr "  https://github.com/diku-dk/futhark/issues"
@@ -130,25 +127,25 @@
 typeCheckInternalProgram :: I.Prog I.SOACS -> FutharkM ()
 typeCheckInternalProgram prog =
   case I.checkProg prog' of
-    Left err -> internalErrorS ("After internalisation:\n" ++ show err) (ppr prog')
+    Left err -> internalErrorS ("After internalisation:\n" ++ show err) (pretty prog')
     Right () -> pure ()
   where
     prog' = Alias.aliasAnalysis prog
 
 -- | Prettyprint program errors as suitable for showing on a text console.
-pprProgErrors :: NE.NonEmpty ProgError -> Doc
-pprProgErrors = stack . punctuate line . map onError . sortOn (rep . locOf) . NE.toList
+prettyProgErrors :: NE.NonEmpty ProgError -> Doc AnsiStyle
+prettyProgErrors = stack . punctuate line . map onError . sortOn (rep . locOf) . NE.toList
   where
     rep NoLoc = ("", 0)
     rep (Loc p _) = (posFile p, posCoff p)
     onError (ProgError NoLoc msg) =
-      msg
+      unAnnotate msg
     onError (ProgError loc msg) =
-      text (inRed $ "Error at " <> locStr (srclocOf loc) <> ":") </> msg
+      annotate (color Red) ("Error at " <> pretty (locText (srclocOf loc))) <> ":" </> unAnnotate msg
     onError (ProgWarning NoLoc msg) =
-      msg
+      unAnnotate msg
     onError (ProgWarning loc msg) =
-      text (inYellow $ "Warning at " <> locStr (srclocOf loc) <> ":") </> msg
+      annotate (color Yellow) $ "Warning at " <> pretty (locText (srclocOf loc)) <> ":" </> unAnnotate msg
 
 -- | Throw an exception formatted with 'pprProgErrors' if there's
 -- an error.
@@ -157,7 +154,7 @@
   Either (NE.NonEmpty ProgError) a ->
   m a
 throwOnProgError =
-  either (externalError . pprProgErrors) pure
+  either (externalError . prettyProgErrors) pure
 
 -- | Read and type-check a Futhark program, comprising a single file,
 -- including all imports.
@@ -215,7 +212,7 @@
   (ws, a) <- m
 
   when (futharkWarn config && anyWarnings ws) $ do
-    liftIO $ hPutStrLn stderr $ pretty ws
+    liftIO $ hPutDoc stderr $ prettyWarnings ws
     when (futharkWerror config) $
       externalErrorS "Treating above warnings as errors due to --Werror."
 
diff --git a/src/Futhark/Compiler/Program.hs b/src/Futhark/Compiler/Program.hs
--- a/src/Futhark/Compiler/Program.hs
+++ b/src/Futhark/Compiler/Program.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
 
 -- | Low-level compilation parts.  Look at "Futhark.Compiler" for a
 -- more high-level API.
@@ -10,6 +8,7 @@
     Imports,
     FileModule (..),
     E.Warnings,
+    prettyWarnings,
     ProgError (..),
     LoadedProg (lpNameSource),
     noLoadedProg,
@@ -36,26 +35,26 @@
 import Control.Monad.State (execStateT, gets, modify)
 import Data.Bifunctor (first)
 import Data.List (intercalate, sort)
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Loc (Loc (..), Located, locOf)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe (mapMaybe)
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Time.Clock (UTCTime, getCurrentTime)
 import Futhark.FreshNames
 import Futhark.Util (interactWithFileSafely, nubOrd, startupTime)
-import Futhark.Util.Pretty (Doc, align, ppr, text)
-import qualified Language.Futhark as E
+import Futhark.Util.Pretty (Doc, align, pretty)
+import Language.Futhark qualified as E
 import Language.Futhark.Parser (SyntaxError (..), parseFuthark)
 import Language.Futhark.Prelude
 import Language.Futhark.Prop (isBuiltin)
 import Language.Futhark.Semantic
-import qualified Language.Futhark.TypeChecker as E
+import Language.Futhark.TypeChecker qualified as E
 import Language.Futhark.Warnings
 import System.Directory (getModificationTime)
 import System.FilePath (normalise)
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 
 data LoadedFile fm = LoadedFile
   { lfPath :: FilePath,
@@ -69,10 +68,10 @@
 -- | Note that the location may be 'NoLoc'.  This essentially only
 -- happens when the problem is that a root file cannot be found.
 data ProgError
-  = ProgError Loc Doc
+  = ProgError Loc (Doc ())
   | -- | Not actually an error, but we want them reported
     -- with errors.
-    ProgWarning Loc Doc
+    ProgWarning Loc (Doc ())
 
 type WithErrors = Either (NE.NonEmpty ProgError)
 
@@ -80,10 +79,10 @@
   locOf (ProgError l _) = l
   locOf (ProgWarning l _) = l
 
--- | A mapping from absolute pathnames to text representing a virtual
+-- | A mapping from absolute pathnames to pretty representing a virtual
 -- file system.  Before loading a file from the file system, this
 -- mapping is consulted.  If the desired pathname has an entry here,
--- the corresponding text is used instead of loading the file from
+-- the corresponding pretty is used instead of loading the file from
 -- disk.
 type VFS = M.Map FilePath T.Text
 
@@ -107,7 +106,7 @@
     spelunk steps (include, mvar)
       | include `elem` steps = do
           let problem =
-                ProgError (locOf include) . text $
+                ProgError (locOf include) . pretty $
                   "Import cycle: "
                     <> intercalate
                       " -> "
@@ -162,11 +161,11 @@
     (Just (Right (s, mod_time)), _) ->
       pure $ Right $ loaded filepath s mod_time
     (Just (Left e), _) ->
-      pure $ Left $ ProgError (locOf include) $ text e
+      pure $ Left $ ProgError (locOf include) $ pretty e
     (Nothing, Just s) ->
       pure $ Right $ loaded prelude_str s startupTime
     (Nothing, Nothing) ->
-      pure $ Left $ ProgError (locOf include) $ text not_found
+      pure $ Left $ ProgError (locOf include) $ pretty not_found
   where
     prelude_str = "/" Posix.</> includeToString include Posix.<.> "fut"
 
@@ -179,13 +178,13 @@
         }
 
     not_found =
-      "Could not find import " <> E.quote (includeToString include) <> "."
+      "Could not find import " <> E.quote (includeToText include) <> "."
 
 handleFile :: ReaderState -> VFS -> LoadedFile T.Text -> IO UncheckedImport
 handleFile state_mvar vfs (LoadedFile file_name import_name file_contents mod_time) = do
   case parseFuthark file_name file_contents of
     Left (SyntaxError loc err) ->
-      pure . UncheckedImport . Left . NE.singleton $ ProgError loc $ text err
+      pure . UncheckedImport . Left . NE.singleton $ ProgError loc $ pretty err
     Right prog -> do
       let imports = map (uncurry (mkImportFrom import_name)) $ E.progImports prog
       mvars <-
@@ -245,12 +244,12 @@
                 Just (Left e) ->
                   pure . UncheckedImport . Left . NE.singleton $
                     ProgError NoLoc $
-                      text $
+                      pretty $
                         show e
                 Nothing ->
                   pure . UncheckedImport . Left . NE.singleton $
                     ProgError NoLoc $
-                      text $
+                      pretty $
                         fp <> ": file not found."
             pure (M.insert include prog_mvar state, (include, prog_mvar))
       where
@@ -288,14 +287,14 @@
             | otherwise = prependRoots roots prog
       case E.checkProg (asImports imports) src import_name prog' of
         (prog_ws, Left (E.TypeError loc notes msg)) -> do
-          let err' = msg <> ppr notes
+          let err' = msg <> pretty notes
               warningToError (wloc, wmsg) = ProgWarning (locOf wloc) wmsg
           Left $
             ProgError (locOf loc) err'
               NE.:| map warningToError (listWarnings prog_ws)
         (prog_ws, Right (m, src')) ->
           let warnHole (loc, t) =
-                singleWarning (E.srclocOf loc) $ "Hole of type: " <> align (ppr t)
+                singleWarning (E.srclocOf loc) $ "Hole of type: " <> align (pretty t)
               prog_ws' = prog_ws <> foldMap warnHole (E.progHoles (fileProg m))
            in Right
                 ( imports ++ [LoadedFile path import_name (CheckedFile src prog_ws' m) mod_time],
diff --git a/src/Futhark/Construct.hs b/src/Futhark/Construct.hs
--- a/src/Futhark/Construct.hs
+++ b/src/Futhark/Construct.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | = Constructing Futhark ASTs
@@ -124,7 +121,7 @@
 import Control.Monad.Identity
 import Control.Monad.State
 import Data.List (foldl', sortOn, transpose)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.Builder
 import Futhark.IR
 import Futhark.Util (maybeNth)
@@ -155,7 +152,7 @@
   letBindNames vs e
   case vs of
     [v] -> pure v
-    _ -> error $ "letExp: tuple-typed expression given:\n" ++ pretty e
+    _ -> error $ "letExp: tuple-typed expression given:\n" ++ prettyString e
 
 -- | Like 'letExp', but the 'VName' and 'Slice' denote an array that
 -- is 'Update'd with the result of the expression.  The name of the
@@ -346,7 +343,7 @@
     Prim (IntType int_t) ->
       pure $ BasicOp $ UnOp (SSignum int_t) e'
     _ ->
-      error $ "eSignum: operand " ++ pretty e ++ " has invalid type."
+      error $ "eSignum: operand " ++ prettyString e ++ " has invalid type."
 
 -- | Construct a 'Copy' expression.
 eCopy ::
@@ -448,7 +445,7 @@
   where
     s = case e of
       Var v -> baseString v
-      _ -> "to_" ++ pretty to_it
+      _ -> "to_" ++ prettyString to_it
 
 -- | Apply a binary operator to several subexpressions.  A left-fold.
 foldBinOp ::
@@ -648,7 +645,7 @@
   where
     instantiate x =
       case maybeNth x names of
-        Nothing -> error $ "instantiateShapes': " ++ pretty names ++ ", " ++ show x
+        Nothing -> error $ "instantiateShapes': " ++ prettyString names ++ ", " ++ show x
         Just name -> pure $ Var name
 
 -- | Remove existentials by imposing sizes from another type where
diff --git a/src/Futhark/Doc/Generator.hs b/src/Futhark/Doc/Generator.hs
--- a/src/Futhark/Doc/Generator.hs
+++ b/src/Futhark/Doc/Generator.hs
@@ -1,44 +1,42 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | The core logic of @futhark doc@.
 module Futhark.Doc.Generator (renderFiles) where
 
-import qualified CMarkGFM as GFM
+import CMarkGFM qualified as GFM
 import Control.Arrow ((***))
 import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.Writer hiding (Sum)
 import Data.Char (isAlpha, isSpace, toUpper)
 import Data.List (find, groupBy, inits, intersperse, isPrefixOf, partition, sort, sortOn, tails)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Data.Ord
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Data.String (fromString)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Version
-import Futhark.Util.Pretty (Doc, ppr)
+import Futhark.Util.Pretty (Doc, docText, pretty)
 import Futhark.Version
 import Language.Futhark
 import Language.Futhark.Semantic
 import Language.Futhark.TypeChecker.Monad hiding (warn)
 import System.FilePath (makeRelative, splitPath, (-<.>), (<.>), (</>))
 import Text.Blaze.Html5 (AttributeValue, Html, toHtml, (!))
-import qualified Text.Blaze.Html5 as H
-import qualified Text.Blaze.Html5.Attributes as A
+import Text.Blaze.Html5 qualified as H
+import Text.Blaze.Html5.Attributes qualified as A
 import Prelude hiding (abs)
 
-docToHtml :: Doc -> Html
-docToHtml = toHtml . pretty
+docToHtml :: Doc a -> Html
+docToHtml = toHtml . docText
 
 primTypeHtml :: PrimType -> Html
-primTypeHtml = docToHtml . ppr
+primTypeHtml = docToHtml . pretty
 
 prettyU :: Uniqueness -> Html
-prettyU = docToHtml . ppr
+prettyU = docToHtml . pretty
 
 renderName :: Name -> Html
-renderName name = docToHtml (ppr name)
+renderName name = docToHtml (pretty name)
 
 joinBy :: Html -> [Html] -> Html
 joinBy _ [] = mempty
@@ -86,7 +84,7 @@
 -- can generate an index.
 type Documented = M.Map VName IndexWhat
 
-warn :: SrcLoc -> Doc -> DocM ()
+warn :: SrcLoc -> Doc () -> DocM ()
 warn loc s = lift $ lift $ tell $ singleWarning loc s
 
 document :: VName -> IndexWhat -> DocM ()
@@ -378,7 +376,7 @@
         Just $
           pure $
             fullRow $
-              keyword "open" <> fromString (" <" <> pretty x <> ">")
+              keyword "open" <> fromString (" <" <> prettyString x <> ">")
   LocalDec (SigDec s) _
     | sigName s `S.member` visible ->
         synopsisModType (keyword "local" <> " ") s
@@ -712,13 +710,13 @@
 typeParamHtml (TypeParamDim name _) =
   brackets $ vnameHtml name
 typeParamHtml (TypeParamType l name _) =
-  "'" <> fromString (pretty l) <> vnameHtml name
+  "'" <> fromString (prettyString l) <> vnameHtml name
 
 typeAbbrevHtml :: Liftedness -> Html -> [TypeParam] -> Html
 typeAbbrevHtml l name params =
   what <> name <> mconcat (map ((" " <>) . typeParamHtml) params)
   where
-    what = keyword $ "type" ++ pretty l ++ " "
+    what = keyword $ "type" ++ prettyString l ++ " "
 
 docHtml :: Maybe DocComment -> DocM Html
 docHtml (Just (DocComment doc loc)) =
diff --git a/src/Futhark/Error.hs b/src/Futhark/Error.hs
--- a/src/Futhark/Error.hs
+++ b/src/Futhark/Error.hs
@@ -1,8 +1,7 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Futhark error definitions.
 module Futhark.Error
   ( CompilerError (..),
+    prettyCompilerError,
     ErrorClass (..),
     externalError,
     externalErrorS,
@@ -17,8 +16,9 @@
 
 import Control.Exception
 import Control.Monad.Error.Class
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Util.Pretty
+import Prettyprinter.Render.Text (renderStrict)
 
 -- | There are two classes of internal errors: actual bugs, and
 -- implementation limitations.  The latter are already known and need
@@ -32,27 +32,30 @@
 data CompilerError
   = -- | An error that happened due to something the user did, such as
     -- provide incorrect code or options.
-    ExternalError Doc
-  | -- | An internal compiler error.  The second text is extra data
+    ExternalError (Doc AnsiStyle)
+  | -- | An internal compiler error.  The second pretty is extra data
     -- for debugging, which can be written to a file.
     InternalError T.Text T.Text ErrorClass
 
-instance Show CompilerError where
-  show (ExternalError s) = pretty s
-  show (InternalError s _ _) = T.unpack s
+-- | Print an error intended for human consumption.
+prettyCompilerError :: CompilerError -> Doc AnsiStyle
+prettyCompilerError (ExternalError e) = e
+prettyCompilerError (InternalError s _ _) = pretty s
 
 -- | Raise an 'ExternalError' based on a prettyprinting result.
-externalError :: MonadError CompilerError m => Doc -> m a
+externalError :: MonadError CompilerError m => Doc AnsiStyle -> m a
 externalError = throwError . ExternalError
 
 -- | Raise an 'ExternalError' based on a string.
 externalErrorS :: MonadError CompilerError m => String -> m a
-externalErrorS = externalError . text
+externalErrorS = externalError . pretty
 
 -- | Raise an v'InternalError' based on a prettyprinting result.
-internalErrorS :: MonadError CompilerError m => String -> Doc -> m a
+internalErrorS :: MonadError CompilerError m => String -> Doc AnsiStyle -> m a
 internalErrorS s d =
-  throwError $ InternalError (T.pack s) (prettyText d) CompilerBug
+  throwError $ InternalError (T.pack s) (p d) CompilerBug
+  where
+    p = renderStrict . layoutSmart defaultLayoutOptions
 
 -- | An error that is not the users fault, but a bug (or limitation)
 -- in the compiler.  Compiler passes should only ever report this
diff --git a/src/Futhark/FreshNames.hs b/src/Futhark/FreshNames.hs
--- a/src/Futhark/FreshNames.hs
+++ b/src/Futhark/FreshNames.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE DeriveLift #-}
-
 -- | This module provides facilities for generating unique names.
 module Futhark.FreshNames
   ( VNameSource,
diff --git a/src/Futhark/IR/Aliases.hs b/src/Futhark/IR/Aliases.hs
--- a/src/Futhark/IR/Aliases.hs
+++ b/src/Futhark/IR/Aliases.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -53,7 +49,7 @@
 
 import Control.Monad.Identity
 import Control.Monad.Reader
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Analysis.Rephrase
 import Futhark.Builder
@@ -64,7 +60,7 @@
 import Futhark.IR.Traversals
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty qualified as PP
 
 -- | The rep for the basic representation.
 data Aliases rep
@@ -96,7 +92,7 @@
   freeIn' = const mempty
 
 instance PP.Pretty AliasDec where
-  ppr = PP.braces . PP.commasep . map PP.ppr . namesToList . unAliases
+  pretty = PP.braces . PP.commasep . map PP.pretty . namesToList . unAliases
 
 -- | The aliases of the let-bound variable.
 type VarAliases = AliasDec
@@ -162,23 +158,23 @@
         als ->
           Just $
             PP.oneLine $
-              PP.text "-- Consumes " <> PP.commasep (map PP.ppr als)
+              "-- Consumes " <> PP.commasep (map PP.pretty als)
 
-maybeComment :: [PP.Doc] -> Maybe PP.Doc
+maybeComment :: [PP.Doc a] -> Maybe (PP.Doc a)
 maybeComment [] = Nothing
-maybeComment cs = Just $ PP.folddoc (PP.</>) cs
+maybeComment cs = Just $ PP.stack cs
 
-resultAliasComment :: PP.Pretty a => a -> Names -> Maybe PP.Doc
+resultAliasComment :: PP.Pretty a => a -> Names -> Maybe (PP.Doc ann)
 resultAliasComment name als =
   case namesToList als of
     [] -> Nothing
     als' ->
       Just $
         PP.oneLine $
-          PP.text "-- Result for "
-            <> PP.ppr name
-            <> PP.text " aliases "
-            <> PP.commasep (map PP.ppr als')
+          "-- Result for "
+            <> PP.pretty name
+            <> " aliases "
+            <> PP.commasep (map PP.pretty als')
 
 removeAliases :: CanBeAliased (Op rep) => Rephraser Identity (Aliases rep) rep
 removeAliases =
diff --git a/src/Futhark/IR/GPU.hs b/src/Futhark/IR/GPU.hs
--- a/src/Futhark/IR/GPU.hs
+++ b/src/Futhark/IR/GPU.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | A representation with flat parallelism via GPU-oriented kernels.
@@ -25,7 +24,7 @@
 import Futhark.IR.SOACS.SOAC hiding (HistOp (..))
 import Futhark.IR.Syntax
 import Futhark.IR.Traversals
-import qualified Futhark.IR.TypeCheck as TC
+import Futhark.IR.TypeCheck qualified as TC
 
 -- | The phantom data type for the kernels representation.
 data GPU
diff --git a/src/Futhark/IR/GPU/Op.hs b/src/Futhark/IR/GPU/Op.hs
--- a/src/Futhark/IR/GPU/Op.hs
+++ b/src/Futhark/IR/GPU/Op.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -26,28 +20,29 @@
 where
 
 import Control.Monad
-import qualified Data.Sequence as SQ
-import qualified Futhark.Analysis.Alias as Alias
+import Data.Sequence qualified as SQ
+import Data.Text qualified as T
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.Metrics
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.IR
 import Futhark.IR.Aliases (Aliases, removeBodyAliases)
 import Futhark.IR.GPU.Sizes
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SegOp
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
 import Futhark.Util.Pretty
   ( commasep,
     parens,
-    ppr,
-    text,
+    ppTuple',
+    pretty,
     (<+>),
   )
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty qualified as PP
 
 -- | At which level the *body* of a t'SegOp' executes.
 data SegLevel
@@ -64,11 +59,11 @@
   deriving (Eq, Ord, Show)
 
 instance PP.Pretty SegLevel where
-  ppr lvl =
+  pretty lvl =
     PP.parens
       ( lvl' <> PP.semi
-          <+> text "#groups=" <> ppr (segNumGroups lvl) <> PP.semi
-          <+> text "groupsize=" <> ppr (segGroupSize lvl) <> virt
+          <+> "#groups=" <> pretty (segNumGroups lvl) <> PP.semi
+          <+> "groupsize=" <> pretty (segGroupSize lvl) <> virt
       )
     where
       lvl' = case lvl of
@@ -76,8 +71,8 @@
         SegGroup {} -> "group"
       virt = case segVirt lvl of
         SegNoVirt -> mempty
-        SegNoVirtFull dims -> PP.semi <+> text "full" <+> ppr (segSeqDims dims)
-        SegVirt -> PP.semi <+> text "virtualise"
+        SegNoVirtFull dims -> PP.semi <+> "full" <+> pretty (segSeqDims dims)
+        SegVirt -> PP.semi <+> "virtualise"
 
 instance Engine.Simplifiable SegLevel where
   simplify (SegThread num_groups group_size virt) =
@@ -164,16 +159,16 @@
   freeIn' _ = mempty
 
 instance PP.Pretty SizeOp where
-  ppr (GetSize name size_class) =
-    text "get_size" <> parens (commasep [ppr name, ppr size_class])
-  ppr (GetSizeMax size_class) =
-    text "get_size_max" <> parens (commasep [ppr size_class])
-  ppr (CmpSizeLe name size_class x) =
-    text "cmp_size" <> parens (commasep [ppr name, ppr size_class])
-      <+> text "<="
-      <+> ppr x
-  ppr (CalcNumGroups w max_num_groups group_size) =
-    text "calc_num_groups" <> parens (commasep [ppr w, ppr max_num_groups, ppr group_size])
+  pretty (GetSize name size_class) =
+    "get_size" <> parens (commasep [pretty name, pretty size_class])
+  pretty (GetSizeMax size_class) =
+    "get_size_max" <> parens (commasep [pretty size_class])
+  pretty (CmpSizeLe name size_class x) =
+    "cmp_size" <> parens (commasep [pretty name, pretty size_class])
+      <+> "<="
+      <+> pretty x
+  pretty (CalcNumGroups w max_num_groups group_size) =
+    "calc_num_groups" <> parens (commasep [pretty w, pretty max_num_groups, pretty group_size])
 
 instance OpMetrics SizeOp where
   opMetrics GetSize {} = seen "GetSize"
@@ -298,11 +293,11 @@
   indexOp _ _ _ _ = Nothing
 
 instance (PrettyRep rep, PP.Pretty op) => PP.Pretty (HostOp rep op) where
-  ppr (SegOp op) = ppr op
-  ppr (OtherOp op) = ppr op
-  ppr (SizeOp op) = ppr op
-  ppr (GPUBody ts body) =
-    "gpu" <+> PP.colon <+> ppTuple' ts <+> PP.nestedBlock "{" "}" (ppr body)
+  pretty (SegOp op) = pretty op
+  pretty (OtherOp op) = pretty op
+  pretty (SizeOp op) = pretty op
+  pretty (GPUBody ts body) =
+    "gpu" <+> PP.colon <+> ppTuple' (map pretty ts) <+> PP.nestedBlock "{" "}" (pretty body)
 
 instance (OpMetrics (Op rep), OpMetrics op) => OpMetrics (HostOp rep op) where
   opMetrics (SegOp op) = opMetrics op
@@ -321,7 +316,7 @@
 checkSegLevel (Just SegThread {}) _ =
   TC.bad $ TC.TypeError "SegOps cannot occur when already at thread level."
 checkSegLevel (Just x) y
-  | x == y = TC.bad $ TC.TypeError $ "Already at at level " ++ pretty x
+  | x == y = TC.bad $ TC.TypeError $ "Already at at level " <> prettyText x
   | segNumGroups x /= segNumGroups y || segGroupSize x /= segGroupSize y =
       TC.bad $ TC.TypeError "Physical layout for SegLevel does not match parent SegLevel."
   | otherwise =
@@ -348,7 +343,7 @@
     extendedScope
       (traverse subExpResType (bodyResult body))
       (scopeOf (bodyStms body))
-  unless (body_ts == ts) . TC.bad . TC.TypeError . unlines $
-    [ "Expected type: " ++ prettyTuple ts,
-      "Got body type: " ++ prettyTuple body_ts
+  unless (body_ts == ts) . TC.bad . TC.TypeError . T.unlines $
+    [ "Expected type: " <> prettyTuple ts,
+      "Got body type: " <> prettyTuple body_ts
     ]
diff --git a/src/Futhark/IR/GPU/Simplify.hs b/src/Futhark/IR/GPU/Simplify.hs
--- a/src/Futhark/IR/GPU/Simplify.hs
+++ b/src/Futhark/IR/GPU/Simplify.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -15,13 +11,13 @@
   )
 where
 
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR.GPU
-import qualified Futhark.IR.SOACS.Simplify as SOAC
+import Futhark.IR.SOACS.Simplify qualified as SOAC
 import Futhark.MonadFreshNames
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Optimise.Simplify.Rule
 import Futhark.Optimise.Simplify.Rules
diff --git a/src/Futhark/IR/GPU/Sizes.hs b/src/Futhark/IR/GPU/Sizes.hs
--- a/src/Futhark/IR/GPU/Sizes.hs
+++ b/src/Futhark/IR/GPU/Sizes.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | In the context of this module, a "size" is any kind of tunable
 -- (run-time) constant.
 module Futhark.IR.GPU.Sizes
@@ -45,19 +41,19 @@
   deriving (Eq, Ord, Show)
 
 instance Pretty SizeClass where
-  ppr (SizeThreshold path def) =
-    "threshold" <> parens (def' <> comma <+> spread (map pStep path))
+  pretty (SizeThreshold path def) =
+    "threshold" <> parens (def' <> comma <+> hsep (map pStep path))
     where
-      pStep (v, True) = ppr v
-      pStep (v, False) = "!" <> ppr v
-      def' = maybe "def" ppr def
-  ppr SizeGroup = text "group_size"
-  ppr SizeNumGroups = text "num_groups"
-  ppr SizeTile = text "tile_size"
-  ppr SizeRegTile = text "reg_tile_size"
-  ppr SizeLocalMemory = text "local_memory"
-  ppr (SizeBespoke k def) =
-    text "bespoke" <> parens (ppr k <> comma <+> ppr def)
+      pStep (v, True) = pretty v
+      pStep (v, False) = "!" <> pretty v
+      def' = maybe "def" pretty def
+  pretty SizeGroup = "group_size"
+  pretty SizeNumGroups = "num_groups"
+  pretty SizeTile = "tile_size"
+  pretty SizeRegTile = "reg_tile_size"
+  pretty SizeLocalMemory = "local_memory"
+  pretty (SizeBespoke k def) =
+    "bespoke" <> parens (pretty k <> comma <+> pretty def)
 
 -- | The default value for the size.  If 'Nothing', that means the backend gets to decide.
 sizeDefault :: SizeClass -> Maybe Int64
diff --git a/src/Futhark/IR/GPUMem.hs b/src/Futhark/IR/GPUMem.hs
--- a/src/Futhark/IR/GPUMem.hs
+++ b/src/Futhark/IR/GPUMem.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.IR.GPUMem
@@ -19,14 +15,14 @@
 where
 
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR.GPU.Op
 import Futhark.IR.GPU.Simplify (simplifyKernelOp)
 import Futhark.IR.Mem
 import Futhark.IR.Mem.Simplify
-import qualified Futhark.IR.TypeCheck as TC
+import Futhark.IR.TypeCheck qualified as TC
 import Futhark.MonadFreshNames
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Pass
 import Futhark.Pass.ExplicitAllocations (BuilderOps (..), mkLetNamesB', mkLetNamesB'')
 
diff --git a/src/Futhark/IR/MC.hs b/src/Futhark/IR/MC.hs
--- a/src/Futhark/IR/MC.hs
+++ b/src/Futhark/IR/MC.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | A representation for multicore CPU parallelism.
@@ -26,13 +24,13 @@
 import Futhark.IR.Pretty
 import Futhark.IR.Prop
 import Futhark.IR.SOACS.SOAC hiding (HistOp (..))
-import qualified Futhark.IR.SOACS.Simplify as SOAC
+import Futhark.IR.SOACS.Simplify qualified as SOAC
 import Futhark.IR.SegOp
 import Futhark.IR.Syntax
 import Futhark.IR.Traversals
-import qualified Futhark.IR.TypeCheck as TypeCheck
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TypeCheck
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rules
 import Futhark.Pass
 
diff --git a/src/Futhark/IR/MC/Op.hs b/src/Futhark/IR/MC/Op.hs
--- a/src/Futhark/IR/MC/Op.hs
+++ b/src/Futhark/IR/MC/Op.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -18,21 +16,21 @@
 
 import Data.Bifunctor (first)
 import Futhark.Analysis.Metrics
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.IR
 import Futhark.IR.Aliases (Aliases)
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SegOp
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
 import Futhark.Util.Pretty
   ( Pretty,
     nestedBlock,
-    ppr,
+    pretty,
     (<+>),
     (</>),
   )
@@ -129,13 +127,13 @@
   indexOp vtable k (OtherOp op) is = ST.indexOp vtable k op is
 
 instance (PrettyRep rep, Pretty op) => Pretty (MCOp rep op) where
-  ppr (ParOp Nothing op) = ppr op
-  ppr (ParOp (Just par_op) op) =
+  pretty (ParOp Nothing op) = pretty op
+  pretty (ParOp (Just par_op) op) =
     "par"
-      <+> nestedBlock "{" "}" (ppr par_op)
+      <+> nestedBlock "{" "}" (pretty par_op)
       </> "seq"
-      <+> nestedBlock "{" "}" (ppr op)
-  ppr (OtherOp op) = ppr op
+      <+> nestedBlock "{" "}" (pretty op)
+  pretty (OtherOp op) = pretty op
 
 instance (OpMetrics (Op rep), OpMetrics op) => OpMetrics (MCOp rep op) where
   opMetrics (ParOp par_op op) = opMetrics par_op >> opMetrics op
diff --git a/src/Futhark/IR/MCMem.hs b/src/Futhark/IR/MCMem.hs
--- a/src/Futhark/IR/MCMem.hs
+++ b/src/Futhark/IR/MCMem.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.IR.MCMem
@@ -22,8 +18,8 @@
 import Futhark.IR.Mem
 import Futhark.IR.Mem.Simplify
 import Futhark.IR.SegOp
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Pass
 import Futhark.Pass.ExplicitAllocations (BuilderOps (..), mkLetNamesB', mkLetNamesB'')
 
diff --git a/src/Futhark/IR/Mem.hs b/src/Futhark/IR/Mem.hs
--- a/src/Futhark/IR/Mem.hs
+++ b/src/Futhark/IR/Mem.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -111,32 +106,33 @@
 import Data.Foldable (traverse_)
 import Data.Function ((&))
 import Data.List (elemIndex, find)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
+import Data.Text qualified as T
 import Futhark.Analysis.Metrics
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.Analysis.PrimExp.Simplify
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.IR.Aliases
   ( Aliases,
     removeExpAliases,
     removePatAliases,
     removeScopeAliases,
   )
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.IR.Pretty
 import Futhark.IR.Prop
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.Syntax
 import Futhark.IR.Traversals
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
 import Futhark.Util
-import Futhark.Util.Pretty (indent, ppr, text, (<+>), (</>))
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty (docText, indent, ppTuple', pretty, (<+>), (</>))
+import Futhark.Util.Pretty qualified as PP
 import Prelude hiding (id, (.))
 
 type LetDecMem = MemInfo SubExp NoUniqueness MemBind
@@ -224,9 +220,9 @@
   substituteNames subst (Inner k) = Inner $ substituteNames subst k
 
 instance PP.Pretty inner => PP.Pretty (MemOp inner) where
-  ppr (Alloc e DefaultSpace) = PP.text "alloc" <> PP.apply [PP.ppr e]
-  ppr (Alloc e s) = PP.text "alloc" <> PP.apply [PP.ppr e, PP.ppr s]
-  ppr (Inner k) = PP.ppr k
+  pretty (Alloc e DefaultSpace) = "alloc" <> PP.apply [PP.pretty e]
+  pretty (Alloc e s) = "alloc" <> PP.apply [PP.pretty e, PP.pretty s]
+  pretty (Inner k) = PP.pretty k
 
 instance OpMetrics inner => OpMetrics (MemOp inner) where
   opMetrics Alloc {} = seen "Alloc"
@@ -377,13 +373,13 @@
   ) =>
   PP.Pretty (MemInfo d u ret)
   where
-  ppr (MemPrim bt) = PP.ppr bt
-  ppr (MemMem DefaultSpace) = PP.text "mem"
-  ppr (MemMem s) = PP.text "mem" <> PP.ppr s
-  ppr (MemArray bt shape u ret) =
-    PP.ppr (Array bt shape u) <+> PP.text "@" <+> PP.ppr ret
-  ppr (MemAcc acc ispace ts u) =
-    PP.ppr u <> PP.ppr (Acc acc ispace ts NoUniqueness :: Type)
+  pretty (MemPrim bt) = PP.pretty bt
+  pretty (MemMem DefaultSpace) = "mem"
+  pretty (MemMem s) = "mem" <> PP.pretty s
+  pretty (MemArray bt shape u ret) =
+    PP.pretty (Array bt shape u) <+> "@" <+> PP.pretty ret
+  pretty (MemAcc acc ispace ts u) =
+    PP.pretty u <> PP.pretty (Acc acc ispace ts NoUniqueness :: Type)
 
 -- | Memory information for an array bound somewhere in the program.
 data MemBind
@@ -406,8 +402,8 @@
     ArrayIn (substituteNames substs ident) (substituteNames substs ixfun)
 
 instance PP.Pretty MemBind where
-  ppr (ArrayIn mem ixfun) =
-    PP.ppr mem <+> "->" PP.</> PP.ppr ixfun
+  pretty (ArrayIn mem ixfun) =
+    PP.pretty mem <+> "->" PP.</> PP.pretty ixfun
 
 instance FreeIn MemBind where
   freeIn' (ArrayIn mem ixfun) = freeIn' mem <> freeIn' ixfun
@@ -476,10 +472,10 @@
     ctx' = M.map leafExp $ M.fromList $ zip (map Free ctx) [0 ..]
 
 instance PP.Pretty MemReturn where
-  ppr (ReturnsInBlock v ixfun) =
-    PP.parens $ ppr v <+> "->" PP.</> PP.ppr ixfun
-  ppr (ReturnsNewBlock space i ixfun) =
-    "?" <> ppr i <> PP.ppr space <+> "->" PP.</> PP.ppr ixfun
+  pretty (ReturnsInBlock v ixfun) =
+    PP.parens $ pretty v <+> "->" PP.</> PP.pretty ixfun
+  pretty (ReturnsNewBlock space i ixfun) =
+    "?" <> pretty i <> PP.pretty space <+> "->" PP.</> PP.pretty ixfun
 
 instance FreeIn MemReturn where
   freeIn' (ReturnsInBlock v ixfun) = freeIn' v <> freeIn' ixfun
@@ -587,12 +583,11 @@
           | IxFun.isLinear ixfun ->
               pure ()
           | otherwise ->
-              TC.bad $
-                TC.TypeError $
-                  "Array "
-                    ++ pretty v
-                    ++ " returned by function, but has nontrivial index function "
-                    ++ pretty ixfun
+              TC.bad . TC.TypeError $
+                "Array "
+                  <> prettyText v
+                  <> " returned by function, but has nontrivial index function "
+                  <> prettyText ixfun
 
 matchLoopResultMem ::
   (Mem rep inner, TC.Checkable rep) =>
@@ -680,7 +675,7 @@
 
       fetchCtx i = case maybeNth i $ zip res ts of
         Nothing ->
-          throwError $ "Cannot find variable #" ++ show i ++ " in results: " ++ pretty res
+          throwError $ "Cannot find variable #" <> prettyText i <> " in results: " <> prettyText res
         Just (se, t) -> pure (se, t)
 
       checkReturn (MemPrim x) (MemPrim y)
@@ -698,68 +693,65 @@
               zipWithM_ checkDim (shapeDims x_shape) (shapeDims y_shape)
               checkMemReturn x_ret y_ret
       checkReturn x y =
-        throwError $ unwords ["Expected", pretty x, "but got", pretty y]
+        throwError $ T.unwords ["Expected", prettyText x, "but got", prettyText y]
 
       checkDim (Free x) y
         | x == y = pure ()
         | otherwise =
-            throwError $ unwords ["Expected dim", pretty x, "but got", pretty y]
+            throwError $ T.unwords ["Expected dim", prettyText x, "but got", prettyText y]
       checkDim (Ext i) y = do
         (x, _) <- fetchCtx i
-        unless (x == y) . throwError . unwords $
-          ["Expected ext dim", pretty i, "=>", pretty x, "but got", pretty y]
+        unless (x == y) . throwError . T.unwords $
+          ["Expected ext dim", prettyText i, "=>", prettyText x, "but got", prettyText y]
 
       checkMemReturn (ReturnsInBlock x_mem x_ixfun) (ArrayIn y_mem y_ixfun)
         | x_mem == y_mem =
             unless (IxFun.closeEnough x_ixfun $ existentialiseIxFun0 y_ixfun) $
-              throwError . unwords $
+              throwError . T.unwords $
                 [ "Index function unification failed (ReturnsInBlock)",
                   "\nixfun of body result: ",
-                  pretty y_ixfun,
+                  prettyText y_ixfun,
                   "\nixfun of return type: ",
-                  pretty x_ixfun
+                  prettyText x_ixfun
                 ]
       checkMemReturn
         (ReturnsNewBlock x_space x_ext x_ixfun)
         (ArrayIn y_mem y_ixfun) = do
           (x_mem, x_mem_type) <- fetchCtx x_ext
           unless (IxFun.closeEnough x_ixfun $ existentialiseIxFun0 y_ixfun) $
-            throwError . pretty $
+            throwError . docText $
               "Index function unification failed (ReturnsNewBlock)"
                 </> "Ixfun of body result:"
-                </> indent 2 (ppr y_ixfun)
+                </> indent 2 (pretty y_ixfun)
                 </> "Ixfun of return type:"
-                </> indent 2 (ppr x_ixfun)
+                </> indent 2 (pretty x_ixfun)
           case x_mem_type of
             MemMem y_space ->
-              unless (x_space == y_space) . throwError . unwords $
+              unless (x_space == y_space) . throwError . T.unwords $
                 [ "Expected memory",
-                  pretty y_mem,
+                  prettyText y_mem,
                   "in space",
-                  pretty x_space,
+                  prettyText x_space,
                   "but actually in space",
-                  pretty y_space
+                  prettyText y_space
                 ]
             t ->
-              throwError . unwords $
-                ["Expected memory", pretty x_ext, "=>", pretty x_mem, "but but has type", pretty t]
+              throwError . T.unwords $
+                ["Expected memory", prettyText x_ext, "=>", prettyText x_mem, "but but has type", prettyText t]
       checkMemReturn x y =
-        throwError . pretty $
+        throwError . docText $
           "Expected array in"
-            </> indent 2 (ppr x)
+            </> indent 2 (pretty x)
             </> "but array returned in"
-            </> indent 2 (ppr y)
+            </> indent 2 (pretty y)
 
-      bad :: String -> TC.TypeM rep a
       bad s =
-        TC.bad $
-          TC.TypeError $
-            pretty $
-              "Return type"
-                </> indent 2 (ppTuple' rettype)
-                </> "cannot match returns of results"
-                </> indent 2 (ppTuple' ts)
-                </> text s
+        TC.bad . TC.TypeError . docText $
+          "Return type"
+            </> indent 2 (ppTuple' $ map pretty rettype)
+            </> "cannot match returns of results"
+            </> indent 2 (ppTuple' $ map pretty ts)
+            </> pretty s
 
   either bad pure =<< runExceptT (zipWithM_ checkReturn rettype ts)
 
@@ -774,18 +766,15 @@
 
   let (ctx_ids, val_ts) = unzip $ bodyReturnsFromPat $ removePatAliases pat
       (ctx_map_ids, ctx_map_exts) = getExtMaps $ zip ctx_ids [0 .. 1]
+      ok =
+        length val_ts == length rt
+          && and (zipWith (matches ctx_map_ids ctx_map_exts) val_ts rt)
 
-  unless
-    ( length val_ts == length rt
-        && and (zipWith (matches ctx_map_ids ctx_map_exts) val_ts rt)
-    )
-    . TC.bad
-    . TC.TypeError
-    . pretty
-    $ "Expression type:"
-      </> indent 2 (ppTuple' rt)
+  unless ok . TC.bad . TC.TypeError . docText $
+    "Expression type:"
+      </> indent 2 (ppTuple' $ map pretty rt)
       </> "cannot match pattern type:"
-      </> indent 2 (ppTuple' val_ts)
+      </> indent 2 (ppTuple' $ map pretty val_ts)
   where
     matches _ _ (MemPrim x) (MemPrim y) = x == y
     matches _ _ (MemMem x_space) (MemMem y_space) =
@@ -860,11 +849,11 @@
     MemArray _ _ _ (ArrayIn mem ixfun) ->
       pure (mem, ixfun)
     _ ->
-      error $
+      error . T.unpack $
         "Expected "
-          ++ pretty name
-          ++ " to be array but bound to:\n"
-          ++ pretty summary
+          <> prettyText name
+          <> " to be array but bound to:\n"
+          <> prettyText summary
 
 lookupMemSpace ::
   (Mem rep inner, HasScope rep m, Monad m) =>
@@ -876,11 +865,11 @@
     MemMem space ->
       pure space
     _ ->
-      error $
+      error . T.unpack $
         "Expected "
-          ++ pretty name
-          ++ " to be memory but bound to:\n"
-          ++ pretty summary
+          <> prettyText name
+          <> " to be memory but bound to:\n"
+          <> prettyText summary
 
 checkMemInfo ::
   TC.Checkable rep =>
@@ -901,12 +890,12 @@
       TC.bad $
         TC.TypeError $
           "Variable "
-            ++ pretty v
-            ++ " used as memory block, but is of type "
-            ++ pretty t
-            ++ "."
+            <> prettyText v
+            <> " used as memory block, but is of type "
+            <> prettyText t
+            <> "."
 
-  TC.context ("in index function " ++ pretty ixfun) $ do
+  TC.context ("in index function " <> prettyText ixfun) $ do
     traverse_ (TC.requirePrimExp int64 . untyped) ixfun
     let ixfun_rank = IxFun.rank ixfun
         ident_rank = shapeRank shape
@@ -914,12 +903,12 @@
       TC.bad $
         TC.TypeError $
           "Arity of index function ("
-            ++ pretty ixfun_rank
-            ++ ") does not match rank of array "
-            ++ pretty name
-            ++ " ("
-            ++ show ident_rank
-            ++ ")"
+            <> prettyText ixfun_rank
+            <> ") does not match rank of array "
+            <> prettyText name
+            <> " ("
+            <> prettyText ident_rank
+            <> ")"
 
 bodyReturnsFromPat ::
   Pat (MemBound NoUniqueness) -> [(VName, BodyReturns)]
@@ -983,7 +972,7 @@
     MemArray et shape _ (ArrayIn mem ixfun) ->
       pure (et, Shape $ shapeDims shape, mem, ixfun)
     _ ->
-      error $ "arrayVarReturns: " ++ pretty v ++ " is not an array."
+      error . T.unpack $ "arrayVarReturns: " <> prettyText v <> " is not an array."
 
 varReturns ::
   (HasScope rep m, Monad m, Mem rep inner) =>
diff --git a/src/Futhark/IR/Mem/IxFun.hs b/src/Futhark/IR/Mem/IxFun.hs
--- a/src/Futhark/IR/Mem/IxFun.hs
+++ b/src/Futhark/IR/Mem/IxFun.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
 
 -- | This module contains a representation for the index function based on
@@ -41,8 +39,8 @@
 import Data.Function (on, (&))
 import Data.List (sort, sortBy, zip4, zipWith4)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Maybe (isJust)
 import Futhark.Analysis.PrimExp
 import Futhark.Analysis.PrimExp.Convert (substituteInPrimExp)
@@ -140,29 +138,27 @@
   deriving (Show, Eq)
 
 instance Pretty Monotonicity where
-  ppr = text . show
+  pretty = pretty . show
 
 instance Pretty num => Pretty (LMAD num) where
-  ppr (LMAD offset dims) =
-    braces $
-      semisep
-        [ "offset: " <> oneLine (ppr offset),
-          "strides: " <> p ldStride,
-          "shape: " <> p ldShape,
-          "permutation: " <> p ldPerm,
-          "monotonicity: " <> p ldMon
-        ]
+  pretty (LMAD offset dims) =
+    braces . semistack $
+      [ "offset:" <+> group (pretty offset),
+        "strides:" <+> p ldStride,
+        "shape:" <+> p ldShape,
+        "permutation:" <+> p ldPerm,
+        "monotonicity:" <+> p ldMon
+      ]
     where
-      p f = oneLine $ brackets $ commasep $ map (ppr . f) dims
+      p f = group $ brackets $ align $ commasep $ map (pretty . f) dims
 
 instance Pretty num => Pretty (IxFun num) where
-  ppr (IxFun lmads oshp cg) =
-    braces $
-      semisep
-        [ "base: " <> brackets (commasep $ map ppr oshp),
-          "contiguous: " <> if cg then "true" else "false",
-          "LMADs: " <> brackets (commastack $ NE.toList $ NE.map ppr lmads)
-        ]
+  pretty (IxFun lmads oshp cg) =
+    braces . semistack $
+      [ "base:" <+> brackets (commasep $ map pretty oshp),
+        "contiguous:" <+> if cg then "true" else "false",
+        "LMADs:" <+> brackets (commastack $ NE.toList $ NE.map pretty lmads)
+      ]
 
 instance Substitute num => Substitute (LMAD num) where
   substituteNames substs = fmap $ substituteNames substs
diff --git a/src/Futhark/IR/Mem/Simplify.hs b/src/Futhark/IR/Mem/Simplify.hs
--- a/src/Futhark/IR/Mem/Simplify.hs
+++ b/src/Futhark/IR/Mem/Simplify.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.IR.Mem.Simplify
@@ -14,14 +10,14 @@
 
 import Control.Monad
 import Data.List (find)
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Construct
 import Futhark.IR.Mem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.IR.Prop.Aliases (AliasedOp)
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Optimise.Simplify.Rule
 import Futhark.Optimise.Simplify.Rules
diff --git a/src/Futhark/IR/Parse.hs b/src/Futhark/IR/Parse.hs
--- a/src/Futhark/IR/Parse.hs
+++ b/src/Futhark/IR/Parse.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Parser for the Futhark core language.
 module Futhark.IR.Parse
   ( parseSOACS,
@@ -18,35 +14,35 @@
 import Data.Functor
 import Data.List (zipWith4)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Set as S
-import qualified Data.Text as T
+import Data.List.NonEmpty qualified as NE
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Data.Void
 import Futhark.Analysis.PrimExp.Parse
 import Futhark.IR
 import Futhark.IR.GPU (GPU)
-import qualified Futhark.IR.GPU.Op as GPU
+import Futhark.IR.GPU.Op qualified as GPU
 import Futhark.IR.GPUMem (GPUMem)
 import Futhark.IR.MC (MC)
-import qualified Futhark.IR.MC.Op as MC
+import Futhark.IR.MC.Op qualified as MC
 import Futhark.IR.MCMem (MCMem)
 import Futhark.IR.Mem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.IR.SOACS (SOACS)
-import qualified Futhark.IR.SOACS.SOAC as SOAC
-import qualified Futhark.IR.SegOp as SegOp
+import Futhark.IR.SOACS.SOAC qualified as SOAC
+import Futhark.IR.SegOp qualified as SegOp
 import Futhark.IR.Seq (Seq)
 import Futhark.IR.SeqMem (SeqMem)
-import Futhark.Util.Pretty (prettyText)
 import Language.Futhark.Primitive.Parse
 import Text.Megaparsec
 import Text.Megaparsec.Char hiding (space)
-import qualified Text.Megaparsec.Char.Lexer as L
+import Text.Megaparsec.Char.Lexer qualified as L
 
 type Parser = Parsec Void T.Text
 
-pStringLiteral :: Parser String
-pStringLiteral = lexeme $ char '"' >> manyTill L.charLiteral (char '"')
+pStringLiteral :: Parser T.Text
+pStringLiteral =
+  lexeme . fmap T.pack $ char '"' >> manyTill L.charLiteral (char '"')
 
 pName :: Parser Name
 pName =
@@ -604,7 +600,7 @@
 pEntryPointType :: Parser EntryPointType
 pEntryPointType =
   choice
-    [ keyword "opaque" $> TypeOpaque <*> pStringLiteral,
+    [ keyword "opaque" $> TypeOpaque . T.unpack <*> pStringLiteral,
       TypeTransparent <$> pValueType
     ]
 
@@ -612,7 +608,7 @@
 pEntry =
   parens $
     (,,)
-      <$> (nameFromString <$> pStringLiteral)
+      <$> (nameFromText <$> pStringLiteral)
       <* pComma
       <*> pEntryPointInputs
       <* pComma
@@ -642,7 +638,7 @@
 pOpaqueType :: Parser (String, OpaqueType)
 pOpaqueType =
   (,)
-    <$> (keyword "type" *> pStringLiteral <* pEqual)
+    <$> (keyword "type" *> (T.unpack <$> pStringLiteral) <* pEqual)
     <*> choice [pRecord, pOpaque]
   where
     pFieldName = choice [pName, nameFromString . show <$> pInt]
diff --git a/src/Futhark/IR/Pretty.hs b/src/Futhark/IR/Pretty.hs
--- a/src/Futhark/IR/Pretty.hs
+++ b/src/Futhark/IR/Pretty.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -11,9 +8,8 @@
 module Futhark.IR.Pretty
   ( prettyTuple,
     prettyTupleLines,
-    pretty,
+    prettyString,
     PrettyRep (..),
-    ppTuple',
   )
 where
 
@@ -35,129 +31,147 @@
   ) =>
   PrettyRep rep
   where
-  ppExpDec :: ExpDec rep -> Exp rep -> Maybe Doc
+  ppExpDec :: ExpDec rep -> Exp rep -> Maybe (Doc a)
   ppExpDec _ _ = Nothing
 
 instance Pretty VName where
-  ppr (VName vn i) = ppr vn <> text "_" <> text (show i)
+  pretty (VName vn i) = pretty vn <> "_" <> pretty (show i)
 
 instance Pretty Commutativity where
-  ppr Commutative = text "commutative"
-  ppr Noncommutative = text "noncommutative"
+  pretty Commutative = "commutative"
+  pretty Noncommutative = "noncommutative"
 
 instance Pretty NoUniqueness where
-  ppr _ = mempty
+  pretty _ = mempty
 
 instance Pretty Shape where
-  ppr = mconcat . map (brackets . ppr) . shapeDims
+  pretty = mconcat . map (brackets . pretty) . shapeDims
 
 instance Pretty Rank where
-  ppr (Rank r) = mconcat $ replicate r "[]"
+  pretty (Rank r) = mconcat $ replicate r "[]"
 
 instance Pretty a => Pretty (Ext a) where
-  ppr (Free e) = ppr e
-  ppr (Ext x) = text "?" <> text (show x)
+  pretty (Free e) = pretty e
+  pretty (Ext x) = "?" <> pretty (show x)
 
 instance Pretty ExtShape where
-  ppr = mconcat . map (brackets . ppr) . shapeDims
+  pretty = mconcat . map (brackets . pretty) . shapeDims
 
 instance Pretty Space where
-  ppr DefaultSpace = mempty
-  ppr (Space s) = text "@" <> text s
-  ppr (ScalarSpace d t) = text "@" <> mconcat (map (brackets . ppr) d) <> ppr t
+  pretty DefaultSpace = mempty
+  pretty (Space s) = "@" <> pretty s
+  pretty (ScalarSpace d t) = "@" <> mconcat (map (brackets . pretty) d) <> pretty t
 
 instance Pretty u => Pretty (TypeBase Shape u) where
-  ppr (Prim t) = ppr t
-  ppr (Acc acc ispace ts u) =
-    ppr u <> text "acc" <> apply [ppr acc, ppr ispace, ppTuple' ts]
-  ppr (Array et (Shape ds) u) =
-    ppr u <> mconcat (map (brackets . ppr) ds) <> ppr et
-  ppr (Mem s) = text "mem" <> ppr s
+  pretty (Prim t) = pretty t
+  pretty (Acc acc ispace ts u) =
+    pretty u
+      <> "acc"
+      <> apply
+        [ pretty acc,
+          pretty ispace,
+          ppTuple' $ map pretty ts
+        ]
+  pretty (Array et (Shape ds) u) =
+    pretty u <> mconcat (map (brackets . pretty) ds) <> pretty et
+  pretty (Mem s) = "mem" <> pretty s
 
 instance Pretty u => Pretty (TypeBase ExtShape u) where
-  ppr (Prim t) = ppr t
-  ppr (Acc acc ispace ts u) =
-    ppr u <> text "acc" <> apply [ppr acc, ppr ispace, ppTuple' ts]
-  ppr (Array et (Shape ds) u) =
-    ppr u <> mconcat (map (brackets . ppr) ds) <> ppr et
-  ppr (Mem s) = text "mem" <> ppr s
+  pretty (Prim t) = pretty t
+  pretty (Acc acc ispace ts u) =
+    pretty u
+      <> "acc"
+      <> apply
+        [ pretty acc,
+          pretty ispace,
+          ppTuple' $ map pretty ts
+        ]
+  pretty (Array et (Shape ds) u) =
+    pretty u <> mconcat (map (brackets . pretty) ds) <> pretty et
+  pretty (Mem s) = "mem" <> pretty s
 
 instance Pretty u => Pretty (TypeBase Rank u) where
-  ppr (Prim t) = ppr t
-  ppr (Acc acc ispace ts u) =
-    ppr u <> text "acc" <> apply [ppr acc, ppr ispace, ppTuple' ts]
-  ppr (Array et (Rank n) u) =
-    ppr u <> mconcat (replicate n $ brackets mempty) <> ppr et
-  ppr (Mem s) = text "mem" <> ppr s
+  pretty (Prim t) = pretty t
+  pretty (Acc acc ispace ts u) =
+    pretty u
+      <> "acc"
+      <> apply
+        [ pretty acc,
+          pretty ispace,
+          ppTuple' $ map pretty ts
+        ]
+  pretty (Array et (Rank n) u) =
+    pretty u <> mconcat (replicate n $ brackets mempty) <> pretty et
+  pretty (Mem s) = "mem" <> pretty s
 
 instance Pretty Ident where
-  ppr ident = ppr (identType ident) <+> ppr (identName ident)
+  pretty ident = pretty (identType ident) <+> pretty (identName ident)
 
 instance Pretty SubExp where
-  ppr (Var v) = ppr v
-  ppr (Constant v) = ppr v
+  pretty (Var v) = pretty v
+  pretty (Constant v) = pretty v
 
 instance Pretty Certs where
-  ppr (Certs []) = empty
-  ppr (Certs cs) = text "#" <> braces (commasep (map ppr cs))
+  pretty (Certs []) = mempty
+  pretty (Certs cs) = "#" <> braces (commasep (map pretty cs))
 
 instance PrettyRep rep => Pretty (Stms rep) where
-  ppr = stack . map ppr . stmsToList
+  pretty = stack . map pretty . stmsToList
 
 instance Pretty SubExpRes where
-  ppr (SubExpRes cs se) = spread $ certAnnots cs ++ [ppr se]
+  pretty (SubExpRes cs se) = hsep $ certAnnots cs ++ [pretty se]
 
 instance PrettyRep rep => Pretty (Body rep) where
-  ppr (Body _ stms res)
-    | null stms = braces (commasep $ map ppr res)
+  pretty (Body _ stms res)
+    | null stms = braces (commasep $ map pretty res)
     | otherwise =
-        stack (map ppr $ stmsToList stms)
-          </> text "in"
-          <+> braces (commasep $ map ppr res)
+        stack (map pretty $ stmsToList stms)
+          </> "in"
+          <+> braces (commasep $ map pretty res)
 
 instance Pretty Attr where
-  ppr (AttrName v) = ppr v
-  ppr (AttrInt x) = ppr x
-  ppr (AttrComp f attrs) = ppr f <> parens (commasep $ map ppr attrs)
+  pretty (AttrName v) = pretty v
+  pretty (AttrInt x) = pretty x
+  pretty (AttrComp f attrs) = pretty f <> parens (commasep $ map pretty attrs)
 
-attrAnnots :: Attrs -> [Doc]
+attrAnnots :: Attrs -> [Doc a]
 attrAnnots = map f . toList . unAttrs
   where
-    f v = text "#[" <> ppr v <> text "]"
+    f v = "#[" <> pretty v <> "]"
 
-stmAttrAnnots :: Stm rep -> [Doc]
+stmAttrAnnots :: Stm rep -> [Doc a]
 stmAttrAnnots = attrAnnots . stmAuxAttrs . stmAux
 
-certAnnots :: Certs -> [Doc]
+certAnnots :: Certs -> [Doc a]
 certAnnots cs
   | cs == mempty = []
-  | otherwise = [ppr cs]
+  | otherwise = [pretty cs]
 
-stmCertAnnots :: Stm rep -> [Doc]
+stmCertAnnots :: Stm rep -> [Doc a]
 stmCertAnnots = certAnnots . stmAuxCerts . stmAux
 
 instance Pretty Attrs where
-  ppr = spread . attrAnnots
+  pretty = hsep . attrAnnots
 
 instance Pretty t => Pretty (Pat t) where
-  ppr (Pat xs) = braces $ commastack $ map ppr xs
+  pretty (Pat xs) = braces $ commastack $ map pretty xs
 
 instance Pretty t => Pretty (PatElem t) where
-  ppr (PatElem name t) = ppr name <+> colon <+> align (ppr t)
+  pretty (PatElem name t) = pretty name <+> colon <+> align (pretty t)
 
 instance Pretty t => Pretty (Param t) where
-  ppr (Param attrs name t) =
-    annot (attrAnnots attrs) $ ppr name <+> colon <+> align (ppr t)
+  pretty (Param attrs name t) =
+    annot (attrAnnots attrs) $ pretty name <+> colon <+> align (pretty t)
 
 instance PrettyRep rep => Pretty (Stm rep) where
-  ppr stm@(Let pat aux e) =
+  pretty stm@(Let pat aux e) =
     align . hang 2 $
-      text "let"
-        <+> align (ppr pat)
+      "let"
+        <+> align (pretty pat)
         <+> case (linebreak, stmannot) of
-          (True, []) -> equals </> ppr e
-          (False, []) -> equals <+/> ppr e
-          (_, ann) -> equals </> (stack ann </> ppr e)
+          (True, []) -> equals </> pretty e
+          (False, []) -> equals <+> pretty e
+          (_, ann) -> equals </> (stack ann </> pretty e)
     where
       linebreak = case e of
         BasicOp BinOp {} -> False
@@ -175,237 +189,238 @@
           ]
 
 instance Pretty a => Pretty (Slice a) where
-  ppr (Slice xs) = brackets (commasep (map ppr xs))
+  pretty (Slice xs) = brackets (commasep (map pretty xs))
 
 instance Pretty d => Pretty (FlatDimIndex d) where
-  ppr (FlatDimIndex n s) = ppr n <+> text ":" <+> ppr s
+  pretty (FlatDimIndex n s) = pretty n <+> ":" <+> pretty s
 
 instance Pretty a => Pretty (FlatSlice a) where
-  ppr (FlatSlice offset xs) = brackets (ppr offset <> text ";" <+> commasep (map ppr xs))
+  pretty (FlatSlice offset xs) = brackets (pretty offset <> ";" <+> commasep (map pretty xs))
 
 instance Pretty BasicOp where
-  ppr (SubExp se) = ppr se
-  ppr (Opaque OpaqueNil e) = text "opaque" <> apply [ppr e]
-  ppr (Opaque (OpaqueTrace s) e) = text "trace" <> apply [ppr (show s), ppr e]
-  ppr (ArrayLit es rt) =
+  pretty (SubExp se) = pretty se
+  pretty (Opaque OpaqueNil e) = "opaque" <> apply [pretty e]
+  pretty (Opaque (OpaqueTrace s) e) = "trace" <> apply [pretty (show s), pretty e]
+  pretty (ArrayLit es rt) =
     case rt of
-      Array {} -> brackets $ commastack $ map ppr es
-      _ -> brackets $ commasep $ map ppr es
+      Array {} -> brackets $ commastack $ map pretty es
+      _ -> brackets $ commasep $ map pretty es
       <+> colon
-      <+> text "[]" <> ppr rt
-  ppr (BinOp bop x y) = ppr bop <> parens (ppr x <> comma <+> ppr y)
-  ppr (CmpOp op x y) = ppr op <> parens (ppr x <> comma <+> ppr y)
-  ppr (ConvOp conv x) =
-    text (convOpFun conv) <+> ppr fromtype <+> ppr x <+> text "to" <+> ppr totype
+      <+> "[]" <> pretty rt
+  pretty (BinOp bop x y) = pretty bop <> parens (pretty x <> comma <+> pretty y)
+  pretty (CmpOp op x y) = pretty op <> parens (pretty x <> comma <+> pretty y)
+  pretty (ConvOp conv x) =
+    pretty (convOpFun conv) <+> pretty fromtype <+> pretty x <+> "to" <+> pretty totype
     where
       (fromtype, totype) = convOpType conv
-  ppr (UnOp op e) = ppr op <+> pprPrec 9 e
-  ppr (Index v slice) = ppr v <> ppr slice
-  ppr (Update safety src slice se) =
-    ppr src <+> with <+> ppr slice <+> text "=" <+> ppr se
+  pretty (UnOp op e) = pretty op <+> pretty e
+  pretty (Index v slice) = pretty v <> pretty slice
+  pretty (Update safety src slice se) =
+    pretty src <+> with <+> pretty slice <+> "=" <+> pretty se
     where
       with = case safety of
-        Unsafe -> text "with"
-        Safe -> text "with?"
-  ppr (FlatIndex v slice) = ppr v <> ppr slice
-  ppr (FlatUpdate src slice se) =
-    ppr src <+> text "with" <+> ppr slice <+> text "=" <+> ppr se
-  ppr (Iota e x s et) = text "iota" <> et' <> apply [ppr e, ppr x, ppr s]
+        Unsafe -> "with"
+        Safe -> "with?"
+  pretty (FlatIndex v slice) = pretty v <> pretty slice
+  pretty (FlatUpdate src slice se) =
+    pretty src <+> "with" <+> pretty slice <+> "=" <+> pretty se
+  pretty (Iota e x s et) = "iota" <> et' <> apply [pretty e, pretty x, pretty s]
     where
-      et' = text $ show $ primBitSize $ IntType et
-  ppr (Replicate ne ve) =
-    text "replicate" <> apply [ppr ne, align (ppr ve)]
-  ppr (Scratch t shape) =
-    text "scratch" <> apply (ppr t : map ppr shape)
-  ppr (Reshape ReshapeArbitrary shape e) =
-    text "reshape" <> apply [ppr shape, ppr e]
-  ppr (Reshape ReshapeCoerce shape e) =
-    text "coerce" <> apply [ppr shape, ppr e]
-  ppr (Rearrange perm e) =
-    text "rearrange" <> apply [apply (map ppr perm), ppr e]
-  ppr (Rotate es e) =
-    text "rotate" <> apply [apply (map ppr es), ppr e]
-  ppr (Concat i (x :| xs) w) =
-    text "concat" <> text "@" <> ppr i <> apply (ppr w : ppr x : map ppr xs)
-  ppr (Copy e) = text "copy" <> parens (ppr e)
-  ppr (Manifest perm e) = text "manifest" <> apply [apply (map ppr perm), ppr e]
-  ppr (Assert e msg (loc, _)) =
-    text "assert" <> apply [ppr e, ppr msg, text $ show $ locStr loc]
-  ppr (UpdateAcc acc is v) =
-    text "update_acc" <> apply [ppr acc, ppTuple' is, ppTuple' v]
+      et' = pretty $ show $ primBitSize $ IntType et
+  pretty (Replicate ne ve) =
+    "replicate" <> apply [pretty ne, align (pretty ve)]
+  pretty (Scratch t shape) =
+    "scratch" <> apply (pretty t : map pretty shape)
+  pretty (Reshape ReshapeArbitrary shape e) =
+    "reshape" <> apply [pretty shape, pretty e]
+  pretty (Reshape ReshapeCoerce shape e) =
+    "coerce" <> apply [pretty shape, pretty e]
+  pretty (Rearrange perm e) =
+    "rearrange" <> apply [apply (map pretty perm), pretty e]
+  pretty (Rotate es e) =
+    "rotate" <> apply [apply (map pretty es), pretty e]
+  pretty (Concat i (x :| xs) w) =
+    "concat" <> "@" <> pretty i <> apply (pretty w : pretty x : map pretty xs)
+  pretty (Copy e) = "copy" <> parens (pretty e)
+  pretty (Manifest perm e) = "manifest" <> apply [apply (map pretty perm), pretty e]
+  pretty (Assert e msg (loc, _)) =
+    "assert" <> apply [pretty e, pretty msg, pretty $ show $ locStr loc]
+  pretty (UpdateAcc acc is v) =
+    "update_acc"
+      <> apply
+        [ pretty acc,
+          ppTuple' $ map pretty is,
+          ppTuple' $ map pretty v
+        ]
 
 instance Pretty a => Pretty (ErrorMsg a) where
-  ppr (ErrorMsg parts) = braces $ align $ commasep $ map p parts
+  pretty (ErrorMsg parts) = braces $ align $ commasep $ map p parts
     where
-      p (ErrorString s) = text $ show s
-      p (ErrorVal t x) = ppr x <+> colon <+> ppr t
+      p (ErrorString s) = pretty $ show s
+      p (ErrorVal t x) = pretty x <+> colon <+> pretty t
 
-maybeNest :: PrettyRep rep => Body rep -> Doc
+maybeNest :: PrettyRep rep => Body rep -> Doc a
 maybeNest b
-  | null $ bodyStms b = ppr b
-  | otherwise = nestedBlock "{" "}" $ ppr b
+  | null $ bodyStms b = pretty b
+  | otherwise = nestedBlock "{" "}" $ pretty b
 
 instance PrettyRep rep => Pretty (Case (Body rep)) where
-  ppr (Case vs b) =
-    "case" <+> ppTuple' (map (maybe "_" ppr) vs) <+> "->" <+> maybeNest b
+  pretty (Case vs b) =
+    "case" <+> ppTuple' (map (maybe "_" pretty) vs) <+> "->" <+> maybeNest b
 
 instance PrettyRep rep => Pretty (Exp rep) where
-  ppr (Match [c] [Case [Just (BoolValue True)] t] f (MatchDec ret ifsort)) =
-    text "if"
+  pretty (Match [c] [Case [Just (BoolValue True)] t] f (MatchDec ret ifsort)) =
+    "if"
       <+> info'
-      <+> ppr c
-      </> text "then"
+      <+> pretty c
+      </> "then"
       <+> maybeNest t
-      <+> text "else"
+      <+> "else"
       <+> maybeNest f
       </> colon
-      <+> ppTuple' ret
+      <+> ppTuple' (map pretty ret)
     where
       info' = case ifsort of
         MatchNormal -> mempty
-        MatchFallback -> text "<fallback>"
-        MatchEquiv -> text "<equiv>"
-  ppr (Match ses cs defb (MatchDec ret ifsort)) =
-    ("match" <+> info' <+> ppTuple' ses)
-      </> stack (map ppr cs)
+        MatchFallback -> "<fallback>"
+        MatchEquiv -> "<equiv>"
+  pretty (Match ses cs defb (MatchDec ret ifsort)) =
+    ("match" <+> info' <+> ppTuple' (map pretty ses))
+      </> stack (map pretty cs)
       </> "default"
       <+> "->"
       <+> maybeNest defb
       </> colon
-      <+> ppTuple' ret
+      <+> ppTuple' (map pretty ret)
     where
       info' = case ifsort of
         MatchNormal -> mempty
-        MatchFallback -> text "<fallback>"
-        MatchEquiv -> text "<equiv>"
-  ppr (BasicOp op) = ppr op
-  ppr (Apply fname args ret (safety, _, _)) =
+        MatchFallback -> "<fallback>"
+        MatchEquiv -> "<equiv>"
+  pretty (BasicOp op) = pretty op
+  pretty (Apply fname args ret (safety, _, _)) =
     applykw
-      <+> text (nameToString fname)
-        <> apply (map (align . pprArg) args)
+      <+> pretty (nameToString fname)
+        <> apply (map (align . prettyArg) args)
       </> colon
-      <+> braces (commasep $ map ppr ret)
+      <+> braces (commasep $ map pretty ret)
     where
-      pprArg (arg, Consume) = text "*" <> ppr arg
-      pprArg (arg, _) = ppr arg
+      prettyArg (arg, Consume) = "*" <> pretty arg
+      prettyArg (arg, _) = pretty arg
       applykw = case safety of
-        Unsafe -> text "apply <unsafe>"
-        Safe -> text "apply"
-  ppr (Op op) = ppr op
-  ppr (DoLoop merge form loopbody) =
-    text "loop"
-      <+> braces (commastack $ map ppr params)
+        Unsafe -> "apply <unsafe>"
+        Safe -> "apply"
+  pretty (Op op) = pretty op
+  pretty (DoLoop merge form loopbody) =
+    "loop"
+      <+> braces (commastack $ map pretty params)
       <+> equals
-      <+> ppTuple' args
+      <+> ppTuple' (map pretty args)
       </> ( case form of
               ForLoop i it bound [] ->
-                text "for"
+                "for"
                   <+> align
-                    ( ppr i <> text ":" <> ppr it
-                        <+> text "<"
-                        <+> align (ppr bound)
+                    ( pretty i <> ":" <> pretty it
+                        <+> "<"
+                        <+> align (pretty bound)
                     )
               ForLoop i it bound loop_vars ->
-                text "for"
+                "for"
                   <+> align
-                    ( ppr i <> text ":" <> ppr it
-                        <+> text "<"
-                        <+> align (ppr bound)
-                        </> stack (map pprLoopVar loop_vars)
+                    ( pretty i <> ":" <> pretty it
+                        <+> "<"
+                        <+> align (pretty bound)
+                        </> stack (map prettyLoopVar loop_vars)
                     )
               WhileLoop cond ->
-                text "while" <+> ppr cond
+                "while" <+> pretty cond
           )
-      <+> text "do"
-      <+> nestedBlock "{" "}" (ppr loopbody)
+      <+> "do"
+      <+> nestedBlock "{" "}" (pretty loopbody)
     where
       (params, args) = unzip merge
-      pprLoopVar (p, a) = ppr p <+> text "in" <+> ppr a
-  ppr (WithAcc inputs lam) =
-    text "with_acc"
-      <> parens (braces (commastack $ map ppInput inputs) <> comma </> ppr lam)
+      prettyLoopVar (p, a) = pretty p <+> "in" <+> pretty a
+  pretty (WithAcc inputs lam) =
+    "with_acc"
+      <> parens (braces (commastack $ map ppInput inputs) <> comma </> pretty lam)
     where
       ppInput (shape, arrs, op) =
         parens
-          ( ppr shape <> comma
-              <+> ppTuple' arrs
+          ( pretty shape <> comma
+              <+> ppTuple' (map pretty arrs)
                 <> case op of
                   Nothing -> mempty
                   Just (op', nes) ->
-                    comma </> parens (ppr op' <> comma </> ppTuple' (map ppr nes))
+                    comma </> parens (pretty op' <> comma </> ppTuple' (map pretty nes))
           )
 
 instance PrettyRep rep => Pretty (Lambda rep) where
-  ppr (Lambda [] (Body _ stms []) []) | stms == mempty = text "nilFn"
-  ppr (Lambda params body rettype) =
-    text "\\"
-      <+> ppTuple' params
-      </> indent 2 (colon <+> ppTupleLines' rettype <+> text "->")
-      </> indent 2 (ppr body)
+  pretty (Lambda [] (Body _ stms []) []) | stms == mempty = "nilFn"
+  pretty (Lambda params body rettype) =
+    "\\"
+      <+> ppTuple' (map pretty params)
+      </> indent 2 (colon <+> ppTupleLines' rettype <+> "->")
+      </> indent 2 (pretty body)
 
 instance Pretty Signedness where
-  ppr Signed = "signed"
-  ppr Unsigned = "unsigned"
+  pretty Signed = "signed"
+  pretty Unsigned = "unsigned"
 
 instance Pretty ValueType where
-  ppr (ValueType s (Rank r) t) =
-    mconcat (replicate r "[]") <> text (prettySigned (s == Unsigned) t)
+  pretty (ValueType s (Rank r) t) =
+    mconcat (replicate r "[]") <> pretty (prettySigned (s == Unsigned) t)
 
 instance Pretty EntryPointType where
-  ppr (TypeTransparent t) = ppr t
-  ppr (TypeOpaque desc) = "opaque" <+> pquote (text desc)
+  pretty (TypeTransparent t) = pretty t
+  pretty (TypeOpaque desc) = "opaque" <+> dquotes (pretty desc)
 
 instance Pretty EntryParam where
-  ppr (EntryParam name u t) = ppr name <> colon <+> ppr u <> ppr t
+  pretty (EntryParam name u t) = pretty name <> colon <+> pretty u <> pretty t
 
 instance Pretty EntryResult where
-  ppr (EntryResult u t) = ppr u <> ppr t
+  pretty (EntryResult u t) = pretty u <> pretty t
 
 instance PrettyRep rep => Pretty (FunDef rep) where
-  ppr (FunDef entry attrs name rettype fparams body) =
+  pretty (FunDef entry attrs name rettype fparams body) =
     annot (attrAnnots attrs) $
       fun
-        </> indent 2 (text (nameToString name))
-        <+> apply (map ppr fparams)
-        </> indent 2 (colon <+> align (ppTuple' rettype))
+        </> indent 2 (pretty (nameToString name))
+        <+> apply (map pretty fparams)
+        </> indent 2 (colon <+> align (ppTuple' $ map pretty rettype))
         <+> equals
-        <+> nestedBlock "{" "}" (ppr body)
+        <+> nestedBlock "{" "}" (pretty body)
     where
       fun = case entry of
         Nothing -> "fun"
         Just (p_name, p_entry, ret_entry) ->
           "entry"
-            <> parens
-              ( "\"" <> ppr p_name <> "\"" <> comma
+            <> (parens . align)
+              ( "\"" <> pretty p_name <> "\"" <> comma
                   </> ppTupleLines' p_entry <> comma
                   </> ppTupleLines' ret_entry
               )
 
 instance Pretty OpaqueType where
-  ppr (OpaqueType ts) =
-    "opaque" <+> nestedBlock "{" "}" (stack $ map ppr ts)
-  ppr (OpaqueRecord fs) =
+  pretty (OpaqueType ts) =
+    "opaque" <+> nestedBlock "{" "}" (stack $ map pretty ts)
+  pretty (OpaqueRecord fs) =
     "record" <+> nestedBlock "{" "}" (stack $ map p fs)
     where
-      p (f, et) = ppr f <> ":" <+> ppr et
+      p (f, et) = pretty f <> ":" <+> pretty et
 
 instance Pretty OpaqueTypes where
-  ppr (OpaqueTypes ts) = "types" <+> nestedBlock "{" "}" (stack $ map p ts)
+  pretty (OpaqueTypes ts) = "types" <+> nestedBlock "{" "}" (stack $ map p ts)
     where
-      p (name, t) = "type" <+> pquote (ppr name) <+> equals <+> ppr t
+      p (name, t) = "type" <+> dquotes (pretty name) <+> equals <+> pretty t
 
 instance PrettyRep rep => Pretty (Prog rep) where
-  ppr (Prog types consts funs) =
-    stack $ punctuate line $ ppr types : ppr consts : map ppr funs
+  pretty (Prog types consts funs) =
+    stack $ punctuate line $ pretty types : pretty consts : map pretty funs
 
 instance Pretty d => Pretty (DimIndex d) where
-  ppr (DimFix i) = ppr i
-  ppr (DimSlice i n s) = ppr i <+> text ":+" <+> ppr n <+> text "*" <+> ppr s
-
--- | Like 'prettyTuple', but produces a 'Doc'.
-ppTuple' :: Pretty a => [a] -> Doc
-ppTuple' ets = braces $ commasep $ map (align . ppr) ets
+  pretty (DimFix i) = pretty i
+  pretty (DimSlice i n s) = pretty i <+> ":+" <+> pretty n <+> "*" <+> pretty s
 
 -- | Like 'prettyTupleLines', but produces a 'Doc'.
-ppTupleLines' :: Pretty a => [a] -> Doc
-ppTupleLines' ets = braces $ stack $ punctuate comma $ map (align . ppr) ets
+ppTupleLines' :: Pretty a => [a] -> Doc b
+ppTupleLines' = braces . stack . punctuate comma . map (align . pretty)
diff --git a/src/Futhark/IR/Prop.hs b/src/Futhark/IR/Prop.hs
--- a/src/Futhark/IR/Prop.hs
+++ b/src/Futhark/IR/Prop.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module provides various simple ways to query and manipulate
@@ -42,9 +38,9 @@
 
 import Control.Monad
 import Data.List (elemIndex, find)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe (isJust, mapMaybe)
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.Pretty
 import Futhark.IR.Prop.Constants
 import Futhark.IR.Prop.Names
diff --git a/src/Futhark/IR/Prop/Aliases.hs b/src/Futhark/IR/Prop/Aliases.hs
--- a/src/Futhark/IR/Prop/Aliases.hs
+++ b/src/Futhark/IR/Prop/Aliases.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | The IR tracks aliases, mostly to ensure the soundness of in-place
@@ -32,9 +30,9 @@
 where
 
 import Data.Bifunctor (first, second)
-import qualified Data.Kind
+import Data.Kind qualified
 import Data.List (find, transpose)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.IR.Prop (IsOp, NameInfo (..), Scope)
 import Futhark.IR.Prop.Names
 import Futhark.IR.Prop.Patterns
diff --git a/src/Futhark/IR/Prop/Names.hs b/src/Futhark/IR/Prop/Names.hs
--- a/src/Futhark/IR/Prop/Names.hs
+++ b/src/Futhark/IR/Prop/Names.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 -- | Facilities for determining which names are used in some syntactic
@@ -46,9 +44,9 @@
 import Control.Category
 import Control.Monad.State.Strict
 import Data.Foldable
-import qualified Data.IntMap.Strict as IM
-import qualified Data.IntSet as IS
-import qualified Data.Map.Strict as M
+import Data.IntMap.Strict qualified as IM
+import Data.IntSet qualified as IS
+import Data.Map.Strict qualified as M
 import Futhark.IR.Prop.Patterns
 import Futhark.IR.Prop.Scope
 import Futhark.IR.Syntax
@@ -79,7 +77,7 @@
   mempty = Names mempty
 
 instance Pretty Names where
-  ppr = ppr . namesToList
+  pretty = pretty . namesToList
 
 -- | Does the set of names contain this name?
 nameIn :: VName -> Names -> Bool
diff --git a/src/Futhark/IR/Prop/Patterns.hs b/src/Futhark/IR/Prop/Patterns.hs
--- a/src/Futhark/IR/Prop/Patterns.hs
+++ b/src/Futhark/IR/Prop/Patterns.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Inspecing and modifying t'Pat's, function parameters and
diff --git a/src/Futhark/IR/Prop/Scope.hs b/src/Futhark/IR/Prop/Scope.hs
--- a/src/Futhark/IR/Prop/Scope.hs
+++ b/src/Futhark/IR/Prop/Scope.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE StandaloneDeriving #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -41,10 +35,10 @@
 where
 
 import Control.Monad.Except
-import qualified Control.Monad.RWS.Lazy
-import qualified Control.Monad.RWS.Strict
+import Control.Monad.RWS.Lazy qualified
+import Control.Monad.RWS.Strict qualified
 import Control.Monad.Reader
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.IR.Pretty ()
 import Futhark.IR.Prop.Types
 import Futhark.IR.Rep
@@ -89,7 +83,7 @@
       notFound =
         error $
           "Scope.lookupInfo: Name "
-            ++ pretty name
+            ++ prettyString name
             ++ " not found in type environment."
 
   -- | Return the type environment contained in the applicative
diff --git a/src/Futhark/IR/Prop/TypeOf.hs b/src/Futhark/IR/Prop/TypeOf.hs
--- a/src/Futhark/IR/Prop/TypeOf.hs
+++ b/src/Futhark/IR/Prop/TypeOf.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module provides facilities for obtaining the types of
diff --git a/src/Futhark/IR/Prop/Types.hs b/src/Futhark/IR/Prop/Types.hs
--- a/src/Futhark/IR/Prop/Types.hs
+++ b/src/Futhark/IR/Prop/Types.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-
 -- | Functions for inspecting and constructing various types.
 module Futhark.IR.Prop.Types
   ( rankShaped,
@@ -70,9 +67,9 @@
 
 import Control.Monad.State
 import Data.List (elemIndex, foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.Prop.Constants
 import Futhark.IR.Prop.Rearrange
 import Futhark.IR.Syntax.Core
diff --git a/src/Futhark/IR/Rep.hs b/src/Futhark/IR/Rep.hs
--- a/src/Futhark/IR/Rep.hs
+++ b/src/Futhark/IR/Rep.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | The core Futhark AST is parameterised by a @rep@ type parameter,
@@ -9,7 +8,7 @@
   )
 where
 
-import qualified Data.Kind
+import Data.Kind qualified
 import Futhark.IR.Prop.Types
 import Futhark.IR.RetType
 import Futhark.IR.Syntax.Core (DeclExtType, DeclType, ExtType, Type)
diff --git a/src/Futhark/IR/RetType.hs b/src/Futhark/IR/RetType.hs
--- a/src/Futhark/IR/RetType.hs
+++ b/src/Futhark/IR/RetType.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module exports a type class covering representations of
@@ -11,7 +10,7 @@
 where
 
 import Control.Monad.Identity
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.IR.Prop.Types
 import Futhark.IR.Syntax.Core
 
diff --git a/src/Futhark/IR/SOACS.hs b/src/Futhark/IR/SOACS.hs
--- a/src/Futhark/IR/SOACS.hs
+++ b/src/Futhark/IR/SOACS.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | A simple representation with SOACs and nested parallelism.
@@ -22,7 +21,7 @@
 import Futhark.IR.SOACS.SOAC
 import Futhark.IR.Syntax
 import Futhark.IR.Traversals
-import qualified Futhark.IR.TypeCheck as TC
+import Futhark.IR.TypeCheck qualified as TC
 
 -- | The rep for the basic representation.
 data SOACS
diff --git a/src/Futhark/IR/SOACS/SOAC.hs b/src/Futhark/IR/SOACS/SOAC.hs
--- a/src/Futhark/IR/SOACS/SOAC.hs
+++ b/src/Futhark/IR/SOACS/SOAC.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -58,23 +54,23 @@
 import Control.Monad.Writer
 import Data.Function ((&))
 import Data.List (intersperse)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.Metrics
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Aliases (Aliases, removeLambdaAliases)
 import Futhark.IR.Prop.Aliases
-import qualified Futhark.IR.TypeCheck as TC
+import Futhark.IR.TypeCheck qualified as TC
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
 import Futhark.Util (chunks, maybeNth)
-import Futhark.Util.Pretty (Doc, Pretty, comma, commasep, parens, ppr, text, (<+>), (</>))
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty (Doc, Pretty, align, comma, commasep, docText, parens, ppTuple', pretty, (<+>), (</>))
+import Futhark.Util.Pretty qualified as PP
 import Prelude hiding (id, (.))
 
 -- | A second-order array combinator (SOAC).
@@ -669,21 +665,21 @@
   TC.checkLambda lam $ map TC.noArgAliases args'
   vec_ts <- mapM TC.checkSubExp vec
   unless (vec_ts == lambdaReturnType lam) $
-    TC.bad . TC.TypeError . pretty $
+    TC.bad . TC.TypeError . docText $
       "Return type"
-        </> PP.indent 2 (ppr (lambdaReturnType lam))
+        </> PP.indent 2 (pretty (lambdaReturnType lam))
         </> "does not match type of seed vector"
-        </> PP.indent 2 (ppr vec_ts)
+        </> PP.indent 2 (pretty vec_ts)
 typeCheckSOAC (JVP lam args vec) = do
   args' <- mapM TC.checkArg args
   TC.checkLambda lam $ map TC.noArgAliases args'
   vec_ts <- mapM TC.checkSubExp vec
   unless (vec_ts == map TC.argType args') $
-    TC.bad . TC.TypeError . pretty $
+    TC.bad . TC.TypeError . docText $
       "Parameter type"
-        </> PP.indent 2 (ppr $ map TC.argType args')
+        </> PP.indent 2 (pretty $ map TC.argType args')
         </> "does not match type of seed vector"
-        </> PP.indent 2 (ppr vec_ts)
+        </> PP.indent 2 (pretty vec_ts)
 typeCheckSOAC (Stream size arrexps accexps lam) = do
   TC.require [Prim int64] size
   accargs <- mapM TC.checkArg accexps
@@ -696,8 +692,8 @@
   let acc_len = length accexps
   let lamrtp = take acc_len $ lambdaReturnType lam
   unless (map TC.argType accargs == lamrtp) $
-    TC.bad $
-      TC.TypeError "Stream with inconsistent accumulator type in lambda."
+    TC.bad . TC.TypeError $
+      "Stream with inconsistent accumulator type in lambda."
   -- just get the dflow of lambda on the fakearg, which does not alias
   -- arr, so we can later check that aliases of arr are not used inside lam.
   let fake_lamarrs' = map asArg lamarrs'
@@ -771,12 +767,11 @@
     TC.checkLambda op $ map TC.noArgAliases $ nes' ++ nes'
     let nes_t = map TC.argType nes'
     unless (nes_t == lambdaReturnType op) $
-      TC.bad $
-        TC.TypeError $
-          "Operator has return type "
-            ++ prettyTuple (lambdaReturnType op)
-            ++ " but neutral element has type "
-            ++ prettyTuple nes_t
+      TC.bad . TC.TypeError $
+        "Operator has return type "
+          <> prettyTuple (lambdaReturnType op)
+          <> " but neutral element has type "
+          <> prettyTuple nes_t
 
     -- Arrays must have proper type.
     forM_ (zip nes_t dests) $ \(t, dest) -> do
@@ -794,12 +789,11 @@
         concatMap ((`replicate` Prim int64) . shapeRank . histShape) ops
           ++ nes_ts
   unless (bucket_ret_t == lambdaReturnType bucket_fun) $
-    TC.bad $
-      TC.TypeError $
-        "Bucket function has return type "
-          ++ prettyTuple (lambdaReturnType bucket_fun)
-          ++ " but should have type "
-          ++ prettyTuple bucket_ret_t
+    TC.bad . TC.TypeError $
+      "Bucket function has return type "
+        <> prettyTuple (lambdaReturnType bucket_fun)
+        <> " but should have type "
+        <> prettyTuple bucket_ret_t
 typeCheckSOAC (Screma w arrs (ScremaForm scans reds map_lam)) = do
   TC.require [Prim int64] w
   arrs' <- TC.checkSOACArrayArgs w arrs
@@ -811,12 +805,11 @@
       let scan_t = map TC.argType scan_nes'
       TC.checkLambda scan_lam $ map TC.noArgAliases $ scan_nes' ++ scan_nes'
       unless (scan_t == lambdaReturnType scan_lam) $
-        TC.bad $
-          TC.TypeError $
-            "Scan function returns type "
-              ++ prettyTuple (lambdaReturnType scan_lam)
-              ++ " but neutral element has type "
-              ++ prettyTuple scan_t
+        TC.bad . TC.TypeError $
+          "Scan function returns type "
+            <> prettyTuple (lambdaReturnType scan_lam)
+            <> " but neutral element has type "
+            <> prettyTuple scan_t
       pure scan_nes'
 
   red_nes' <- fmap concat $
@@ -825,12 +818,11 @@
       let red_t = map TC.argType red_nes'
       TC.checkLambda red_lam $ map TC.noArgAliases $ red_nes' ++ red_nes'
       unless (red_t == lambdaReturnType red_lam) $
-        TC.bad $
-          TC.TypeError $
-            "Reduce function returns type "
-              ++ prettyTuple (lambdaReturnType red_lam)
-              ++ " but neutral element has type "
-              ++ prettyTuple red_t
+        TC.bad . TC.TypeError $
+          "Reduce function returns type "
+            <> prettyTuple (lambdaReturnType red_lam)
+            <> " but neutral element has type "
+            <> prettyTuple red_t
       pure red_nes'
 
   let map_lam_ts = lambdaReturnType map_lam
@@ -839,11 +831,11 @@
     ( take (length scan_nes' + length red_nes') map_lam_ts
         == map TC.argType (scan_nes' ++ red_nes')
     )
-    $ TC.bad
-    $ TC.TypeError
+    . TC.bad
+    . TC.TypeError
     $ "Map function return type "
-      ++ prettyTuple map_lam_ts
-      ++ " wrong for given scan and reduction functions."
+      <> prettyTuple map_lam_ts
+      <> " wrong for given scan and reduction functions."
 
 instance OpMetrics (Op rep) => OpMetrics (SOAC rep) where
   opMetrics (VJP lam _ _) =
@@ -863,104 +855,104 @@
       lambdaMetrics map_lam
 
 instance PrettyRep rep => PP.Pretty (SOAC rep) where
-  ppr (VJP lam args vec) =
-    text "vjp"
+  pretty (VJP lam args vec) =
+    "vjp"
       <> parens
         ( PP.align $
-            ppr lam <> comma
-              </> PP.braces (commasep $ map ppr args) <> comma
-              </> PP.braces (commasep $ map ppr vec)
+            pretty lam <> comma
+              </> PP.braces (commasep $ map pretty args) <> comma
+              </> PP.braces (commasep $ map pretty vec)
         )
-  ppr (JVP lam args vec) =
-    text "jvp"
+  pretty (JVP lam args vec) =
+    "jvp"
       <> parens
         ( PP.align $
-            ppr lam <> comma
-              </> PP.braces (commasep $ map ppr args) <> comma
-              </> PP.braces (commasep $ map ppr vec)
+            pretty lam <> comma
+              </> PP.braces (commasep $ map pretty args) <> comma
+              </> PP.braces (commasep $ map pretty vec)
         )
-  ppr (Stream size arrs acc lam) =
+  pretty (Stream size arrs acc lam) =
     ppStream size arrs acc lam
-  ppr (Scatter w arrs lam dests) =
+  pretty (Scatter w arrs lam dests) =
     ppScatter w arrs lam dests
-  ppr (Hist w arrs ops bucket_fun) =
+  pretty (Hist w arrs ops bucket_fun) =
     ppHist w arrs ops bucket_fun
-  ppr (Screma w arrs (ScremaForm scans reds map_lam))
+  pretty (Screma w arrs (ScremaForm scans reds map_lam))
     | null scans,
       null reds =
-        text "map"
-          <> parens
-            ( ppr w <> comma
-                </> ppTuple' arrs <> comma
-                </> ppr map_lam
+        "map"
+          <> (parens . align)
+            ( pretty w <> comma
+                </> ppTuple' (map pretty arrs) <> comma
+                </> pretty map_lam
             )
     | null scans =
-        text "redomap"
-          <> parens
-            ( ppr w <> comma
-                </> ppTuple' arrs <> comma
-                </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map ppr reds) <> comma
-                </> ppr map_lam
+        "redomap"
+          <> (parens . align)
+            ( pretty w <> comma
+                </> ppTuple' (map pretty arrs) <> comma
+                </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map pretty reds) <> comma
+                </> pretty map_lam
             )
     | null reds =
-        text "scanomap"
-          <> parens
-            ( ppr w <> comma
-                </> ppTuple' arrs <> comma
-                </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map ppr scans) <> comma
-                </> ppr map_lam
+        "scanomap"
+          <> (parens . align)
+            ( pretty w <> comma
+                </> ppTuple' (map pretty arrs) <> comma
+                </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map pretty scans) <> comma
+                </> pretty map_lam
             )
-  ppr (Screma w arrs form) = ppScrema w arrs form
+  pretty (Screma w arrs form) = ppScrema w arrs form
 
 -- | Prettyprint the given Screma.
 ppScrema ::
-  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> ScremaForm rep -> Doc
+  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> ScremaForm rep -> Doc ann
 ppScrema w arrs (ScremaForm scans reds map_lam) =
-  text "screma"
-    <> parens
-      ( ppr w <> comma
-          </> ppTuple' arrs <> comma
-          </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map ppr scans) <> comma
-          </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map ppr reds) <> comma
-          </> ppr map_lam
+  "screma"
+    <> (parens . align)
+      ( pretty w <> comma
+          </> ppTuple' (map pretty arrs) <> comma
+          </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map pretty scans) <> comma
+          </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map pretty reds) <> comma
+          </> pretty map_lam
       )
 
 -- | Prettyprint the given Stream.
 ppStream ::
-  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> [SubExp] -> Lambda rep -> Doc
+  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> [SubExp] -> Lambda rep -> Doc ann
 ppStream size arrs acc lam =
-  text "streamSeq"
-    <> parens
-      ( ppr size <> comma
-          </> ppTuple' arrs <> comma
-          </> ppTuple' acc <> comma
-          </> ppr lam
+  "streamSeq"
+    <> (parens . align)
+      ( pretty size <> comma
+          </> ppTuple' (map pretty arrs) <> comma
+          </> ppTuple' (map pretty acc) <> comma
+          </> pretty lam
       )
 
 -- | Prettyprint the given Scatter.
 ppScatter ::
-  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> Lambda rep -> [(Shape, Int, VName)] -> Doc
+  (PrettyRep rep, Pretty inp) => SubExp -> [inp] -> Lambda rep -> [(Shape, Int, VName)] -> Doc ann
 ppScatter w arrs lam dests =
   "scatter"
-    <> parens
-      ( ppr w <> comma
-          </> ppTuple' arrs <> comma
-          </> ppr lam <> comma
-          </> commasep (map ppr dests)
+    <> (parens . align)
+      ( pretty w <> comma
+          </> ppTuple' (map pretty arrs) <> comma
+          </> pretty lam <> comma
+          </> commasep (map pretty dests)
       )
 
 instance PrettyRep rep => Pretty (Scan rep) where
-  ppr (Scan scan_lam scan_nes) =
-    ppr scan_lam <> comma </> PP.braces (commasep $ map ppr scan_nes)
+  pretty (Scan scan_lam scan_nes) =
+    pretty scan_lam <> comma </> PP.braces (commasep $ map pretty scan_nes)
 
-ppComm :: Commutativity -> Doc
+ppComm :: Commutativity -> Doc ann
 ppComm Noncommutative = mempty
-ppComm Commutative = text "commutative "
+ppComm Commutative = "commutative "
 
 instance PrettyRep rep => Pretty (Reduce rep) where
-  ppr (Reduce comm red_lam red_nes) =
-    ppComm comm <> ppr red_lam <> comma
-      </> PP.braces (commasep $ map ppr red_nes)
+  pretty (Reduce comm red_lam red_nes) =
+    ppComm comm <> pretty red_lam <> comma
+      </> PP.braces (commasep $ map pretty red_nes)
 
 -- | Prettyprint the given histogram operation.
 ppHist ::
@@ -969,19 +961,19 @@
   [inp] ->
   [HistOp rep] ->
   Lambda rep ->
-  Doc
+  Doc ann
 ppHist w arrs ops bucket_fun =
-  text "hist"
+  "hist"
     <> parens
-      ( ppr w <> comma
-          </> ppTuple' arrs <> comma
+      ( pretty w <> comma
+          </> ppTuple' (map pretty arrs) <> comma
           </> PP.braces (mconcat $ intersperse (comma <> PP.line) $ map ppOp ops) <> comma
-          </> ppr bucket_fun
+          </> pretty bucket_fun
       )
   where
     ppOp (HistOp dest_w rf dests nes op) =
-      ppr dest_w <> comma
-        <+> ppr rf <> comma
-        <+> PP.braces (commasep $ map ppr dests) <> comma
-        </> ppTuple' nes <> comma
-        </> ppr op
+      pretty dest_w <> comma
+        <+> pretty rf <> comma
+        <+> PP.braces (commasep $ map pretty dests) <> comma
+        </> ppTuple' (map pretty nes) <> comma
+        </> pretty op
diff --git a/src/Futhark/IR/SOACS/Simplify.hs b/src/Futhark/IR/SOACS/Simplify.hs
--- a/src/Futhark/IR/SOACS/Simplify.hs
+++ b/src/Futhark/IR/SOACS/Simplify.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -32,17 +28,17 @@
 import Data.Foldable
 import Data.List (partition, transpose, unzip6, zip6)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Analysis.DataDependencies
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SOACS
 import Futhark.MonadFreshNames
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Optimise.Simplify.Rule
 import Futhark.Optimise.Simplify.Rules
diff --git a/src/Futhark/IR/SegOp.hs b/src/Futhark/IR/SegOp.hs
--- a/src/Futhark/IR/SegOp.hs
+++ b/src/Futhark/IR/SegOp.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -69,13 +63,13 @@
     isPrefixOf,
     partition,
   )
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Analysis.Metrics
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR
 import Futhark.IR.Aliases
   ( Aliases,
@@ -84,8 +78,8 @@
   )
 import Futhark.IR.Mem
 import Futhark.IR.Prop.Aliases
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Optimise.Simplify.Rule
 import Futhark.Tools
@@ -93,15 +87,17 @@
 import Futhark.Transform.Substitute
 import Futhark.Util (chunks, maybeNth)
 import Futhark.Util.Pretty
-  ( Pretty,
-    commasep,
+  ( Doc,
+    Pretty,
+    apply,
+    hsep,
     parens,
-    ppr,
-    text,
+    ppTuple',
+    pretty,
     (<+>),
     (</>),
   )
-import qualified Futhark.Util.Pretty as PP
+import Futhark.Util.Pretty qualified as PP
 import Prelude hiding (id, (.))
 
 -- | An operator for 'SegHist'.
@@ -336,13 +332,12 @@
   mapM_ consumeKernelResult kres
   TC.checkStms stms $ do
     unless (length ts == length kres) $
-      TC.bad $
-        TC.TypeError $
-          "Kernel return type is "
-            ++ prettyTuple ts
-            ++ ", but body returns "
-            ++ show (length kres)
-            ++ " values."
+      TC.bad . TC.TypeError $
+        "Kernel return type is "
+          <> prettyTuple ts
+          <> ", but body returns "
+          <> prettyText (length kres)
+          <> " values."
     zipWithM_ checkKernelResult kres ts
   where
     consumeKernelResult (WriteReturns _ _ arr _) =
@@ -364,13 +359,13 @@
           TC.bad $
             TC.TypeError $
               "WriteReturns returning "
-                ++ pretty e
-                ++ " of type "
-                ++ pretty t
-                ++ ", shape="
-                ++ pretty shape
-                ++ ", but destination array has type "
-                ++ pretty arr_t
+                <> prettyText e
+                <> " of type "
+                <> prettyText t
+                <> ", shape="
+                <> prettyText shape
+                <> ", but destination array has type "
+                <> prettyText arr_t
     checkKernelResult (TileReturns cs dims v) t = do
       TC.checkCerts cs
       forM_ dims $ \(dim, tile) -> do
@@ -380,7 +375,7 @@
       unless (vt == t `arrayOfShape` Shape (map snd dims)) $
         TC.bad $
           TC.TypeError $
-            "Invalid type for TileReturns " ++ pretty v
+            "Invalid type for TileReturns " <> prettyText v
     checkKernelResult (RegTileReturns cs dims_n_tiles arr) t = do
       TC.checkCerts cs
       mapM_ (TC.require [Prim int64]) dims
@@ -392,54 +387,54 @@
       unless (arr_t == expected) $
         TC.bad . TC.TypeError $
           "Invalid type for TileReturns. Expected:\n  "
-            ++ pretty expected
-            ++ ",\ngot:\n  "
-            ++ pretty arr_t
+            <> prettyText expected
+            <> ",\ngot:\n  "
+            <> prettyText arr_t
       where
         (dims, blk_tiles, reg_tiles) = unzip3 dims_n_tiles
-        expected = t `arrayOfShape` Shape (blk_tiles ++ reg_tiles)
+        expected = t `arrayOfShape` Shape (blk_tiles <> reg_tiles)
 
 kernelBodyMetrics :: OpMetrics (Op rep) => KernelBody rep -> MetricsM ()
 kernelBodyMetrics = mapM_ stmMetrics . kernelBodyStms
 
 instance PrettyRep rep => Pretty (KernelBody rep) where
-  ppr (KernelBody _ stms res) =
-    PP.stack (map ppr (stmsToList stms))
-      </> text "return"
-      <+> PP.braces (PP.commasep $ map ppr res)
+  pretty (KernelBody _ stms res) =
+    PP.stack (map pretty (stmsToList stms))
+      </> "return"
+      <+> PP.braces (PP.commasep $ map pretty res)
 
-certAnnots :: Certs -> [PP.Doc]
+certAnnots :: Certs -> [Doc ann]
 certAnnots cs
   | cs == mempty = []
-  | otherwise = [ppr cs]
+  | otherwise = [pretty cs]
 
 instance Pretty KernelResult where
-  ppr (Returns ResultNoSimplify cs what) =
-    PP.spread $ certAnnots cs ++ ["returns (manifest)" <+> ppr what]
-  ppr (Returns ResultPrivate cs what) =
-    PP.spread $ certAnnots cs ++ ["returns (private)" <+> ppr what]
-  ppr (Returns ResultMaySimplify cs what) =
-    PP.spread $ certAnnots cs ++ ["returns" <+> ppr what]
-  ppr (WriteReturns cs shape arr res) =
-    PP.spread $
+  pretty (Returns ResultNoSimplify cs what) =
+    hsep $ certAnnots cs <> ["returns (manifest)" <+> pretty what]
+  pretty (Returns ResultPrivate cs what) =
+    hsep $ certAnnots cs <> ["returns (private)" <+> pretty what]
+  pretty (Returns ResultMaySimplify cs what) =
+    hsep $ certAnnots cs <> ["returns" <+> pretty what]
+  pretty (WriteReturns cs shape arr res) =
+    hsep $
       certAnnots cs
-        ++ [ ppr arr
+        <> [ pretty arr
                <+> PP.colon
-               <+> ppr shape
+               <+> pretty shape
                </> "with"
                <+> PP.apply (map ppRes res)
            ]
     where
-      ppRes (slice, e) = ppr slice <+> text "=" <+> ppr e
-  ppr (TileReturns cs dims v) =
-    PP.spread $ certAnnots cs ++ ["tile" <> parens (commasep $ map onDim dims) <+> ppr v]
+      ppRes (slice, e) = pretty slice <+> "=" <+> pretty e
+  pretty (TileReturns cs dims v) =
+    hsep $ certAnnots cs <> ["tile" <> apply (map onDim dims) <+> pretty v]
     where
-      onDim (dim, tile) = ppr dim <+> "/" <+> ppr tile
-  ppr (RegTileReturns cs dims_n_tiles v) =
-    PP.spread $ certAnnots cs ++ ["blkreg_tile" <> parens (commasep $ map onDim dims_n_tiles) <+> ppr v]
+      onDim (dim, tile) = pretty dim <+> "/" <+> pretty tile
+  pretty (RegTileReturns cs dims_n_tiles v) =
+    hsep $ certAnnots cs <> ["blkreg_tile" <> apply (map onDim dims_n_tiles) <+> pretty v]
     where
       onDim (dim, blk_tile, reg_tile) =
-        ppr dim <+> "/" <+> parens (ppr blk_tile <+> "*" <+> ppr reg_tile)
+        pretty dim <+> "/" <+> parens (pretty blk_tile <+> "*" <+> pretty reg_tile)
 
 -- | These dimensions (indexed from 0, outermost) of the corresponding
 -- 'SegSpace' should not be parallelised, but instead iterated
@@ -653,9 +648,9 @@
         TC.bad $
           TC.TypeError $
             "SegHist operator has return type "
-              ++ prettyTuple (lambdaReturnType op)
-              ++ " but neutral element has type "
-              ++ prettyTuple nes_t
+              <> prettyTuple (lambdaReturnType op)
+              <> " but neutral element has type "
+              <> prettyTuple nes_t
 
       -- Arrays must have proper type.
       let dest_shape' = Shape segment_dims <> dest_shape <> shape
@@ -676,9 +671,9 @@
       TC.bad $
         TC.TypeError $
           "SegHist body has return type "
-            ++ prettyTuple ts
-            ++ " but should have type "
-            ++ prettyTuple bucket_ret_t
+            <> prettyTuple ts
+            <> " but should have type "
+            <> prettyTuple bucket_ret_t
   where
     segment_dims = init $ segSpaceDims space
 
@@ -714,10 +709,10 @@
       TC.bad $
         TC.TypeError $
           "Wrong return for body (does not match neutral elements; expected "
-            ++ pretty expecting
-            ++ "; found "
-            ++ pretty got
-            ++ ")"
+            <> prettyText expecting
+            <> "; found "
+            <> prettyText got
+            <> ")"
 
     checkKernelBody ts kbody
 
@@ -886,60 +881,60 @@
       kernelBodyMetrics body
 
 instance Pretty SegSpace where
-  ppr (SegSpace phys dims) =
-    parens
-      ( commasep $ do
+  pretty (SegSpace phys dims) =
+    apply
+      ( do
           (i, d) <- dims
-          pure $ ppr i <+> "<" <+> ppr d
+          pure $ pretty i <+> "<" <+> pretty d
       )
-      <+> parens (text "~" <> ppr phys)
+      <+> parens ("~" <> pretty phys)
 
 instance PrettyRep rep => Pretty (SegBinOp rep) where
-  ppr (SegBinOp comm lam nes shape) =
-    PP.braces (PP.commasep $ map ppr nes) <> PP.comma
-      </> ppr shape <> PP.comma
-      </> comm' <> ppr lam
+  pretty (SegBinOp comm lam nes shape) =
+    PP.braces (PP.commasep $ map pretty nes) <> PP.comma
+      </> pretty shape <> PP.comma
+      </> comm' <> pretty lam
     where
       comm' = case comm of
-        Commutative -> text "commutative "
+        Commutative -> "commutative "
         Noncommutative -> mempty
 
 instance (PrettyRep rep, PP.Pretty lvl) => PP.Pretty (SegOp lvl rep) where
-  ppr (SegMap lvl space ts body) =
-    text "segmap" <> ppr lvl
-      </> PP.align (ppr space)
+  pretty (SegMap lvl space ts body) =
+    "segmap" <> pretty lvl
+      </> PP.align (pretty space)
       <+> PP.colon
-      <+> ppTuple' ts
-      <+> PP.nestedBlock "{" "}" (ppr body)
-  ppr (SegRed lvl space reds ts body) =
-    text "segred" <> ppr lvl
-      </> PP.align (ppr space)
-      </> PP.parens (mconcat $ intersperse (PP.comma <> PP.line) $ map ppr reds)
+      <+> ppTuple' (map pretty ts)
+      <+> PP.nestedBlock "{" "}" (pretty body)
+  pretty (SegRed lvl space reds ts body) =
+    "segred" <> pretty lvl
+      </> PP.align (pretty space)
+      </> PP.parens (mconcat $ intersperse (PP.comma <> PP.line) $ map pretty reds)
       </> PP.colon
-      <+> ppTuple' ts
-      <+> PP.nestedBlock "{" "}" (ppr body)
-  ppr (SegScan lvl space scans ts body) =
-    text "segscan" <> ppr lvl
-      </> PP.align (ppr space)
-      </> PP.parens (mconcat $ intersperse (PP.comma <> PP.line) $ map ppr scans)
+      <+> ppTuple' (map pretty ts)
+      <+> PP.nestedBlock "{" "}" (pretty body)
+  pretty (SegScan lvl space scans ts body) =
+    "segscan" <> pretty lvl
+      </> PP.align (pretty space)
+      </> PP.parens (mconcat $ intersperse (PP.comma <> PP.line) $ map pretty scans)
       </> PP.colon
-      <+> ppTuple' ts
-      <+> PP.nestedBlock "{" "}" (ppr body)
-  ppr (SegHist lvl space ops ts body) =
-    text "seghist" <> ppr lvl
-      </> PP.align (ppr space)
+      <+> ppTuple' (map pretty ts)
+      <+> PP.nestedBlock "{" "}" (pretty body)
+  pretty (SegHist lvl space ops ts body) =
+    "seghist" <> pretty lvl
+      </> PP.align (pretty space)
       </> PP.parens (mconcat $ intersperse (PP.comma <> PP.line) $ map ppOp ops)
       </> PP.colon
-      <+> ppTuple' ts
-      <+> PP.nestedBlock "{" "}" (ppr body)
+      <+> ppTuple' (map pretty ts)
+      <+> PP.nestedBlock "{" "}" (pretty body)
     where
       ppOp (HistOp w rf dests nes shape op) =
-        ppr w <> PP.comma
-          <+> ppr rf <> PP.comma
-          </> PP.braces (PP.commasep $ map ppr dests) <> PP.comma
-          </> PP.braces (PP.commasep $ map ppr nes) <> PP.comma
-          </> ppr shape <> PP.comma
-          </> ppr op
+        pretty w <> PP.comma
+          <+> pretty rf <> PP.comma
+          </> PP.braces (PP.commasep $ map pretty dests) <> PP.comma
+          </> PP.braces (PP.commasep $ map pretty nes) <> PP.comma
+          </> pretty shape <> PP.comma
+          </> pretty op
 
 instance
   ( ASTRep rep,
diff --git a/src/Futhark/IR/Seq.hs b/src/Futhark/IR/Seq.hs
--- a/src/Futhark/IR/Seq.hs
+++ b/src/Futhark/IR/Seq.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | A sequential representation.
@@ -22,9 +21,9 @@
 import Futhark.IR.Prop
 import Futhark.IR.Syntax
 import Futhark.IR.Traversals
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify as Simplify
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify qualified as Simplify
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rules
 import Futhark.Pass
 
diff --git a/src/Futhark/IR/SeqMem.hs b/src/Futhark/IR/SeqMem.hs
--- a/src/Futhark/IR/SeqMem.hs
+++ b/src/Futhark/IR/SeqMem.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.IR.SeqMem
@@ -19,8 +15,8 @@
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.IR.Mem
 import Futhark.IR.Mem.Simplify
-import qualified Futhark.IR.TypeCheck as TC
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.IR.TypeCheck qualified as TC
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Pass
 import Futhark.Pass.ExplicitAllocations (BuilderOps (..), mkLetNamesB', mkLetNamesB'')
 
diff --git a/src/Futhark/IR/Syntax.hs b/src/Futhark/IR/Syntax.hs
--- a/src/Futhark/IR/Syntax.hs
+++ b/src/Futhark/IR/Syntax.hs
@@ -1,8 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | = Definition of the Futhark core language IR
@@ -102,7 +98,8 @@
 -- for what is likely the simplest example.
 module Futhark.IR.Syntax
   ( module Language.Futhark.Core,
-    pretty,
+    prettyString,
+    prettyText,
     module Futhark.IR.Rep,
     module Futhark.IR.Syntax.Core,
 
@@ -169,12 +166,12 @@
 import Control.Category
 import Data.Foldable
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Sequence as Seq
-import Data.String
+import Data.Sequence qualified as Seq
+import Data.Text qualified as T
 import Data.Traversable (fmapDefault, foldMapDefault)
 import Futhark.IR.Rep
 import Futhark.IR.Syntax.Core
-import Futhark.Util.Pretty (pretty)
+import Futhark.Util.Pretty (prettyString, prettyText)
 import Language.Futhark.Core
 import Prelude hiding (id, (.))
 
@@ -303,7 +300,7 @@
   = -- | No special operation.
     OpaqueNil
   | -- | Print the argument, prefixed by this string.
-    OpaqueTrace String
+    OpaqueTrace T.Text
   deriving (Eq, Ord, Show)
 
 -- | Which kind of reshape is this?
diff --git a/src/Futhark/IR/Syntax/Core.hs b/src/Futhark/IR/Syntax/Core.hs
--- a/src/Futhark/IR/Syntax/Core.hs
+++ b/src/Futhark/IR/Syntax/Core.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
 
 -- | The most primitive ("core") aspects of the AST.  Split out of
@@ -83,10 +79,11 @@
 import Data.Bifoldable
 import Data.Bifunctor
 import Data.Bitraversable
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Data.String
+import Data.Text qualified as T
 import Data.Traversable (fmapDefault, foldMapDefault)
 import Language.Futhark.Core
 import Language.Futhark.Primitive
@@ -500,13 +497,13 @@
 -- | A part of an error message.
 data ErrorMsgPart a
   = -- | A literal string.
-    ErrorString String
+    ErrorString T.Text
   | -- | A run-time value.
     ErrorVal PrimType a
   deriving (Eq, Ord, Show)
 
 instance IsString (ErrorMsgPart a) where
-  fromString = ErrorString
+  fromString = ErrorString . T.pack
 
 instance Functor ErrorMsg where
   fmap f (ErrorMsg parts) = ErrorMsg $ map (fmap f) parts
diff --git a/src/Futhark/IR/TypeCheck.hs b/src/Futhark/IR/TypeCheck.hs
--- a/src/Futhark/IR/TypeCheck.hs
+++ b/src/Futhark/IR/TypeCheck.hs
@@ -1,13 +1,5 @@
 {-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | The type checker checks whether the program is type-consistent.
@@ -57,19 +49,20 @@
 import Control.Parallel.Strategies
 import Data.List (find, intercalate, isPrefixOf, sort)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Futhark.Analysis.PrimExp
 import Futhark.Construct (instantiateShapes)
 import Futhark.IR.Aliases hiding (lookupAliases)
 import Futhark.Util
-import Futhark.Util.Pretty (Pretty, align, indent, ppr, prettyDoc, text, (<+>), (</>))
+import Futhark.Util.Pretty (Pretty, align, docText, indent, ppTuple', pretty, (<+>), (</>))
 
 -- | Information about an error during type checking.  The 'Show'
 -- instance for this type produces a human-readable description.
 data ErrorCase rep
-  = TypeError String
+  = TypeError T.Text
   | UnexpectedType (Exp rep) Type [Type]
   | ReturnTypeError Name [ExtType] [ExtType]
   | DupDefinitionError Name
@@ -88,48 +81,48 @@
 
 instance Checkable rep => Show (ErrorCase rep) where
   show (TypeError msg) =
-    "Type error:\n" ++ msg
+    "Type error:\n" ++ T.unpack msg
   show (UnexpectedType e _ []) =
     "Type of expression\n"
-      ++ prettyDoc 160 (indent 2 $ ppr e)
+      ++ T.unpack (docText $ indent 2 $ pretty e)
       ++ "\ncannot have any type - possibly a bug in the type checker."
   show (UnexpectedType e t ts) =
     "Type of expression\n"
-      ++ prettyDoc 160 (indent 2 $ ppr e)
+      ++ T.unpack (docText $ indent 2 $ pretty e)
       ++ "\nmust be one of "
-      ++ intercalate ", " (map pretty ts)
+      ++ intercalate ", " (map prettyString ts)
       ++ ", but is "
-      ++ pretty t
+      ++ prettyString t
       ++ "."
   show (ReturnTypeError fname rettype bodytype) =
     "Declaration of function "
       ++ nameToString fname
       ++ " declares return type\n  "
-      ++ prettyTuple rettype
+      ++ T.unpack (prettyTuple rettype)
       ++ "\nBut body has type\n  "
-      ++ prettyTuple bodytype
+      ++ T.unpack (prettyTuple bodytype)
   show (DupDefinitionError name) =
     "Duplicate definition of function " ++ nameToString name ++ ""
   show (DupParamError funname paramname) =
     "Parameter "
-      ++ pretty paramname
+      ++ prettyString paramname
       ++ " mentioned multiple times in argument list of function "
       ++ nameToString funname
       ++ "."
   show (DupPatError name) =
-    "Variable " ++ pretty name ++ " bound twice in pattern."
+    "Variable " ++ prettyString name ++ " bound twice in pattern."
   show (InvalidPatError pat t desc) =
     "Pat\n"
-      ++ pretty pat
+      ++ prettyString pat
       ++ "\ncannot match value of type\n"
-      ++ prettyTupleLines t
+      ++ T.unpack (prettyTupleLines t)
       ++ end
     where
       end = case desc of
         Nothing -> "."
         Just desc' -> ":\n" ++ desc'
   show (UnknownVariableError name) =
-    "Use of unknown variable " ++ pretty name ++ "."
+    "Use of unknown variable " ++ prettyString name ++ "."
   show (UnknownFunctionError fname) =
     "Call of unknown function " ++ nameToString fname ++ "."
   show (ParameterMismatch fname expected got) =
@@ -139,11 +132,11 @@
       ++ "expecting "
       ++ show nexpected
       ++ " arguments of type(s)\n"
-      ++ intercalate ", " (map pretty expected)
+      ++ intercalate ", " (map prettyString expected)
       ++ "\nGot "
       ++ show ngot
       ++ " arguments of types\n"
-      ++ intercalate ", " (map pretty got)
+      ++ intercalate ", " (map prettyString got)
     where
       nexpected = length expected
       ngot = length got
@@ -154,15 +147,15 @@
     "Annotation of \""
       ++ desc
       ++ "\" type of expression is "
-      ++ pretty expected
+      ++ prettyString expected
       ++ ", but derived to be "
-      ++ pretty got
+      ++ prettyString got
       ++ "."
   show (ReturnAliased fname name) =
     "Unique return value of function "
       ++ nameToString fname
       ++ " is aliased to "
-      ++ pretty name
+      ++ prettyString name
       ++ ", which is not consumed."
   show (UniqueReturnAliased fname) =
     "A unique tuple element of return value of function "
@@ -170,9 +163,9 @@
       ++ " is aliased to some other tuple component."
   show (NotAnArray e t) =
     "The expression "
-      ++ pretty e
+      ++ prettyString e
       ++ " is expected to be an array, but is "
-      ++ pretty t
+      ++ prettyString t
       ++ "."
   show (PermutationError perm rank name) =
     "The permutation ("
@@ -183,16 +176,16 @@
       ++ show rank
       ++ "."
     where
-      name' = maybe "" ((++ " ") . pretty) name
+      name' = maybe "" ((++ " ") . prettyString) name
 
 -- | A type error.
-data TypeError rep = Error [String] (ErrorCase rep)
+data TypeError rep = Error [T.Text] (ErrorCase rep)
 
 instance Checkable rep => Show (TypeError rep) where
   show (Error [] err) =
     show err
   show (Error msgs err) =
-    intercalate "\n" msgs ++ "\n" ++ show err
+    intercalate "\n" (map T.unpack msgs) ++ "\n" ++ show err
 
 -- | A tuple of a return type and a list of parameters, possibly
 -- named.
@@ -256,7 +249,7 @@
 -- | The 'Consumption' data structure is used to keep track of which
 -- variables have been consumed, as well as whether a violation has been detected.
 data Consumption
-  = ConsumptionError String
+  = ConsumptionError T.Text
   | Consumption Occurences
   deriving (Show)
 
@@ -265,7 +258,7 @@
   _ <> ConsumptionError e = ConsumptionError e
   Consumption o1 <> Consumption o2
     | v : _ <- namesToList $ consumed_in_o1 `namesIntersection` used_in_o2 =
-        ConsumptionError $ "Variable " <> pretty v <> " referenced after being consumed."
+        ConsumptionError $ "Variable " <> prettyText v <> " referenced after being consumed."
     | otherwise =
         Consumption $ o1 `seqOccurences` o2
     where
@@ -284,7 +277,7 @@
   { envVtable :: M.Map VName (VarBinding rep),
     envFtable :: M.Map Name (FunBinding rep),
     envCheckOp :: OpWithAliases (Op rep) -> TypeM rep (),
-    envContext :: [String]
+    envContext :: [T.Text]
   }
 
 data TState = TState
@@ -333,29 +326,21 @@
 -- type errors, as the strings are added to type errors signalled via
 -- 'bad'.
 context ::
-  String ->
+  T.Text ->
   TypeM rep a ->
   TypeM rep a
 context s = local $ \env -> env {envContext = s : envContext env}
 
-message ::
-  Pretty a =>
-  String ->
-  a ->
-  String
-message s x =
-  prettyDoc 80 $
-    text s <+> align (ppr x)
+message :: Pretty a => T.Text -> a -> T.Text
+message s x = docText $ pretty s <+> align (pretty x)
 
 -- | Mark a name as bound.  If the name has been bound previously in
 -- the program, report a type error.
 bound :: VName -> TypeM rep ()
 bound name = do
   already_seen <- gets $ nameIn name . stateNames
-  when already_seen $
-    bad $
-      TypeError $
-        "Name " ++ pretty name ++ " bound twice"
+  when already_seen . bad . TypeError $
+    "Name " <> prettyText name <> " bound twice"
   modify $ \s -> s {stateNames = oneName name <> stateNames s}
 
 occur :: Occurences -> TypeM rep ()
@@ -425,13 +410,13 @@
     wasConsumed v
       | Just als <- lookup v consumable = pure als
       | otherwise =
-          bad . TypeError . unlines $
-            [ pretty v ++ " was invalidly consumed.",
-              what ++ " can be consumed here."
+          bad . TypeError . T.unlines $
+            [ prettyText v <> " was invalidly consumed.",
+              what <> " can be consumed here."
             ]
     what
       | null consumable = "Nothing"
-      | otherwise = "Only " ++ intercalate ", " (map (pretty . fst) consumable)
+      | otherwise = "Only " <> T.intercalate ", " (map (prettyText . fst) consumable)
 
 -- | Given the immediate aliases, compute the full transitive alias
 -- set (including the immediate aliases).
@@ -557,9 +542,9 @@
       pure (ispace, ts)
     _ ->
       bad . TypeError $
-        pretty v
-          ++ " should be an accumulator but is of type "
-          ++ pretty t
+        prettyText v
+          <> " should be an accumulator but is of type "
+          <> prettyText t
 
 checkOpaques :: OpaqueTypes -> Either (TypeError rep) ()
 checkOpaques (OpaqueTypes types) = descend [] types
@@ -575,7 +560,7 @@
     checkEntryPointType known (TypeOpaque s) =
       when (s `notElem` known) $
         Left . Error [] . TypeError $
-          "Opaque not defined before first use: " <> s
+          "Opaque not defined before first use: " <> T.pack s
     checkEntryPointType _ (TypeTransparent _) = pure ()
 
 -- | Type check a program containing arbitrary type information,
@@ -629,7 +614,7 @@
   FunDef (Aliases rep) ->
   TypeM rep ()
 checkFun (FunDef _ _ fname rettype params body) =
-  context ("In function " ++ nameToString fname)
+  context ("In function " <> nameToText fname)
     $ checkFun'
       ( fname,
         map declExtTypeOf rettype,
@@ -662,7 +647,7 @@
   [FParam rep] ->
   TypeM rep ()
 checkFunParams = mapM_ $ \param ->
-  context ("In function parameter " ++ pretty param) $
+  context ("In function parameter " <> prettyText param) $
     checkFParamDec (paramName param) (paramDec param)
 
 checkLambdaParams ::
@@ -670,7 +655,7 @@
   [LParam rep] ->
   TypeM rep ()
 checkLambdaParams = mapM_ $ \param ->
-  context ("In lambda parameter " ++ pretty param) $
+  context ("In lambda parameter " <> prettyText param) $
     checkLParamDec (paramName param) (paramDec param)
 
 checkFun' ::
@@ -691,7 +676,7 @@
       let isArray = maybe False ((> 0) . arrayRank . typeOf) . (`M.lookup` scope)
       context
         ( "When checking the body aliases: "
-            ++ pretty (map namesToList body_aliases)
+            <> prettyText (map namesToList body_aliases)
         )
         $ checkReturnAlias
         $ map (namesFromList . filter isArray . namesToList) body_aliases
@@ -729,7 +714,7 @@
 checkSubExp :: Checkable rep => SubExp -> TypeM rep Type
 checkSubExp (Constant val) =
   pure $ Prim $ primValueType val
-checkSubExp (Var ident) = context ("In subexp " ++ pretty ident) $ do
+checkSubExp (Var ident) = context ("In subexp " <> prettyText ident) $ do
   observe ident
   lookupType ident
 
@@ -749,7 +734,7 @@
 checkStms origstms m = delve $ stmsToList origstms
   where
     delve (stm@(Let pat _ e) : stms) = do
-      context (pretty $ "In expression of statement" </> indent 2 (ppr pat)) $
+      context (docText $ "In expression of statement" </> indent 2 (pretty pat)) $
         checkExp e
       checkStm stm $
         delve stms
@@ -799,22 +784,22 @@
   | length ts /= length es =
       bad . TypeError $
         "Lambda has return type "
-          ++ prettyTuple ts
-          ++ " describing "
-          ++ show (length ts)
-          ++ " values, but body returns "
-          ++ show (length es)
-          ++ " values: "
-          ++ prettyTuple es
+          <> prettyTuple ts
+          <> " describing "
+          <> prettyText (length ts)
+          <> " values, but body returns "
+          <> prettyText (length es)
+          <> " values: "
+          <> prettyTuple es
   | otherwise = forM_ (zip ts es) $ \(t, e) -> do
       et <- checkSubExpRes e
       unless (et == t) . bad . TypeError $
         "Subexpression "
-          ++ pretty e
-          ++ " has type "
-          ++ pretty et
-          ++ " but expected "
-          ++ pretty t
+          <> prettyText e
+          <> " has type "
+          <> prettyText et
+          <> " but expected "
+          <> prettyText t
 
 checkBody ::
   Checkable rep =>
@@ -839,10 +824,10 @@
   let check elemt eleme = do
         elemet <- checkSubExp eleme
         unless (elemet == elemt) . bad . TypeError $
-          pretty elemet
-            ++ " is not of expected type "
-            ++ pretty elemt
-            ++ "."
+          prettyText elemet
+            <> " is not of expected type "
+            <> prettyText elemt
+            <> "."
   et <- checkSubExp e
 
   -- Compare that type with the one given for the array literal.
@@ -928,10 +913,10 @@
     bad $
       TypeError $
         "Cannot rotate "
-          ++ show (length rots)
-          ++ " dimensions of "
-          ++ show rank
-          ++ "-dimensional array."
+          <> prettyText (length rots)
+          <> " dimensions of "
+          <> prettyText rank
+          <> "-dimensional array."
 checkBasicOp (Concat i (arr1exp :| arr2exps) ressize) = do
   arr1_dims <- shapeDims . fst <$> checkArrIdent arr1exp
   arr2s_dims <- map (shapeDims . fst) <$> mapM checkArrIdent arr2exps
@@ -954,18 +939,18 @@
 
   unless (length ses == length ts) . bad . TypeError $
     "Accumulator requires "
-      ++ show (length ts)
-      ++ " values, but "
-      ++ show (length ses)
-      ++ " provided."
+      <> prettyText (length ts)
+      <> " values, but "
+      <> prettyText (length ses)
+      <> " provided."
 
   unless (length is == shapeRank shape) $
     bad . TypeError $
       "Accumulator requires "
-        ++ show (shapeRank shape)
-        ++ " indices, but "
-        ++ show (length is)
-        ++ " provided."
+        <> prettyText (shapeRank shape)
+        <> " indices, but "
+        <> prettyText (length is)
+        <> " provided."
 
   zipWithM_ require (map pure ts) ses
   consume =<< lookupAliases acc
@@ -1011,22 +996,22 @@
     checkVal _ Nothing = True
     checkCase ses_ts (Case vs body) = do
       let ok = length vs == length ses_ts && and (zipWith checkVal ses_ts vs)
-      unless ok . bad . TypeError . pretty $
+      unless ok . bad . TypeError . docText $
         "Scrutinee"
-          </> indent 2 (ppTuple' ses)
+          </> indent 2 (ppTuple' $ map pretty ses)
           </> "cannot match pattern"
-          </> indent 2 (ppTuple' vs)
+          </> indent 2 (ppTuple' $ map pretty vs)
       context ("in body of case " <> prettyTuple vs) $ checkCaseBody body
     checkCaseBody = matchBranchType (matchReturns info)
 checkExp (Apply fname args rettype_annot _) = do
   (rettype_derived, paramtypes) <- lookupFun fname $ map fst args
   argflows <- mapM (checkArg . fst) args
   when (rettype_derived /= rettype_annot) $
-    bad . TypeError . pretty $
+    bad . TypeError . docText $
       "Expected apply result type:"
-        </> indent 2 (ppr rettype_derived)
+        </> indent 2 (pretty rettype_derived)
         </> "But annotation is:"
-        </> indent 2 (ppr rettype_annot)
+        </> indent 2 (pretty rettype_annot)
   consumeArgs paramtypes argflows
 checkExp (DoLoop merge form loopbody) = do
   let (mergepat, mergeexps) = unzip merge
@@ -1079,19 +1064,19 @@
           unless (a_t_r `subtypeOf` typeOf (paramDec p)) $
             bad . TypeError $
               "Loop parameter "
-                ++ pretty p
-                ++ " not valid for element of "
-                ++ pretty a
-                ++ ", which has row type "
-                ++ pretty a_t_r
+                <> prettyText p
+                <> " not valid for element of "
+                <> prettyText a
+                <> ", which has row type "
+                <> prettyText a_t_r
           als <- lookupAliases a
           pure (paramName p, als)
         _ ->
           bad . TypeError $
             "Cannot loop over "
-              ++ pretty a
-              ++ " of type "
-              ++ pretty a_t
+              <> prettyText a
+              <> " of type "
+              <> prettyText a_t
     checkForm mergeargs (ForLoop loopvar it boundexp loopvars) = do
       iparam <- primFParam loopvar $ IntType it
       let mergepat = map fst merge
@@ -1108,14 +1093,14 @@
           unless (paramType condparam == Prim Bool) $
             bad . TypeError $
               "Conditional '"
-                ++ pretty cond
-                ++ "' of while-loop is not boolean, but "
-                ++ pretty (paramType condparam)
-                ++ "."
+                <> prettyText cond
+                <> "' of while-loop is not boolean, but "
+                <> prettyText (paramType condparam)
+                <> "."
         Nothing ->
           bad $
             TypeError $
-              "Conditional '" ++ pretty cond ++ "' of while-loop is not a merge variable."
+              "Conditional '" <> prettyText cond <> "' of while-loop is not a merge variable."
       let mergepat = map fst merge
           funparams = mergepat
           paramts = map paramDeclType funparams
@@ -1128,20 +1113,19 @@
       argtypes <- mapM subExpType args
 
       let expected = expectedTypes (map paramName params) params args
-      unless (expected == argtypes) . bad . TypeError . pretty $
+      unless (expected == argtypes) . bad . TypeError . docText $
         "Loop parameters"
-          </> indent 2 (ppTuple' params)
+          </> indent 2 (ppTuple' $ map pretty params)
           </> "cannot accept initial values"
-          </> indent 2 (ppTuple' args)
+          </> indent 2 (ppTuple' $ map pretty args)
           </> "of types"
-          </> indent 2 (ppTuple' argtypes)
+          </> indent 2 (ppTuple' $ map pretty argtypes)
 checkExp (WithAcc inputs lam) = do
-  unless (length (lambdaParams lam) == 2 * num_accs) $
-    bad . TypeError $
-      show (length (lambdaParams lam))
-        ++ " parameters, but "
-        ++ show num_accs
-        ++ " accumulators."
+  unless (length (lambdaParams lam) == 2 * num_accs) . bad . TypeError $
+    prettyText (length (lambdaParams lam))
+      <> " parameters, but "
+      <> prettyText num_accs
+      <> " accumulators."
 
   let cert_params = take num_accs $ lambdaParams lam
   acc_args <- forM (zip inputs cert_params) $ \((shape, arrs, op), p) -> do
@@ -1150,7 +1134,7 @@
       arr_t <- lookupType arr
       unless (shapeDims shape `isPrefixOf` arrayDims arr_t) $
         bad . TypeError $
-          pretty arr <> " is not an array of outer shape " <> pretty shape
+          prettyText arr <> " is not an array of outer shape " <> prettyText shape
       consume =<< lookupAliases arr
       pure $ stripArray (shapeRank shape) arr_t
 
@@ -1159,12 +1143,10 @@
         let mkArrArg t = (t, mempty)
         nes_ts <- mapM checkSubExp nes
         unless (nes_ts == lambdaReturnType op_lam) $
-          bad $
-            TypeError $
-              unlines
-                [ "Accumulator operator return type: " ++ pretty (lambdaReturnType op_lam),
-                  "Type of neutral elements: " ++ pretty nes_ts
-                ]
+          bad . TypeError . T.unlines $
+            [ "Accumulator operator return type: " <> prettyText (lambdaReturnType op_lam),
+              "Type of neutral elements: " <> prettyText nes_ts
+            ]
         checkLambda op_lam $
           replicate (shapeRank shape) (Prim int64, mempty)
             ++ map mkArrArg (elem_ts ++ elem_ts)
@@ -1195,15 +1177,15 @@
           let argSize = arraySize 0 t
           unless (argSize == width) . bad . TypeError $
             "SOAC argument "
-              ++ pretty v
-              ++ " has outer size "
-              ++ pretty argSize
-              ++ ", but width of SOAC is "
-              ++ pretty width
+              <> prettyText v
+              <> " has outer size "
+              <> prettyText argSize
+              <> ", but width of SOAC is "
+              <> prettyText width
           pure (rowType t, als)
         _ ->
           bad . TypeError $
-            "SOAC argument " ++ pretty v ++ " is not an array"
+            "SOAC argument " <> prettyText v <> " is not an array"
 
 checkType ::
   Checkable rep =>
@@ -1258,7 +1240,7 @@
   PatElem (LetDec rep) ->
   TypeM rep ()
 checkPatElem (PatElem name dec) =
-  context ("When checking pattern element " ++ pretty name) $
+  context ("When checking pattern element " <> prettyText name) $
     checkLetBoundDec name dec
 
 checkFlatDimIndex ::
@@ -1290,7 +1272,7 @@
 checkStm stm@(Let pat (StmAux (Certs cs) _ (_, dec)) e) m = do
   context "When checking certificates" $ mapM_ (requireI [Prim Unit]) cs
   context "When checking expression annotation" $ checkExpDec dec
-  context ("When matching\n" ++ message "  " pat ++ "\nwith\n" ++ message "  " e) $
+  context ("When matching\n" <> message "  " pat <> "\nwith\n" <> message "  " e) $
     matchPat pat e
   binding (maybeWithoutAliases $ scopeOf stm) $ do
     mapM_ checkPatElem (patElems $ removePatAliases pat)
@@ -1344,11 +1326,11 @@
 matchExtReturns rettype res ts = do
   let problem :: TypeM rep a
       problem =
-        bad . TypeError . unlines $
+        bad . TypeError . T.unlines $
           [ "Type annotation is",
-            "  " ++ prettyTuple rettype,
+            "  " <> prettyTuple rettype,
             "But result returns type",
-            "  " ++ prettyTuple ts
+            "  " <> prettyTuple ts
           ]
 
   unless (length res == length rettype) problem
@@ -1450,12 +1432,12 @@
     else
       bad . TypeError $
         "Anonymous function defined with "
-          ++ show (length params)
-          ++ " parameters:\n"
-          ++ pretty params
-          ++ "\nbut expected to take "
-          ++ show (length args)
-          ++ " arguments."
+          <> prettyText (length params)
+          <> " parameters:\n"
+          <> prettyText params
+          <> "\nbut expected to take "
+          <> prettyText (length args)
+          <> " arguments."
 
 checkLambda :: Checkable rep => Lambda (Aliases rep) -> [Arg] -> TypeM rep ()
 checkLambda = checkAnyLambda True
@@ -1474,27 +1456,27 @@
 checkPrimExp (FunExp h args t) = do
   (h_ts, h_ret, _) <-
     maybe
-      (bad $ TypeError $ "Unknown function: " ++ h)
+      (bad $ TypeError $ "Unknown function: " <> T.pack h)
       pure
       $ M.lookup h primFuns
   when (length h_ts /= length args) . bad . TypeError $
     "Function expects "
-      ++ show (length h_ts)
-      ++ " parameters, but given "
-      ++ show (length args)
-      ++ " arguments."
+      <> prettyText (length h_ts)
+      <> " parameters, but given "
+      <> prettyText (length args)
+      <> " arguments."
   when (h_ret /= t) . bad . TypeError $
     "Function return annotation is "
-      ++ pretty t
-      ++ ", but expected "
-      ++ pretty h_ret
+      <> prettyText t
+      <> ", but expected "
+      <> prettyText h_ret
   zipWithM_ requirePrimExp h_ts args
 
 requirePrimExp :: Checkable rep => PrimType -> PrimExp VName -> TypeM rep ()
-requirePrimExp t e = context ("in PrimExp " ++ pretty e) $ do
+requirePrimExp t e = context ("in PrimExp " <> prettyText e) $ do
   checkPrimExp e
   unless (primExpType e == t) . bad . TypeError $
-    pretty e ++ " must have type " ++ pretty t
+    prettyText e <> " must have type " <> prettyText t
 
 class ASTRep rep => CheckableOp rep where
   checkOp :: OpWithAliases (Op rep) -> TypeM rep ()
diff --git a/src/Futhark/Internalise.hs b/src/Futhark/Internalise.hs
--- a/src/Futhark/Internalise.hs
+++ b/src/Futhark/Internalise.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
 
 -- |
@@ -42,13 +41,13 @@
 --   than in the source language.  See 'Futhark.IR.SOACS.SOAC.SOAC'.
 module Futhark.Internalise (internaliseProg) where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Compiler.Config
 import Futhark.IR.SOACS as I hiding (stmPat)
 import Futhark.Internalise.Defunctionalise as Defunctionalise
 import Futhark.Internalise.Defunctorise as Defunctorise
 import Futhark.Internalise.Entry (visibleTypes)
-import qualified Futhark.Internalise.Exps as Exps
+import Futhark.Internalise.Exps qualified as Exps
 import Futhark.Internalise.LiftLambdas as LiftLambdas
 import Futhark.Internalise.Monad as I
 import Futhark.Internalise.Monomorphise as Monomorphise
diff --git a/src/Futhark/Internalise/AccurateSizes.hs b/src/Futhark/Internalise/AccurateSizes.hs
--- a/src/Futhark/Internalise/AccurateSizes.hs
+++ b/src/Futhark/Internalise/AccurateSizes.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 module Futhark.Internalise.AccurateSizes
   ( argShapes,
     ensureResultShape,
@@ -11,7 +9,7 @@
 where
 
 import Control.Monad
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Construct
 import Futhark.IR.SOACS
@@ -48,7 +46,7 @@
   let addShape name =
         case M.lookup name mapping of
           Just se -> se
-          _ -> error $ "argShapes: " ++ pretty name
+          _ -> error $ "argShapes: " ++ prettyString name
   pure $ map addShape shapes
 
 ensureResultShape ::
diff --git a/src/Futhark/Internalise/Bindings.hs b/src/Futhark/Internalise/Bindings.hs
--- a/src/Futhark/Internalise/Bindings.hs
+++ b/src/Futhark/Internalise/Bindings.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE Strict #-}
 
 -- | Internalising bindings.
@@ -14,9 +13,9 @@
 
 import Control.Monad.Reader hiding (mapM)
 import Data.Bifunctor
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.IR.SOACS as I
+import Futhark.IR.SOACS qualified as I
 import Futhark.Internalise.Monad
 import Futhark.Internalise.TypesValues
 import Futhark.Util
diff --git a/src/Futhark/Internalise/Defunctionalise.hs b/src/Futhark/Internalise/Defunctionalise.hs
--- a/src/Futhark/Internalise/Defunctionalise.hs
+++ b/src/Futhark/Internalise/Defunctionalise.hs
@@ -1,12 +1,7 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Defunctionalization of typed, monomorphic Futhark programs without modules.
 module Futhark.Internalise.Defunctionalise (transformProg) where
 
-import qualified Control.Arrow as Arrow
+import Control.Arrow qualified as Arrow
 import Control.Monad.Identity
 import Control.Monad.Reader
 import Control.Monad.State
@@ -14,10 +9,10 @@
 import Data.Bitraversable
 import Data.Foldable
 import Data.List (partition, sortOn, tails)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.Pretty ()
 import Futhark.MonadFreshNames
 import Language.Futhark
@@ -422,12 +417,12 @@
     )
   where
     closureFromDynamicFun (vn, Binding _ (DynamicFun (clsr_env, sv) _)) =
-      let name = nameFromString $ pretty vn
+      let name = nameFromString $ prettyString vn
        in ( RecordFieldExplicit name clsr_env mempty,
             (vn, Binding Nothing sv)
           )
     closureFromDynamicFun (vn, Binding _ sv) =
-      let name = nameFromString $ pretty vn
+      let name = nameFromString $ prettyString vn
           tp' = typeFromSV sv
        in ( RecordFieldExplicit
               name
@@ -532,7 +527,7 @@
       t' = first (runIdentity . astMap mapper) $ typeOf e2'
   pure (AppExp (LetPat sizes pat' e1' e2' loc) (Info (AppRes t' retext)), sv2)
 defuncExp (AppExp (LetFun vn _ _ _) _) =
-  error $ "defuncExp: Unexpected LetFun: " ++ prettyName vn
+  error $ "defuncExp: Unexpected LetFun: " ++ show vn
 defuncExp (AppExp (If e1 e2 e3 loc) res) = do
   (e1', _) <- defuncExp e1
   (e2', sv) <- defuncExp e2
@@ -582,7 +577,7 @@
     envFromIdent (Ident vn (Info tp) _) =
       M.singleton vn $ Binding Nothing $ Dynamic tp
 defuncExp e@(AppExp BinOp {} _) =
-  error $ "defuncExp: unexpected binary operator: " ++ pretty e
+  error $ "defuncExp: unexpected binary operator: " ++ prettyString e
 defuncExp (Project vn e0 tp@(Info tp') loc) = do
   (e0', sv0) <- defuncExp e0
   case sv0 of
@@ -664,9 +659,9 @@
 defuncExp (Constr name _ (Info t) loc) =
   error $
     "Constructor "
-      ++ pretty name
+      ++ prettyString name
       ++ " given type "
-      ++ pretty t
+      ++ prettyString t
       ++ " at "
       ++ locStr loc
 defuncExp (AppExp (Match e cs loc) res) = do
@@ -954,7 +949,7 @@
     _ ->
       error $
         "Application of an expression\n"
-          ++ pretty e1
+          ++ prettyString e1
           ++ "\nthat is neither a static lambda "
           ++ "nor a dynamic function, but has static value:\n"
           ++ show sv1
@@ -971,7 +966,7 @@
           pure (Var qn (Info (foldFunType argtypes' $ RetType [] rettype)) loc, sv)
       | otherwise -> do
           fname <- newVName $ "dyn_" <> baseString (qualLeaf qn)
-          let (pats, e0, sv') = liftDynFun (pretty qn) sv depth
+          let (pats, e0, sv') = liftDynFun (prettyString qn) sv depth
               (argtypes', rettype) = dynamicFunType sv' argtypes
               dims' = mempty
 
@@ -1019,7 +1014,7 @@
       ++ " Tried to lift a StaticVal "
       ++ take 100 (show sv)
       ++ ", but expected a dynamic function.\n"
-      ++ pretty d
+      ++ prettyString d
 
 -- | Converts a pattern to an environment that binds the individual names of the
 -- pattern to their corresponding types wrapped in a 'Dynamic' static value.
@@ -1077,7 +1072,7 @@
 buildEnvPat sizes env = RecordPat (map buildField $ M.toList env) mempty
   where
     buildField (vn, Binding _ sv) =
-      ( nameFromString (pretty vn),
+      ( nameFromString (prettyString vn),
         if vn `elem` sizes
           then Wildcard (Info $ typeFromSV sv) mempty
           else Id vn (Info $ typeFromSV sv) mempty
@@ -1122,7 +1117,7 @@
   tp
 typeFromSV (LambdaSV _ _ _ env) =
   Scalar . Record . M.fromList $
-    map (bimap (nameFromString . pretty) (typeFromSV . bindingSV)) $
+    map (bimap (nameFromString . prettyString) (typeFromSV . bindingSV)) $
       M.toList env
 typeFromSV (RecordSV ls) =
   let ts = map (fmap typeFromSV) ls
@@ -1175,17 +1170,17 @@
   | Just ts <- lookup c1 fs =
       mconcat $ zipWith matchPatSV ps $ map svFromType ts
   | otherwise =
-      error $ "matchPatSV: missing constructor in type: " ++ pretty c1
+      error $ "matchPatSV: missing constructor in type: " ++ prettyString c1
 matchPatSV (PatConstr c1 _ ps _) (Dynamic (Scalar (Sum fs)))
   | Just ts <- M.lookup c1 fs =
       mconcat $ zipWith matchPatSV ps $ map svFromType ts
   | otherwise =
-      error $ "matchPatSV: missing constructor in type: " ++ pretty c1
+      error $ "matchPatSV: missing constructor in type: " ++ prettyString c1
 matchPatSV pat (Dynamic t) = matchPatSV pat $ svFromType t
 matchPatSV pat sv =
   error $
     "Tried to match pattern "
-      ++ pretty pat
+      ++ prettyString pat
       ++ " with static value "
       ++ show sv
       ++ "."
@@ -1238,7 +1233,7 @@
 updatePat pat sv =
   error $
     "Tried to update pattern "
-      ++ pretty pat
+      ++ prettyString pat
       ++ "to reflect the static value "
       ++ show sv
 
@@ -1272,7 +1267,7 @@
 defuncValBind valbind@(ValBind _ name retdecl (Info (RetType ret_dims rettype)) tparams params body _ _ _) = do
   when (any isTypeParam tparams) $
     error $
-      prettyName name
+      show name
         ++ " has type parameters, "
         ++ "but the defunctionaliser expects a monomorphic input program."
   (tparams', params', body', sv) <-
diff --git a/src/Futhark/Internalise/Defunctorise.hs b/src/Futhark/Internalise/Defunctorise.hs
--- a/src/Futhark/Internalise/Defunctorise.hs
+++ b/src/Futhark/Internalise/Defunctorise.hs
@@ -1,16 +1,13 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Partially evaluate all modules away from a source Futhark
 -- program.  This is implemented as a source-to-source transformation.
 module Futhark.Internalise.Defunctorise (transformProg) where
 
 import Control.Monad.Identity
 import Control.Monad.RWS.Strict
-import qualified Data.DList as DL
-import qualified Data.Map as M
+import Data.DList qualified as DL
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.MonadFreshNames
 import Language.Futhark
 import Language.Futhark.Semantic (FileModule (..), Imports)
@@ -131,7 +128,7 @@
   let (mname', scope') = lookupSubstInScope mname scope
    in maybe (Left $ bad mname') (Right . extend) $ M.lookup (qualLeaf mname') $ scopeMods scope'
   where
-    bad mname' = "Unknown module: " ++ pretty mname ++ " (" ++ pretty mname' ++ ")"
+    bad mname' = "Unknown module: " ++ prettyString mname ++ " (" ++ prettyString mname' ++ ")"
     extend (ModMod (Scope inner_scope inner_mods)) =
       -- XXX: perhaps hacky fix for #1653.  We need to impose the
       -- substitutions of abstract types from outside, because the
diff --git a/src/Futhark/Internalise/Entry.hs b/src/Futhark/Internalise/Entry.hs
--- a/src/Futhark/Internalise/Entry.hs
+++ b/src/Futhark/Internalise/Entry.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Generating metadata so that programs can run at all.
 module Futhark.Internalise.Entry
   ( entryPoint,
@@ -11,14 +8,14 @@
 
 import Control.Monad.State
 import Data.List (find)
-import qualified Data.Map as M
-import Data.String (fromString)
-import qualified Futhark.IR as I
+import Data.Map qualified as M
+import Data.Text qualified as T
+import Futhark.IR qualified as I
 import Futhark.Internalise.TypesValues (internalisedTypeSize)
-import Futhark.Util.Pretty (prettyOneLine)
-import qualified Language.Futhark as E hiding (TypeArg)
+import Futhark.Util.Pretty (prettyTextOneLine)
+import Language.Futhark qualified as E hiding (TypeArg)
 import Language.Futhark.Core (Name, Uniqueness (..), VName)
-import qualified Language.Futhark.Semantic as E
+import Language.Futhark.Semantic qualified as E
 
 -- | The types that are visible to the outside world.
 newtype VisibleTypes = VisibleTypes [E.TypeBind]
@@ -60,7 +57,7 @@
     f (E.TEArray _ te _) =
       let (d, te') = withoutDims te
        in "arr_" <> typeExpOpaqueName te' <> "_" <> show (1 + d) <> "d"
-    f te = fromString $ prettyOneLine te
+    f te = T.unpack $ prettyTextOneLine te
 
 type GenOpaque = State I.OpaqueTypes
 
@@ -132,7 +129,7 @@
   where
     u = foldl max Nonunique $ map I.uniqueness ts
     desc =
-      maybe (fromString $ prettyOneLine t') typeExpOpaqueName $
+      maybe (T.unpack $ prettyTextOneLine t') typeExpOpaqueName $
         E.entryAscribed t
     t' = E.noSizes (E.entryType t) `E.setUniqueness` Nonunique
 
diff --git a/src/Futhark/Internalise/Exps.hs b/src/Futhark/Internalise/Exps.hs
--- a/src/Futhark/Internalise/Exps.hs
+++ b/src/Futhark/Internalise/Exps.hs
@@ -1,7 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Conversion of a monomorphic, first-order, defunctorised source
@@ -11,9 +8,10 @@
 import Control.Monad.Reader
 import Data.List (elemIndex, find, intercalate, intersperse, transpose)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
-import qualified Data.Set as S
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Futhark.IR.SOACS as I hiding (stmPat)
 import Futhark.Internalise.AccurateSizes
 import Futhark.Internalise.Bindings
@@ -23,7 +21,7 @@
 import Futhark.Internalise.TypesValues
 import Futhark.Transform.Rename as I
 import Futhark.Util (splitAt3)
-import Futhark.Util.Pretty (align, ppr)
+import Futhark.Util.Pretty (align, docText, pretty)
 import Language.Futhark as E hiding (TypeArg)
 
 -- | Convert a program in source Futhark to a program in the Futhark
@@ -38,7 +36,7 @@
 internaliseValBinds types = mapM_ $ internaliseValBind types
 
 internaliseFunName :: VName -> Name
-internaliseFunName = nameFromString . pretty
+internaliseFunName = nameFromString . prettyString
 
 internaliseValBind :: VisibleTypes -> E.ValBind -> InternaliseM ()
 internaliseValBind types fb@(E.ValBind entry fname retdecl (Info rettype) tparams params body _ attrs loc) = do
@@ -206,7 +204,7 @@
     case E.typeOf start of
       E.Scalar (E.Prim (E.Signed it)) -> pure (it, CmpSle it, CmpSlt it)
       E.Scalar (E.Prim (E.Unsigned it)) -> pure (it, CmpUle it, CmpUlt it)
-      start_t -> error $ "Start value in range has type " ++ pretty start_t
+      start_t -> error $ "Start value in range has type " ++ prettyString start_t
 
   let one = intConst it 1
       negone = intConst it (-1)
@@ -345,7 +343,7 @@
     (FunctionName qfname, args) -> do
       -- Argument evaluation is outermost-in so that any existential sizes
       -- created by function applications can be brought into scope.
-      let fname = nameFromString $ pretty $ baseName $ qualLeaf qfname
+      let fname = nameFromString $ prettyString $ baseName $ qualLeaf qfname
           loc = srclocOf e
           arg_desc = nameToString fname ++ "_arg"
 
@@ -369,7 +367,7 @@
 internaliseAppExp desc _ (E.LetPat sizes pat e body _) =
   internalisePat desc sizes pat e $ internaliseExp desc body
 internaliseAppExp _ _ (E.LetFun ofname _ _ _) =
-  error $ "Unexpected LetFun " ++ pretty ofname
+  error $ "Unexpected LetFun " ++ prettyString ofname
 internaliseAppExp desc _ (E.DoLoop sparams mergepat mergeexp form loopbody loc) = do
   ses <- internaliseExp "loop_init" mergeexp
   ((loopbody', (form', shapepat, mergepat', mergeinit')), initstms) <-
@@ -550,13 +548,13 @@
       (internaliseBody (desc <> "_t") te)
       (internaliseBody (desc <> "_f") fe)
 internaliseAppExp _ _ e@E.BinOp {} =
-  error $ "internaliseAppExp: Unexpected BinOp " ++ pretty e
+  error $ "internaliseAppExp: Unexpected BinOp " ++ prettyString e
 
 internaliseExp :: String -> E.Exp -> InternaliseM [I.SubExp]
 internaliseExp desc (E.Parens e _) =
   internaliseExp desc e
 internaliseExp desc (E.Hole (Info t) loc) = do
-  let msg = pretty $ "Reached hole of type: " <> align (ppr t)
+  let msg = docText $ "Reached hole of type: " <> align (pretty t)
       ts = internaliseType (E.toStruct t)
   c <- assert "hole_c" (constant False) (errorMsg [ErrorString msg]) loc
   case mapM hasStaticShape ts of
@@ -632,7 +630,7 @@
             -- Fixing this in the monomorphiser is a lot more tricky
             -- than just working around it here.
             case es' of
-              [] -> error $ "internaliseExp ArrayLit: existential type: " ++ pretty arr_t
+              [] -> error $ "internaliseExp ArrayLit: existential type: " ++ prettyString arr_t
               e' : _ -> mapM subExpType e'
 
       let arraylit ks rt = do
@@ -722,9 +720,9 @@
   e' <- local (f attr') $ internaliseExp desc e
   case attr' of
     "trace" ->
-      traceRes (locStr loc) e'
+      traceRes (T.pack $ locStr loc) e'
     I.AttrComp "trace" [I.AttrName tag] ->
-      traceRes (nameToString tag) e'
+      traceRes (nameToText tag) e'
     "opaque" ->
       mapM (letSubExp desc . BasicOp . Opaque OpaqueNil) e'
     _ ->
@@ -770,7 +768,7 @@
     clauses _ [] _ =
       pure []
 internaliseExp _ (E.Constr _ _ (Info t) loc) =
-  error $ "internaliseExp: constructor with type " ++ pretty t ++ " at " ++ locStr loc
+  error $ "internaliseExp: constructor with type " ++ prettyString t ++ " at " ++ locStr loc
 -- The "interesting" cases are over, now it's mostly boilerplate.
 
 internaliseExp _ (E.Literal v _) =
@@ -783,12 +781,12 @@
       pure [I.Constant $ I.IntValue $ intValue it v]
     E.Scalar (E.Prim (E.FloatType ft)) ->
       pure [I.Constant $ I.FloatValue $ floatValue ft v]
-    _ -> error $ "internaliseExp: nonsensical type for integer literal: " ++ pretty t
+    _ -> error $ "internaliseExp: nonsensical type for integer literal: " ++ prettyString t
 internaliseExp _ (E.FloatLit v (Info t) _) =
   case t of
     E.Scalar (E.Prim (E.FloatType ft)) ->
       pure [I.Constant $ I.FloatValue $ floatValue ft v]
-    _ -> error $ "internaliseExp: nonsensical type for float literal: " ++ pretty t
+    _ -> error $ "internaliseExp: nonsensical type for float literal: " ++ prettyString t
 -- Builtin operators are handled specially because they are
 -- overloaded.
 internaliseExp desc (E.Project k e (Info rt) _) = do
@@ -867,7 +865,7 @@
         Nothing ->
           error "generateCond: missing constructor"
     compares (E.PatConstr _ (Info t) _ _) _ =
-      error $ "generateCond: PatConstr has nonsensical type: " ++ pretty t
+      error $ "generateCond: PatConstr has nonsensical type: " ++ prettyString t
     compares (E.Id _ t loc) ses =
       compares (E.Wildcard t loc) ses
     compares (E.Wildcard (Info t) _) ses = do
@@ -888,7 +886,7 @@
     compares (E.PatAscription pat _ _) ses =
       compares pat ses
     compares pat [] =
-      error $ "generateCond: No values left for pattern " ++ pretty pat
+      error $ "generateCond: No values left for pattern " ++ prettyString pat
 
     comparesMany [] ses = pure ([], [], ses)
     comparesMany (pat : pats) ses = do
@@ -1391,11 +1389,11 @@
 internaliseBinOp _ _ op _ _ t1 t2 =
   error $
     "Invalid binary operator "
-      ++ pretty op
+      ++ prettyString op
       ++ " with operand types "
-      ++ pretty t1
+      ++ prettyString t1
       ++ ", "
-      ++ pretty t2
+      ++ prettyString t2
 
 simpleBinOp ::
   String ->
@@ -1430,7 +1428,7 @@
   | E.Hole (Info t) loc <- f =
       (FunctionHole t loc, [(arg, argext)])
 findFuncall e =
-  error $ "Invalid function expression in application:\n" ++ pretty e
+  error $ "Invalid function expression in application:\n" ++ prettyString e
 
 -- The type of a body.  Watch out: this only works for the degenerate
 -- case where the body does not already return its context.
@@ -1449,7 +1447,7 @@
     body' <- internaliseBody "lam" body
     rettype' <- internaliseLambdaReturnType rettype =<< bodyExtType body'
     pure (params', body', rettype')
-internaliseLambda e _ = error $ "internaliseLambda: unexpected expression:\n" ++ pretty e
+internaliseLambda e _ = error $ "internaliseLambda: unexpected expression:\n" ++ prettyString e
 
 internaliseLambdaCoerce :: E.Exp -> [Type] -> InternaliseM (I.Lambda SOACS)
 internaliseLambdaCoerce lam argtypes = do
@@ -1492,20 +1490,20 @@
     handleSign _ _ = Nothing
 
     handleIntrinsicOps [x] s
-      | Just unop <- find ((== s) . pretty) allUnOps = Just $ \desc -> do
+      | Just unop <- find ((== s) . prettyString) allUnOps = Just $ \desc -> do
           x' <- internaliseExp1 "x" x
           fmap pure $ letSubExp desc $ I.BasicOp $ I.UnOp unop x'
     handleIntrinsicOps [TupLit [x, y] _] s
-      | Just bop <- find ((== s) . pretty) allBinOps = Just $ \desc -> do
+      | Just bop <- find ((== s) . prettyString) allBinOps = Just $ \desc -> do
           x' <- internaliseExp1 "x" x
           y' <- internaliseExp1 "y" y
           fmap pure $ letSubExp desc $ I.BasicOp $ I.BinOp bop x' y'
-      | Just cmp <- find ((== s) . pretty) allCmpOps = Just $ \desc -> do
+      | Just cmp <- find ((== s) . prettyString) allCmpOps = Just $ \desc -> do
           x' <- internaliseExp1 "x" x
           y' <- internaliseExp1 "y" y
           fmap pure $ letSubExp desc $ I.BasicOp $ I.CmpOp cmp x' y'
     handleIntrinsicOps [x] s
-      | Just conv <- find ((== s) . pretty) allConvOps = Just $ \desc -> do
+      | Just conv <- find ((== s) . prettyString) allConvOps = Just $ \desc -> do
           x' <- internaliseExp1 "x" x
           fmap pure $ letSubExp desc $ I.BasicOp $ I.ConvOp conv x'
     handleIntrinsicOps _ _ = Nothing
@@ -1575,7 +1573,7 @@
               letSubExp "arrays_equal"
                 =<< eIf (eSubExp shapes_match) compare_elems_body (resultBodyM [constant False])
     handleOps [x, y] name
-      | Just bop <- find ((name ==) . pretty) [minBound .. maxBound :: E.BinOp] =
+      | Just bop <- find ((name ==) . prettyString) [minBound .. maxBound :: E.BinOp] =
           Just $ \desc -> do
             x' <- internaliseExp1 "x" x
             y' <- internaliseExp1 "y" y
@@ -1818,7 +1816,7 @@
           bodyNames = indexName <> valueNames
           bodyParams = zipWith (I.Param mempty) bodyNames paramTypes
 
-      -- This body is pretty boring right now, as every input is exactly the output.
+      -- This body is prettyString boring right now, as every input is exactly the output.
       -- But it can get funky later on if fused with something else.
       body <- localScope (scopeOfLParams bodyParams) . buildBody_ $ do
         let outs = concat (replicate (length valueNames) indexName) ++ valueNames
@@ -1881,7 +1879,7 @@
       offset_inbounds_down
       [offset_inbounds_up, min_in_bounds, max_in_bounds]
 
-  c <- assert "bounds_cert" all_bounds (ErrorMsg [ErrorString $ "Flat slice out of bounds: " ++ pretty old_dim ++ " and " ++ pretty slices']) loc
+  c <- assert "bounds_cert" all_bounds (ErrorMsg [ErrorString $ "Flat slice out of bounds: " <> prettyText old_dim <> " and " <> prettyText slices']) loc
   let slice = I.FlatSlice offset' $ map (uncurry FlatDimIndex) slices'
   certifying c $
     forM arrs $ \arr' ->
@@ -1929,7 +1927,7 @@
       offset_inbounds_down
       [offset_inbounds_up, min_in_bounds, max_in_bounds]
 
-  c <- assert "bounds_cert" all_bounds (ErrorMsg [ErrorString $ "Flat slice out of bounds: " ++ pretty old_dim ++ " and " ++ pretty slices']) loc
+  c <- assert "bounds_cert" all_bounds (ErrorMsg [ErrorString $ "Flat slice out of bounds: " <> prettyText old_dim <> " and " <> prettyText slices']) loc
   let slice = I.FlatSlice offset' $ map (uncurry FlatDimIndex) slices'
   certifying c $
     forM (zip arrs1 arrs2) $ \(arr1', arr2') ->
@@ -1963,17 +1961,17 @@
       error $
         concat
           [ "Cannot apply ",
-            pretty fname,
+            prettyString fname,
             " to ",
             show (length args'),
             " arguments\n ",
-            pretty args',
+            prettyString args',
             "\nof types\n ",
-            pretty argts',
+            prettyString argts',
             "\nFunction has ",
             show (length fun_params),
             " parameters\n ",
-            pretty fun_params
+            prettyString fun_params
           ]
     Just ts -> do
       safety <- askSafety
@@ -2128,14 +2126,14 @@
 
 typeExpForError :: E.TypeExp VName -> InternaliseM [ErrorMsgPart SubExp]
 typeExpForError (E.TEVar qn _) =
-  pure [ErrorString $ pretty qn]
+  pure [ErrorString $ prettyText qn]
 typeExpForError (E.TEUnique te _) =
   ("*" :) <$> typeExpForError te
 typeExpForError (E.TEDim dims te _) =
   (ErrorString ("?" <> dims' <> ".") :) <$> typeExpForError te
   where
     dims' = mconcat (map onDim dims)
-    onDim d = "[" <> pretty d <> "]"
+    onDim d = "[" <> prettyText d <> "]"
 typeExpForError (E.TEArray d te _) = do
   d' <- dimExpForError d
   te' <- typeExpForError te
@@ -2148,7 +2146,7 @@
   pure $ ["{"] ++ intercalate [", "] fields' ++ ["}"]
   where
     onField (k, te) =
-      (ErrorString (pretty k ++ ": ") :) <$> typeExpForError te
+      (ErrorString (prettyText k <> ": ") :) <$> typeExpForError te
 typeExpForError (E.TEArrow _ t1 t2 _) = do
   t1' <- typeExpForError t1
   t2' <- typeExpForError t2
@@ -2175,7 +2173,7 @@
     _ -> pure $ I.Var $ E.qualLeaf d
   pure $ ErrorVal int64 d'
 dimExpForError (SizeExpConst d _) =
-  pure $ ErrorString $ pretty d
+  pure $ ErrorString $ prettyText d
 dimExpForError SizeExpAny = pure ""
 
 -- A smart constructor that compacts neighbouring literals for easier
@@ -2185,5 +2183,5 @@
   where
     compact [] = []
     compact (ErrorString x : ErrorString y : parts) =
-      compact (ErrorString (x ++ y) : parts)
+      compact (ErrorString (x <> y) : parts)
     compact (x : y) = x : compact y
diff --git a/src/Futhark/Internalise/Lambdas.hs b/src/Futhark/Internalise/Lambdas.hs
--- a/src/Futhark/Internalise/Lambdas.hs
+++ b/src/Futhark/Internalise/Lambdas.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 module Futhark.Internalise.Lambdas
   ( InternaliseLambda,
     internaliseFoldLambda,
diff --git a/src/Futhark/Internalise/LiftLambdas.hs b/src/Futhark/Internalise/LiftLambdas.hs
--- a/src/Futhark/Internalise/LiftLambdas.hs
+++ b/src/Futhark/Internalise/LiftLambdas.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Lambda-lifting of typed, monomorphic Futhark programs without
 -- modules.  After this pass, the program will no longer contain any
 -- 'LetFun's or 'Lambda's.
@@ -12,9 +8,9 @@
 import Data.Bifunctor
 import Data.Foldable
 import Data.List (partition)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.Pretty ()
 import Futhark.MonadFreshNames
 import Language.Futhark
diff --git a/src/Futhark/Internalise/Monad.hs b/src/Futhark/Internalise/Monad.hs
--- a/src/Futhark/Internalise/Monad.hs
+++ b/src/Futhark/Internalise/Monad.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Internalise.Monad
@@ -31,7 +26,7 @@
 import Control.Monad.Except
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.IR.SOACS
 import Futhark.MonadFreshNames
 import Futhark.Tools
@@ -155,7 +150,7 @@
 lookupFunction :: VName -> InternaliseM FunInfo
 lookupFunction fname = maybe bad pure =<< lookupFunction' fname
   where
-    bad = error $ "Internalise.lookupFunction: Function '" ++ pretty fname ++ "' not found."
+    bad = error $ "Internalise.lookupFunction: Function '" ++ prettyString fname ++ "' not found."
 
 lookupConst :: VName -> InternaliseM (Maybe [SubExp])
 lookupConst fname = do
diff --git a/src/Futhark/Internalise/Monomorphise.hs b/src/Futhark/Internalise/Monomorphise.hs
--- a/src/Futhark/Internalise/Monomorphise.hs
+++ b/src/Futhark/Internalise/Monomorphise.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | This monomorphization module converts a well-typed, polymorphic,
 -- module-free Futhark program into an equivalent monomorphic program.
 --
@@ -37,10 +32,10 @@
 import Data.Bitraversable
 import Data.Foldable
 import Data.List (partition)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Sequence as Seq
-import qualified Data.Set as S
+import Data.Sequence qualified as Seq
+import Data.Set qualified as S
 import Futhark.MonadFreshNames
 import Futhark.Util.Pretty
 import Language.Futhark
@@ -156,11 +151,11 @@
   _ == _ = False
 
 instance Pretty MonoSize where
-  ppr (MonoKnown i) = text "?" <> ppr i
-  ppr (MonoAnon v) = text "?" <> pprName v
+  pretty (MonoKnown i) = "?" <> pretty i
+  pretty (MonoAnon v) = "?" <> prettyName v
 
 instance Pretty (Shape MonoSize) where
-  ppr (Shape ds) = mconcat (map (brackets . ppr) ds)
+  pretty (Shape ds) = mconcat (map (brackets . pretty) ds)
 
 -- The kind of type relative to which we monomorphise.  What is most
 -- important to us is not the specific dimensions, but merely whether
@@ -605,10 +600,10 @@
         t ->
           error $
             "desugarOpSection: type "
-              ++ pretty t
+              ++ prettyString t
               ++ " does not have field "
-              ++ pretty field
-desugarProjectSection _ t _ = error $ "desugarOpSection: not a function type: " ++ pretty t
+              ++ prettyString field
+desugarProjectSection _ t _ = error $ "desugarOpSection: not a function type: " ++ prettyString t
 
 desugarIndexSection :: [DimIndex] -> PatType -> SrcLoc -> MonoM Exp
 desugarIndexSection idxs (Scalar (Arrow _ _ t1 (RetType dims t2))) loc = do
@@ -623,7 +618,7 @@
       loc
   where
     t1' = fromStruct t1
-desugarIndexSection _ t _ = error $ "desugarIndexSection: not a function type: " ++ pretty t
+desugarIndexSection _ t _ = error $ "desugarIndexSection: not a function type: " ++ prettyString t
 
 noticeDims :: TypeBase Size as -> MonoM ()
 noticeDims = mapM_ notice . freeInType
@@ -854,7 +849,7 @@
         typeSubstClause (_, ts1) (_, ts2) = zipWithM sub ts1 ts2
     sub t1@(Scalar Sum {}) t2 = sub t1 t2
     sub t1 t2@(Scalar Sum {}) = sub t1 t2
-    sub t1 t2 = error $ unlines ["typeSubstsM: mismatched types:", pretty t1, pretty t2]
+    sub t1 t2 = error $ unlines ["typeSubstsM: mismatched types:", prettyString t1, prettyString t2]
 
     addSubst (QualName _ v) (RetType ext t) = do
       (ts, sizes) <- get
@@ -976,7 +971,7 @@
   error $
     "The monomorphization module expects a module-free "
       ++ "input program, but received: "
-      ++ pretty dec
+      ++ prettyString dec
 
 -- | Monomorphise a list of top-level declarations. A module-free input program
 -- is expected, so only value declarations and type declaration are accepted.
diff --git a/src/Futhark/Internalise/TypesValues.hs b/src/Futhark/Internalise/TypesValues.hs
--- a/src/Futhark/Internalise/TypesValues.hs
+++ b/src/Futhark/Internalise/TypesValues.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 module Futhark.Internalise.TypesValues
   ( -- * Internalising types
     internaliseReturnType,
@@ -22,11 +18,11 @@
 import Control.Monad.State
 import Data.Bitraversable (bitraverse)
 import Data.List (delete, find, foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.IR.SOACS as I
 import Futhark.Internalise.Monad
-import qualified Language.Futhark as E
+import Language.Futhark qualified as E
 
 internaliseUniqueness :: E.Uniqueness -> I.Uniqueness
 internaliseUniqueness E.Nonunique = I.Nonunique
@@ -52,7 +48,7 @@
     mapM (fmap (map onType) . internaliseTypeM mempty) ts
   where
     onType = fromMaybe bad . hasStaticShape
-    bad = error $ "internaliseParamTypes: " ++ pretty ts
+    bad = error $ "internaliseParamTypes: " ++ prettyString ts
 
 -- We need to fix up the arrays for any Acc return values or loop
 -- parameters.  We look at the concrete types for this, since the Acc
@@ -164,9 +160,9 @@
               acc_t = Acc acc_param (Shape [arraysSize 0 ts]) (map rowType ts) $ internaliseUniqueness u
           pure [acc_t]
     E.Scalar E.TypeVar {} ->
-      error $ "internaliseTypeM: cannot handle type variable: " ++ pretty orig_t
+      error $ "internaliseTypeM: cannot handle type variable: " ++ prettyString orig_t
     E.Scalar E.Arrow {} ->
-      error $ "internaliseTypeM: cannot handle function type: " ++ pretty orig_t
+      error $ "internaliseTypeM: cannot handle function type: " ++ prettyString orig_t
     E.Scalar (E.Sum cs) -> do
       (ts, _) <-
         internaliseConstructors
@@ -176,7 +172,7 @@
     internaliseShape = mapM (internaliseDim exts) . E.shapeDims
 
     onAccType = fromMaybe bad . hasStaticShape
-    bad = error $ "internaliseTypeM Acc: " ++ pretty orig_t
+    bad = error $ "internaliseTypeM Acc: " ++ prettyString orig_t
 
 internaliseConstructors ::
   M.Map Name [I.TypeBase ExtShape Uniqueness] ->
diff --git a/src/Futhark/LSP/Compile.hs b/src/Futhark/LSP/Compile.hs
--- a/src/Futhark/LSP/Compile.hs
+++ b/src/Futhark/LSP/Compile.hs
@@ -8,9 +8,9 @@
 import Control.Lens.Getter (view)
 import Control.Monad.IO.Class (MonadIO (liftIO))
 import Data.IORef (IORef, readIORef, writeIORef)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe (fromMaybe)
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Compiler.Program (LoadedProg, lpFilePaths, lpWarnings, noLoadedProg, reloadProg)
 import Futhark.LSP.Diagnostic (diagnosticSource, maxDiagnostic, publishErrorDiagnostics, publishWarningDiagnostics)
 import Futhark.LSP.State (State (..), emptyState, updateStaleContent, updateStaleMapping)
diff --git a/src/Futhark/LSP/Diagnostic.hs b/src/Futhark/LSP/Diagnostic.hs
--- a/src/Futhark/LSP/Diagnostic.hs
+++ b/src/Futhark/LSP/Diagnostic.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Handling of diagnostics in the language server - things like
 -- warnings and errors.
 module Futhark.LSP.Diagnostic
@@ -13,13 +11,13 @@
 import Colog.Core (logStringStderr, (<&))
 import Control.Lens ((^.))
 import Data.Foldable (for_)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.Compiler.Program (ProgError (..))
 import Futhark.LSP.Tool (posToUri, rangeFromLoc, rangeFromSrcLoc)
 import Futhark.Util.Loc (Loc (..), SrcLoc, locOf)
-import Futhark.Util.Pretty (Doc, prettyText)
+import Futhark.Util.Pretty (Doc, docText)
 import Language.LSP.Diagnostics (partitionBySource)
 import Language.LSP.Server (LspT, getVersionedTextDoc, publishDiagnostics)
 import Language.LSP.Types
@@ -44,12 +42,12 @@
   publishDiagnostics maxDiagnostic (toNormalizedUri uri) (doc ^. version) (partitionBySource diags)
 
 -- | Send warning diagnostics to the client.
-publishWarningDiagnostics :: [(SrcLoc, Doc)] -> LspT () IO ()
+publishWarningDiagnostics :: [(SrcLoc, Doc a)] -> LspT () IO ()
 publishWarningDiagnostics warnings = do
   publish $ M.assocs $ M.unionsWith (++) $ map onWarn warnings
   where
     onWarn (srcloc, msg) =
-      let diag = mkDiagnostic (rangeFromSrcLoc srcloc) DsWarning (prettyText msg)
+      let diag = mkDiagnostic (rangeFromSrcLoc srcloc) DsWarning (docText msg)
        in case locOf srcloc of
             NoLoc -> mempty
             Loc pos _ -> M.singleton (posToUri pos) [diag]
@@ -60,12 +58,12 @@
   publish $ M.assocs $ M.unionsWith (++) $ map onDiag $ NE.toList errors
   where
     onDiag (ProgError loc msg) =
-      let diag = mkDiagnostic (rangeFromLoc loc) DsError (prettyText msg)
+      let diag = mkDiagnostic (rangeFromLoc loc) DsError (docText msg)
        in case loc of
             NoLoc -> mempty
             Loc pos _ -> M.singleton (posToUri pos) [diag]
     onDiag (ProgWarning loc msg) =
-      let diag = mkDiagnostic (rangeFromLoc loc) DsError (prettyText msg)
+      let diag = mkDiagnostic (rangeFromLoc loc) DsError (docText msg)
        in case loc of
             NoLoc -> mempty
             Loc pos _ -> M.singleton (posToUri pos) [diag]
diff --git a/src/Futhark/LSP/Handlers.hs b/src/Futhark/LSP/Handlers.hs
--- a/src/Futhark/LSP/Handlers.hs
+++ b/src/Futhark/LSP/Handlers.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | The handlers exposed by the language server.
 module Futhark.LSP.Handlers (handlers) where
 
@@ -7,7 +5,7 @@
 import Control.Lens ((^.))
 import Data.Aeson.Types (Value (Array, String))
 import Data.IORef
-import qualified Data.Vector as V
+import Data.Vector qualified as V
 import Futhark.LSP.Compile (tryReCompile, tryTakeStateFromIORef)
 import Futhark.LSP.State (State (..))
 import Futhark.LSP.Tool (findDefinitionRange, getHoverInfoFromState)
diff --git a/src/Futhark/LSP/PositionMapping.hs b/src/Futhark/LSP/PositionMapping.hs
--- a/src/Futhark/LSP/PositionMapping.hs
+++ b/src/Futhark/LSP/PositionMapping.hs
@@ -10,8 +10,8 @@
 
 import Data.Algorithm.Diff (Diff, PolyDiff (Both, First, Second), getDiff)
 import Data.Bifunctor (Bifunctor (bimap, first, second))
-import qualified Data.Text as T
-import qualified Data.Vector as V
+import Data.Text qualified as T
+import Data.Vector qualified as V
 import Futhark.Util.Loc (Loc (Loc), Pos (Pos))
 import Language.LSP.VFS (VirtualFile)
 
@@ -27,7 +27,7 @@
   }
   deriving (Show)
 
--- | Stale text document stored in state.
+-- | Stale pretty document stored in state.
 data StaleFile = StaleFile
   { -- | The last successfully compiled file content.
     -- Using VirtualFile for convenience, we can use anything with {version, content}
diff --git a/src/Futhark/LSP/State.hs b/src/Futhark/LSP/State.hs
--- a/src/Futhark/LSP/State.hs
+++ b/src/Futhark/LSP/State.hs
@@ -9,7 +9,7 @@
   )
 where
 
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.Compiler.Program (LoadedProg)
 import Futhark.LSP.PositionMapping (PositionMapping, StaleFile (..))
 import Language.LSP.VFS (VirtualFile)
diff --git a/src/Futhark/LSP/Tool.hs b/src/Futhark/LSP/Tool.hs
--- a/src/Futhark/LSP/Tool.hs
+++ b/src/Futhark/LSP/Tool.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Generally useful definition used in various places in the
 -- language server implementation.
 module Futhark.LSP.Tool
@@ -12,7 +10,7 @@
   )
 where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Compiler.Program (lpImports)
 import Futhark.LSP.PositionMapping
   ( PositionMapping,
diff --git a/src/Futhark/MonadFreshNames.hs b/src/Futhark/MonadFreshNames.hs
--- a/src/Futhark/MonadFreshNames.hs
+++ b/src/Futhark/MonadFreshNames.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 -- | This module provides a monadic facility similar (and built on top
@@ -21,16 +20,16 @@
 where
 
 import Control.Monad.Except
-import qualified Control.Monad.RWS.Lazy
-import qualified Control.Monad.RWS.Strict
+import Control.Monad.RWS.Lazy qualified
+import Control.Monad.RWS.Strict qualified
 import Control.Monad.Reader
-import qualified Control.Monad.State.Lazy
-import qualified Control.Monad.State.Strict
-import qualified Control.Monad.Trans.Maybe
-import qualified Control.Monad.Writer.Lazy
-import qualified Control.Monad.Writer.Strict
+import Control.Monad.State.Lazy qualified
+import Control.Monad.State.Strict qualified
+import Control.Monad.Trans.Maybe qualified
+import Control.Monad.Writer.Lazy qualified
+import Control.Monad.Writer.Strict qualified
 import Futhark.FreshNames hiding (newName)
-import qualified Futhark.FreshNames as FreshNames
+import Futhark.FreshNames qualified as FreshNames
 import Futhark.IR.Syntax
 
 -- | A monad that stores a name source.  The following is a good
diff --git a/src/Futhark/Optimise/BlkRegTiling.hs b/src/Futhark/Optimise/BlkRegTiling.hs
--- a/src/Futhark/Optimise/BlkRegTiling.hs
+++ b/src/Futhark/Optimise/BlkRegTiling.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Perform a restricted form of block+register tiling corresponding to
@@ -20,13 +19,13 @@
 module Futhark.Optimise.BlkRegTiling (mmBlkRegTiling, doRegTiling3D) where
 
 import Control.Monad.Reader
-import qualified Data.List as L
+import Data.List qualified as L
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Sequence as Seq
+import Data.Sequence qualified as Seq
 import Futhark.IR.GPU
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Optimise.TileLoops.Shared
 import Futhark.Tools
@@ -424,7 +423,7 @@
       case acc_0s of
         [acc_0] -> pure acc_0
         _ -> error "Impossible case reached when treating accumulators!"
-    getAccumFV tp = error ("Should be an accumulator type at this point, given: " ++ pretty tp)
+    getAccumFV tp = error ("Should be an accumulator type at this point, given: " ++ prettyString tp)
     --
     -- checks that the redomap result is used directly as the accumulated value,
     -- in which case it is safe to parallelize the innermost dimension (of tile tk)
@@ -732,11 +731,11 @@
       SubExp
     )
 mkTileMemSizes height_A width_B common_dim = do
-  tk_name <- nameFromString . pretty <$> newVName "Tk"
-  tx_name <- nameFromString . pretty <$> newVName "Tx"
-  ty_name <- nameFromString . pretty <$> newVName "Ty"
-  rx_name <- nameFromString . pretty <$> newVName "Rx"
-  ry_name <- nameFromString . pretty <$> newVName "Ry"
+  tk_name <- nameFromString . prettyString <$> newVName "Tk"
+  tx_name <- nameFromString . prettyString <$> newVName "Tx"
+  ty_name <- nameFromString . prettyString <$> newVName "Ty"
+  rx_name <- nameFromString . prettyString <$> newVName "Rx"
+  ry_name <- nameFromString . prettyString <$> newVName "Ry"
 
   (ty, ry) <- getParTiles ("Ty", "Ry") (ty_name, ry_name) height_A
   (tx, rx) <- getParTiles ("Tx", "Rx") (tx_name, rx_name) width_B
@@ -1069,8 +1068,8 @@
             (M.empty, M.empty)
             $ M.toList arr_tab0
 
-        tx_name <- nameFromString . pretty <$> newVName "Tx"
-        ty_name <- nameFromString . pretty <$> newVName "Ty"
+        tx_name <- nameFromString . prettyString <$> newVName "Tx"
+        ty_name <- nameFromString . prettyString <$> newVName "Ty"
 
         tx0 <- letSubExp "Tx" $ Op $ SizeOp $ GetSize tx_name SizeTile
         ty0 <- letSubExp "Ty" $ Op $ SizeOp $ GetSize ty_name SizeTile
diff --git a/src/Futhark/Optimise/CSE.hs b/src/Futhark/Optimise/CSE.hs
--- a/src/Futhark/Optimise/CSE.hs
+++ b/src/Futhark/Optimise/CSE.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 -- | This module implements common-subexpression elimination.  This
@@ -36,7 +33,7 @@
 where
 
 import Control.Monad.Reader
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe (isJust)
 import Futhark.Analysis.Alias
 import Futhark.IR
@@ -48,11 +45,11 @@
     removeProgAliases,
     removeStmAliases,
   )
-import qualified Futhark.IR.GPU as GPU
-import qualified Futhark.IR.MC as MC
-import qualified Futhark.IR.Mem as Memory
+import Futhark.IR.GPU qualified as GPU
+import Futhark.IR.MC qualified as MC
+import Futhark.IR.Mem qualified as Memory
 import Futhark.IR.Prop.Aliases
-import qualified Futhark.IR.SOACS.SOAC as SOAC
+import Futhark.IR.SOACS.SOAC qualified as SOAC
 import Futhark.Pass
 import Futhark.Transform.Substitute
 
diff --git a/src/Futhark/Optimise/DoubleBuffer.hs b/src/Futhark/Optimise/DoubleBuffer.hs
--- a/src/Futhark/Optimise/DoubleBuffer.hs
+++ b/src/Futhark/Optimise/DoubleBuffer.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | The simplification engine is only willing to hoist allocations
@@ -82,12 +76,12 @@
 import Control.Monad.Writer
 import Data.Bifunctor
 import Data.List (find)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Construct
 import Futhark.IR.GPUMem as GPU
 import Futhark.IR.MCMem as MC
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Pass
 import Futhark.Pass.ExplicitAllocations (arraySizeInBytesExp)
 import Futhark.Pass.ExplicitAllocations.GPU ()
diff --git a/src/Futhark/Optimise/EntryPointMem.hs b/src/Futhark/Optimise/EntryPointMem.hs
--- a/src/Futhark/Optimise/EntryPointMem.hs
+++ b/src/Futhark/Optimise/EntryPointMem.hs
@@ -1,15 +1,10 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | We require that entry points return arrays with zero offset in
 -- row-major order.  "Futhark.Pass.ExplicitAllocations" is
 -- conservative and inserts copies to ensure this is the case.  After
 -- simplification, it may turn out that those copies are redundant.
--- This pass removes them.  It's a pretty simple pass, as it only has
+-- This pass removes them.  It's a prettyString simple pass, as it only has
 -- to look at the top level of entry points.
 module Futhark.Optimise.EntryPointMem
   ( entryPointMemGPU,
@@ -19,7 +14,7 @@
 where
 
 import Data.List (find)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.IR.GPUMem (GPUMem)
 import Futhark.IR.MCMem (MCMem)
 import Futhark.IR.Mem
diff --git a/src/Futhark/Optimise/Fusion.hs b/src/Futhark/Optimise/Fusion.hs
--- a/src/Futhark/Optimise/Fusion.hs
+++ b/src/Futhark/Optimise/Fusion.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
 -- | Perform horizontal and vertical fusion of SOACs.  See the paper
 -- /A T2 Graph-Reduction Approach To Fusion/ for the basic idea (some
 -- extensions discussed in /Design and GPGPU Performance of Futhark’s
@@ -9,20 +6,20 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Graph.Inductive.Graph as G
-import qualified Data.Graph.Inductive.Query.DFS as Q
-import qualified Data.List as L
-import qualified Data.Map.Strict as M
+import Data.Graph.Inductive.Graph qualified as G
+import Data.Graph.Inductive.Query.DFS qualified as Q
+import Data.List qualified as L
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.Alias as Alias
-import qualified Futhark.Analysis.HORep.SOAC as H
+import Futhark.Analysis.Alias qualified as Alias
+import Futhark.Analysis.HORep.SOAC qualified as H
 import Futhark.Construct
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SOACS hiding (SOAC (..))
-import qualified Futhark.IR.SOACS as Futhark
+import Futhark.IR.SOACS qualified as Futhark
 import Futhark.IR.SOACS.Simplify (simplifyLambda)
 import Futhark.Optimise.Fusion.GraphRep
-import qualified Futhark.Optimise.Fusion.TryFusion as TF
+import Futhark.Optimise.Fusion.TryFusion qualified as TF
 import Futhark.Pass
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
diff --git a/src/Futhark/Optimise/Fusion/Composing.hs b/src/Futhark/Optimise/Fusion/Composing.hs
--- a/src/Futhark/Optimise/Fusion/Composing.hs
+++ b/src/Futhark/Optimise/Fusion/Composing.hs
@@ -17,9 +17,9 @@
 where
 
 import Data.List (mapAccumL)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.HORep.SOAC as SOAC
+import Futhark.Analysis.HORep.SOAC qualified as SOAC
 import Futhark.Builder (Buildable (..), insertStm, insertStms, mkLet)
 import Futhark.Construct (mapResult)
 import Futhark.IR
diff --git a/src/Futhark/Optimise/Fusion/GraphRep.hs b/src/Futhark/Optimise/Fusion/GraphRep.hs
--- a/src/Futhark/Optimise/Fusion/GraphRep.hs
+++ b/src/Futhark/Optimise/Fusion/GraphRep.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | A graph representation of a sequence of Futhark statements
 -- (i.e. a 'Body'), built to handle fusion.  Could perhaps be made
 -- more general.  An important property is that it does not handle
@@ -47,18 +45,18 @@
 
 import Data.Bifunctor (bimap)
 import Data.Foldable (foldlM)
-import qualified Data.Graph.Inductive.Dot as G
-import qualified Data.Graph.Inductive.Graph as G
-import qualified Data.Graph.Inductive.Query.DFS as Q
-import qualified Data.Graph.Inductive.Tree as G
-import qualified Data.List as L
-import qualified Data.Map.Strict as M
-import qualified Data.Set as S
-import qualified Futhark.Analysis.Alias as Alias
-import qualified Futhark.Analysis.HORep.SOAC as H
+import Data.Graph.Inductive.Dot qualified as G
+import Data.Graph.Inductive.Graph qualified as G
+import Data.Graph.Inductive.Query.DFS qualified as Q
+import Data.Graph.Inductive.Tree qualified as G
+import Data.List qualified as L
+import Data.Map.Strict qualified as M
+import Data.Set qualified as S
+import Futhark.Analysis.Alias qualified as Alias
+import Futhark.Analysis.HORep.SOAC qualified as H
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SOACS hiding (SOAC (..))
-import qualified Futhark.IR.SOACS as Futhark
+import Futhark.IR.SOACS qualified as Futhark
 import Futhark.Util (nubOrd)
 
 -- | Information associated with an edge in the graph.
@@ -89,21 +87,21 @@
   deriving (Eq)
 
 instance Show EdgeT where
-  show (Dep vName) = "Dep " <> pretty vName
-  show (InfDep vName) = "iDep " <> pretty vName
+  show (Dep vName) = "Dep " <> prettyString vName
+  show (InfDep vName) = "iDep " <> prettyString vName
   show (Cons _) = "Cons"
   show (Fake _) = "Fake"
   show (Res _) = "Res"
   show (Alias _) = "Alias"
 
 instance Show NodeT where
-  show (StmNode (Let pat _ _)) = L.intercalate ", " $ map pretty $ patNames pat
-  show (SoacNode _ pat _ _) = pretty pat
+  show (StmNode (Let pat _ _)) = L.intercalate ", " $ map prettyString $ patNames pat
+  show (SoacNode _ pat _ _) = prettyString pat
   show (FinalNode _ nt _) = show nt
-  show (ResNode name) = pretty $ "Res: " ++ pretty name
-  show (FreeNode name) = pretty $ "Input: " ++ pretty name
-  show (MatchNode stm _) = "Match: " ++ L.intercalate ", " (map pretty $ stmNames stm)
-  show (DoNode stm _) = "Do: " ++ L.intercalate ", " (map pretty $ stmNames stm)
+  show (ResNode name) = prettyString $ "Res: " ++ prettyString name
+  show (FreeNode name) = prettyString $ "Input: " ++ prettyString name
+  show (MatchNode stm _) = "Match: " ++ L.intercalate ", " (map prettyString $ stmNames stm)
+  show (DoNode stm _) = "Do: " ++ L.intercalate ", " (map prettyString $ stmNames stm)
 
 -- | The name that this edge depends on.
 getName :: EdgeT -> VName
diff --git a/src/Futhark/Optimise/Fusion/TryFusion.hs b/src/Futhark/Optimise/Fusion/TryFusion.hs
--- a/src/Futhark/Optimise/Fusion/TryFusion.hs
+++ b/src/Futhark/Optimise/Fusion/TryFusion.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Facilities for fusing two SOACs.
@@ -20,13 +18,13 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.List (find, tails, (\\))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.HORep.MapNest as MapNest
-import qualified Futhark.Analysis.HORep.SOAC as SOAC
+import Futhark.Analysis.HORep.MapNest qualified as MapNest
+import Futhark.Analysis.HORep.SOAC qualified as SOAC
 import Futhark.Construct
 import Futhark.IR.SOACS hiding (SOAC (..))
-import qualified Futhark.IR.SOACS as Futhark
+import Futhark.IR.SOACS qualified as Futhark
 import Futhark.Optimise.Fusion.Composing
 import Futhark.Pass.ExtractKernels.ISRWIM (rwimPossible)
 import Futhark.Transform.Rename (renameLambda)
diff --git a/src/Futhark/Optimise/GenRedOpt.hs b/src/Futhark/Optimise/GenRedOpt.hs
--- a/src/Futhark/Optimise/GenRedOpt.hs
+++ b/src/Futhark/Optimise/GenRedOpt.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Tries to turn a generalized reduction kernel into
@@ -19,8 +18,8 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.List as L
-import qualified Data.Map.Strict as M
+import Data.List qualified as L
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Builder
 import Futhark.IR.GPU
@@ -205,7 +204,7 @@
         (Acc tp_id _shp el_tps _) ->
           case M.lookup tp_id (fst env) of
             Just lam -> (lam, el_tps)
-            _ -> error $ "Lookup in environment failed! " ++ pretty tp_id ++ " env: " ++ pretty (fst env)
+            _ -> error $ "Lookup in environment failed! " ++ prettyString tp_id ++ " env: " ++ show (fst env)
         _ -> error "Illegal accumulator type!"
     -- is a subexp invariant to a gid of a parallel dimension?
     isSeInvar2 variance gid (Var x) =
diff --git a/src/Futhark/Optimise/HistAccs.hs b/src/Futhark/Optimise/HistAccs.hs
--- a/src/Futhark/Optimise/HistAccs.hs
+++ b/src/Futhark/Optimise/HistAccs.hs
@@ -5,7 +5,7 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.IR.GPU
 import Futhark.MonadFreshNames
 import Futhark.Pass
diff --git a/src/Futhark/Optimise/InPlaceLowering.hs b/src/Futhark/Optimise/InPlaceLowering.hs
--- a/src/Futhark/Optimise/InPlaceLowering.hs
+++ b/src/Futhark/Optimise/InPlaceLowering.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -71,7 +66,7 @@
 where
 
 import Control.Monad.RWS
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Ord (comparing)
 import Futhark.Analysis.Alias
 import Futhark.Builder
@@ -347,7 +342,7 @@
     Nothing ->
       error $
         "bindingNumber: variable "
-          ++ pretty name
+          ++ prettyString name
           ++ " not found."
 
 deepen :: ForwardingM rep a -> ForwardingM rep a
@@ -368,7 +363,7 @@
     Nothing ->
       error $
         "isInCurrentBody: variable "
-          ++ pretty name
+          ++ prettyString name
           ++ " not found."
 
 isOptimisable :: VName -> ForwardingM rep Bool
@@ -379,7 +374,7 @@
     Nothing ->
       error $
         "isOptimisable: variable "
-          ++ pretty name
+          ++ prettyString name
           ++ " not found."
 
 seenVar :: VName -> ForwardingM rep ()
diff --git a/src/Futhark/Optimise/InPlaceLowering/LowerIntoStm.hs b/src/Futhark/Optimise/InPlaceLowering/LowerIntoStm.hs
--- a/src/Futhark/Optimise/InPlaceLowering/LowerIntoStm.hs
+++ b/src/Futhark/Optimise/InPlaceLowering/LowerIntoStm.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Optimise.InPlaceLowering.LowerIntoStm
diff --git a/src/Futhark/Optimise/InPlaceLowering/SubstituteIndices.hs b/src/Futhark/Optimise/InPlaceLowering/SubstituteIndices.hs
--- a/src/Futhark/Optimise/InPlaceLowering/SubstituteIndices.hs
+++ b/src/Futhark/Optimise/InPlaceLowering/SubstituteIndices.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module exports facilities for transforming array accesses in
@@ -13,7 +12,7 @@
 where
 
 import Control.Monad
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
@@ -202,4 +201,4 @@
 update needle name subst ((othername, othersubst) : substs)
   | needle == othername = (name, subst) : substs
   | otherwise = (othername, othersubst) : update needle name subst substs
-update needle _ _ [] = error $ "Cannot find substitution for " ++ pretty needle
+update needle _ _ [] = error $ "Cannot find substitution for " ++ prettyString needle
diff --git a/src/Futhark/Optimise/InliningDeadFun.hs b/src/Futhark/Optimise/InliningDeadFun.hs
--- a/src/Futhark/Optimise/InliningDeadFun.hs
+++ b/src/Futhark/Optimise/InliningDeadFun.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | This module implements a compiler pass for inlining functions,
 -- then removing those that have become dead.
 module Futhark.Optimise.InliningDeadFun
@@ -14,10 +11,10 @@
 import Control.Monad.State
 import Control.Parallel.Strategies
 import Data.List (partition)
-import qualified Data.Map.Strict as M
-import qualified Data.Set as S
+import Data.Map.Strict qualified as M
+import Data.Set qualified as S
 import Futhark.Analysis.CallGraph
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Builder
 import Futhark.IR.SOACS
 import Futhark.IR.SOACS.Simplify
diff --git a/src/Futhark/Optimise/MemoryBlockMerging.hs b/src/Futhark/Optimise/MemoryBlockMerging.hs
--- a/src/Futhark/Optimise/MemoryBlockMerging.hs
+++ b/src/Futhark/Optimise/MemoryBlockMerging.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module implements an optimization that tries to statically reuse
@@ -11,16 +9,16 @@
 import Control.Monad.State.Strict
 import Data.Function ((&))
 import Data.Map (Map, (!))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Set (Set)
-import qualified Data.Set as S
-import qualified Futhark.Analysis.Interference as Interference
+import Data.Set qualified as S
+import Futhark.Analysis.Interference qualified as Interference
 import Futhark.Builder.Class
 import Futhark.Construct
 import Futhark.IR.GPUMem
-import qualified Futhark.Optimise.MemoryBlockMerging.GreedyColoring as GreedyColoring
+import Futhark.Optimise.MemoryBlockMerging.GreedyColoring qualified as GreedyColoring
 import Futhark.Pass (Pass (..), PassM)
-import qualified Futhark.Pass as Pass
+import Futhark.Pass qualified as Pass
 import Futhark.Util (invertMap)
 
 -- | A mapping from allocation names to their size and space.
diff --git a/src/Futhark/Optimise/MemoryBlockMerging/GreedyColoring.hs b/src/Futhark/Optimise/MemoryBlockMerging/GreedyColoring.hs
--- a/src/Futhark/Optimise/MemoryBlockMerging/GreedyColoring.hs
+++ b/src/Futhark/Optimise/MemoryBlockMerging/GreedyColoring.hs
@@ -2,10 +2,10 @@
 module Futhark.Optimise.MemoryBlockMerging.GreedyColoring (colorGraph, Coloring) where
 
 import Data.Function ((&))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe (fromMaybe)
-import qualified Data.Set as S
-import qualified Futhark.Analysis.Interference as Interference
+import Data.Set qualified as S
+import Futhark.Analysis.Interference qualified as Interference
 
 -- | A map of values to their color, identified by an integer.
 type Coloring a = M.Map a Int
diff --git a/src/Futhark/Optimise/MergeGPUBodies.hs b/src/Futhark/Optimise/MergeGPUBodies.hs
--- a/src/Futhark/Optimise/MergeGPUBodies.hs
+++ b/src/Futhark/Optimise/MergeGPUBodies.hs
@@ -14,13 +14,13 @@
 import Control.Monad.Trans.State.Strict hiding (State)
 import Data.Bifunctor (first)
 import Data.Foldable
-import qualified Data.IntMap as IM
+import Data.IntMap qualified as IM
 import Data.IntSet ((\\))
-import qualified Data.IntSet as IS
-import qualified Data.Map as M
+import Data.IntSet qualified as IS
+import Data.Map qualified as M
 import Data.Maybe (fromMaybe)
 import Data.Sequence ((|>))
-import qualified Data.Sequence as SQ
+import Data.Sequence qualified as SQ
 import Futhark.Analysis.Alias
 import Futhark.Construct (sliceDim)
 import Futhark.Error
diff --git a/src/Futhark/Optimise/ReduceDeviceSyncs.hs b/src/Futhark/Optimise/ReduceDeviceSyncs.hs
--- a/src/Futhark/Optimise/ReduceDeviceSyncs.hs
+++ b/src/Futhark/Optimise/ReduceDeviceSyncs.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
 -- | This module implements an optimization that migrates host
 -- statements into 'GPUBody' kernels to reduce the number of
 -- host-device synchronizations that occur when a scalar variable is
@@ -14,11 +12,11 @@
 import Control.Monad.State hiding (State)
 import Data.Bifunctor (second)
 import Data.Foldable
-import qualified Data.IntMap.Strict as IM
+import Data.IntMap.Strict qualified as IM
 import Data.List (transpose, zip4)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Sequence ((<|), (><), (|>))
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Construct (fullSlice, mkBody, sliceDim)
 import Futhark.Error
 import Futhark.IR.GPU
diff --git a/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs b/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs
--- a/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs
+++ b/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs
@@ -55,19 +55,19 @@
 
 import Control.Monad
 import Control.Monad.Trans.Class
-import qualified Control.Monad.Trans.Reader as R
+import Control.Monad.Trans.Reader qualified as R
 import Control.Monad.Trans.State.Strict ()
 import Control.Monad.Trans.State.Strict hiding (State)
 import Data.Bifunctor (first, second)
 import Data.Foldable
-import qualified Data.IntMap.Strict as IM
-import qualified Data.IntSet as IS
-import qualified Data.List as L
-import qualified Data.Map.Strict as M
+import Data.IntMap.Strict qualified as IM
+import Data.IntSet qualified as IS
+import Data.List qualified as L
+import Data.Map.Strict qualified as M
 import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
-import qualified Data.Sequence as SQ
+import Data.Sequence qualified as SQ
 import Data.Set (Set, (\\))
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Error
 import Futhark.IR.GPU
 import Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph
@@ -79,7 +79,7 @@
     Routing (..),
     Vertex (..),
   )
-import qualified Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph as MG
+import Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph qualified as MG
 
 --------------------------------------------------------------------------------
 --                              MIGRATION TABLES                              --
diff --git a/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs b/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs
--- a/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs
+++ b/src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs
@@ -81,10 +81,10 @@
   )
 where
 
-import qualified Data.IntMap.Strict as IM
-import qualified Data.IntSet as IS
+import Data.IntMap.Strict qualified as IM
+import Data.IntSet qualified as IS
 import Data.List (foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe (fromJust)
 import Prelude hiding (lookup)
 
diff --git a/src/Futhark/Optimise/Simplify.hs b/src/Futhark/Optimise/Simplify.hs
--- a/src/Futhark/Optimise/Simplify.hs
+++ b/src/Futhark/Optimise/Simplify.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE Strict #-}
 
 module Futhark.Optimise.Simplify
@@ -20,11 +18,11 @@
   )
 where
 
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR
 import Futhark.MonadFreshNames
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep
 import Futhark.Optimise.Simplify.Rule
 import Futhark.Pass
diff --git a/src/Futhark/Optimise/Simplify/Engine.hs b/src/Futhark/Optimise/Simplify/Engine.hs
--- a/src/Futhark/Optimise/Simplify/Engine.hs
+++ b/src/Futhark/Optimise/Simplify/Engine.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -73,10 +67,10 @@
 import Control.Monad.State.Strict
 import Data.Either
 import Data.List (find, foldl', inits, mapAccumL)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
@@ -187,7 +181,7 @@
       Nothing ->
         error $
           "SimpleM.lookupType: cannot find variable "
-            ++ pretty name
+            ++ prettyString name
             ++ " in symbol table."
 
 instance
diff --git a/src/Futhark/Optimise/Simplify/Rep.hs b/src/Futhark/Optimise/Simplify/Rep.hs
--- a/src/Futhark/Optimise/Simplify/Rep.hs
+++ b/src/Futhark/Optimise/Simplify/Rep.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -38,8 +35,8 @@
 import Control.Category
 import Control.Monad.Identity
 import Control.Monad.Reader
-import qualified Data.Kind
-import qualified Data.Map.Strict as M
+import Data.Kind qualified
+import Data.Map.Strict qualified as M
 import Futhark.Analysis.Rephrase
 import Futhark.Builder
 import Futhark.IR
@@ -49,7 +46,7 @@
     VarAliases,
     unAliases,
   )
-import qualified Futhark.IR.Aliases as Aliases
+import Futhark.IR.Aliases qualified as Aliases
 import Futhark.IR.Prop.Aliases
 import Futhark.Transform.Rename
 import Futhark.Transform.Substitute
@@ -144,7 +141,7 @@
     withoutWisdom . expTypesFromPat . removePatWisdom
 
 instance Pretty VarWisdom where
-  ppr _ = ppr ()
+  pretty _ = pretty ()
 
 instance (PrettyRep rep, CanBeWise (Op rep)) => PrettyRep (Wise rep) where
   ppExpDec (_, dec) = ppExpDec dec . removeExpWisdom
diff --git a/src/Futhark/Optimise/Simplify/Rule.hs b/src/Futhark/Optimise/Simplify/Rule.hs
--- a/src/Futhark/Optimise/Simplify/Rule.hs
+++ b/src/Futhark/Optimise/Simplify/Rule.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 
@@ -56,8 +53,8 @@
 where
 
 import Control.Monad.State
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Builder
 import Futhark.IR
 
diff --git a/src/Futhark/Optimise/Simplify/Rules.hs b/src/Futhark/Optimise/Simplify/Rules.hs
--- a/src/Futhark/Optimise/Simplify/Rules.hs
+++ b/src/Futhark/Optimise/Simplify/Rules.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | This module defines a collection of simplification rules, as per
@@ -22,11 +20,11 @@
 import Control.Monad
 import Control.Monad.State
 import Data.List (insert, unzip4, zip4)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
@@ -302,4 +300,4 @@
 -- That's it!  We then let ordinary dead code elimination eventually
 -- simplify the body enough that we have an "identity" WithAcc.  There
 -- is no _guarantee_ that this will happen, but our general dead code
--- elimination tends to be pretty good.
+-- elimination tends to be prettyString good.
diff --git a/src/Futhark/Optimise/Simplify/Rules/BasicOp.hs b/src/Futhark/Optimise/Simplify/Rules/BasicOp.hs
--- a/src/Futhark/Optimise/Simplify/Rules/BasicOp.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/BasicOp.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -Wno-overlapping-patterns -Wno-incomplete-patterns -Wno-incomplete-uni-patterns -Wno-incomplete-record-updates #-}
 
@@ -14,7 +11,7 @@
 import Data.List (find, foldl', isSuffixOf, sort)
 import Data.List.NonEmpty (NonEmpty (..))
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
diff --git a/src/Futhark/Optimise/Simplify/Rules/ClosedForm.hs b/src/Futhark/Optimise/Simplify/Rules/ClosedForm.hs
--- a/src/Futhark/Optimise/Simplify/Rules/ClosedForm.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/ClosedForm.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | This module implements facilities for determining whether a
 -- reduction or fold can be expressed in a closed form (i.e. not as a
 -- SOAC).
@@ -14,7 +12,7 @@
 where
 
 import Control.Monad
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Construct
 import Futhark.IR
diff --git a/src/Futhark/Optimise/Simplify/Rules/Index.hs b/src/Futhark/Optimise/Simplify/Rules/Index.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Index.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Index.hs
@@ -10,7 +10,7 @@
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.Maybe
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Construct
 import Futhark.IR
 import Futhark.Optimise.Simplify.Rules.Simple
diff --git a/src/Futhark/Optimise/Simplify/Rules/Loop.hs b/src/Futhark/Optimise/Simplify/Rules/Loop.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Loop.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Loop.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Loop simplification rules.
 module Futhark.Optimise.Simplify.Rules.Loop (loopRules) where
 
@@ -9,8 +7,8 @@
 import Data.Maybe
 import Futhark.Analysis.DataDependencies
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Construct
 import Futhark.IR
 import Futhark.IR.Prop.Aliases
diff --git a/src/Futhark/Optimise/Simplify/Rules/Match.hs b/src/Futhark/Optimise/Simplify/Rules/Match.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Match.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Match.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Match simplification rules.
@@ -9,8 +7,8 @@
 import Data.Either
 import Data.List (partition, transpose, unzip4, zip5)
 import Futhark.Analysis.PrimExp.Convert
-import qualified Futhark.Analysis.SymbolTable as ST
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.SymbolTable qualified as ST
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.Construct
 import Futhark.IR
 import Futhark.Optimise.Simplify.Rule
diff --git a/src/Futhark/Optimise/Simplify/Rules/Simple.hs b/src/Futhark/Optimise/Simplify/Rules/Simple.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Simple.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Simple.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE TupleSections #-}
-
 -- | Particularly simple simplification rules.
 module Futhark.Optimise.Simplify.Rules.Simple
   ( TypeLookup,
@@ -10,7 +8,7 @@
 
 import Control.Monad
 import Data.List (isSuffixOf)
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.IR
 import Futhark.Util (focusNth)
diff --git a/src/Futhark/Optimise/Sink.hs b/src/Futhark/Optimise/Sink.hs
--- a/src/Futhark/Optimise/Sink.hs
+++ b/src/Futhark/Optimise/Sink.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | "Sinking" is conceptually the opposite of hoisting.  The idea is
@@ -48,11 +46,11 @@
 import Control.Monad.State
 import Data.Bifunctor
 import Data.List (foldl')
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Sequence ((<|))
-import qualified Data.Sequence as SQ
-import qualified Futhark.Analysis.Alias as Alias
-import qualified Futhark.Analysis.SymbolTable as ST
+import Data.Sequence qualified as SQ
+import Futhark.Analysis.Alias qualified as Alias
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Builder.Class
 import Futhark.Construct (sliceDim)
 import Futhark.IR.Aliases
diff --git a/src/Futhark/Optimise/TileLoops.hs b/src/Futhark/Optimise/TileLoops.hs
--- a/src/Futhark/Optimise/TileLoops.hs
+++ b/src/Futhark/Optimise/TileLoops.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TypeFamilies #-}
 
@@ -8,10 +7,10 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe (mapMaybe)
-import qualified Data.Sequence as Seq
-import qualified Futhark.Analysis.Alias as Alias
+import Data.Sequence qualified as Seq
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.IR.GPU
 import Futhark.IR.Prop.Aliases (consumedInStm)
 import Futhark.MonadFreshNames
@@ -1226,7 +1225,7 @@
   gid_x <- newVName "gid_x"
   gid_y <- newVName "gid_y"
 
-  tile_size_key <- nameFromString . pretty <$> newVName "tile_size"
+  tile_size_key <- nameFromString . prettyString <$> newVName "tile_size"
   tile_size <- letSubExp "tile_size" $ Op $ SizeOp $ GetSize tile_size_key SizeTile
   group_size <- letSubExp "group_size" $ BasicOp $ BinOp (Mul Int64 OverflowUndef) tile_size tile_size
 
diff --git a/src/Futhark/Optimise/TileLoops/Shared.hs b/src/Futhark/Optimise/TileLoops/Shared.hs
--- a/src/Futhark/Optimise/TileLoops/Shared.hs
+++ b/src/Futhark/Optimise/TileLoops/Shared.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 module Futhark.Optimise.TileLoops.Shared
   ( TileM,
     Env,
@@ -22,10 +20,10 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Data.List (foldl', zip4)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.IR.GPU
-import qualified Futhark.IR.Mem.IxFun as IxFun
-import qualified Futhark.IR.SeqMem as ExpMem
+import Futhark.IR.Mem.IxFun qualified as IxFun
+import Futhark.IR.SeqMem qualified as ExpMem
 import Futhark.MonadFreshNames
 import Futhark.Tools
 import Futhark.Transform.Rename
diff --git a/src/Futhark/Optimise/Unstream.hs b/src/Futhark/Optimise/Unstream.hs
--- a/src/Futhark/Optimise/Unstream.hs
+++ b/src/Futhark/Optimise/Unstream.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Sequentialise any remaining SOACs.  It is very important that
@@ -23,14 +22,14 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Futhark.IR.GPU
-import qualified Futhark.IR.GPU as GPU
+import Futhark.IR.GPU qualified as GPU
 import Futhark.IR.GPU.Simplify (simplifyGPU)
 import Futhark.IR.MC
-import qualified Futhark.IR.MC as MC
+import Futhark.IR.MC qualified as MC
 import Futhark.MonadFreshNames
 import Futhark.Pass
 import Futhark.Tools
-import qualified Futhark.Transform.FirstOrderTransform as FOT
+import Futhark.Transform.FirstOrderTransform qualified as FOT
 
 -- | The pass for GPU kernels.
 unstreamGPU :: Pass GPU GPU
diff --git a/src/Futhark/Pass.hs b/src/Futhark/Pass.hs
--- a/src/Futhark/Pass.hs
+++ b/src/Futhark/Pass.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE Strict #-}
 
 -- | Definition of a polymorphic (generic) pass that can work with
@@ -36,7 +34,7 @@
   getNameSource = PassM get
 
 -- | Execute a 'PassM' action, yielding logging information and either
--- an error text or a result.
+-- an error pretty or a result.
 runPassM ::
   MonadFreshNames m =>
   PassM a ->
@@ -51,7 +49,7 @@
     -- name via 'passLongOption'.
     passName :: String,
     -- | A slightly longer description, which will show up in the
-    -- command-line help text.
+    -- command-line help pretty.
     passDescription :: String,
     passFunction :: Prog fromrep -> PassM (Prog torep)
   }
diff --git a/src/Futhark/Pass/ExpandAllocations.hs b/src/Futhark/Pass/ExpandAllocations.hs
--- a/src/Futhark/Pass/ExpandAllocations.hs
+++ b/src/Futhark/Pass/ExpandAllocations.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Expand allocations inside of maps when possible.
@@ -11,15 +9,15 @@
 import Control.Monad.Writer
 import Data.Either (rights)
 import Data.List (find, foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Analysis.Rephrase
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.Error
 import Futhark.IR
-import qualified Futhark.IR.GPU.Simplify as GPU
+import Futhark.IR.GPU.Simplify qualified as GPU
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.MonadFreshNames
 import Futhark.Optimise.Simplify.Rep (addScopeWisdom)
 import Futhark.Pass
@@ -169,7 +167,7 @@
         (_, v, _) : _ ->
           throwError $
             "Cannot handle un-sliceable allocation size: "
-              ++ pretty v
+              ++ prettyString v
               ++ "\nLikely cause: irregular nested operations inside accumulator update operator."
         [] ->
           pure ()
@@ -211,7 +209,7 @@
     Just v ->
       throwError $
         "Cannot handle un-sliceable allocation size: "
-          ++ pretty v
+          ++ prettyString v
           ++ "\nLikely cause: irregular nested operations inside parallel constructs."
     Nothing ->
       pure ()
@@ -723,7 +721,7 @@
       fmap (stmsFromList . catMaybes) . mapM (unAllocStm nested) . stmsToList
 
     unAllocStm nested stm@(Let _ _ (Op Alloc {}))
-      | nested = throwError $ "Cannot handle nested allocation: " ++ pretty stm
+      | nested = throwError $ "Cannot handle nested allocation: " ++ prettyString stm
       | otherwise = pure Nothing
     unAllocStm _ (Let pat dec e) =
       Just <$> (Let <$> unAllocPat pat <*> pure dec <*> mapExpM unAlloc' e)
diff --git a/src/Futhark/Pass/ExplicitAllocations.hs b/src/Futhark/Pass/ExplicitAllocations.hs
--- a/src/Futhark/Pass/ExplicitAllocations.hs
+++ b/src/Futhark/Pass/ExplicitAllocations.hs
@@ -1,10 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
@@ -48,18 +41,18 @@
 import Data.Either (partitionEithers)
 import Data.Foldable (toList)
 import Data.List (foldl', transpose, zip4)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Analysis.SymbolTable (IndexOp)
-import qualified Futhark.Analysis.UsageTable as UT
+import Futhark.Analysis.UsageTable qualified as UT
 import Futhark.IR.Mem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.IR.Prop.Aliases (AliasedOp)
 import Futhark.MonadFreshNames
 import Futhark.Optimise.Simplify.Engine (SimpleOps (..))
-import qualified Futhark.Optimise.Simplify.Engine as Engine
+import Futhark.Optimise.Simplify.Engine qualified as Engine
 import Futhark.Optimise.Simplify.Rep (mkWiseBody)
 import Futhark.Pass
 import Futhark.Tools
@@ -263,7 +256,7 @@
       case maybeNth i idents of
         Just ident -> identName ident
         Nothing ->
-          error $ "getIdent: Ext " <> show i <> " but pattern has " <> show (length idents) <> " elements: " <> pretty idents
+          error $ "getIdent: Ext " <> show i <> " but pattern has " <> show (length idents) <> " elements: " <> prettyString idents
 
     instantiateExtIxFun idents = fmap $ fmap inst
       where
@@ -360,7 +353,7 @@
   SubExp ->
   WriterT ([SubExp], [SubExp]) (AllocM fromrep torep) SubExp
 ensureArrayIn _ (Constant v) =
-  error $ "ensureArrayIn: " ++ pretty v ++ " cannot be an array."
+  error $ "ensureArrayIn: " ++ prettyString v ++ " cannot be an array."
 ensureArrayIn space (Var v) = do
   (mem', v') <- lift $ ensureRowMajorArray (Just space) v
   (_, ixfun) <- lift $ lookupArraySummary v'
@@ -525,7 +518,7 @@
       addStm $ Let pat (defAux ()) $ BasicOp $ Manifest perm v
       pure (mem, v')
     _ ->
-      error $ "allocPermArray: " ++ pretty t
+      error $ "allocPermArray: " ++ prettyString t
 
 allocLinearArray ::
   (Allocable fromrep torep inner) =>
@@ -638,7 +631,7 @@
       case mem_info of
         MemMem space ->
           pure [(subExpRes $ Var mem, MemMem space)]
-        _ -> error $ "bodyReturnMemCtx: not a memory block: " ++ pretty mem
+        _ -> error $ "bodyReturnMemCtx: not a memory block: " ++ prettyString mem
 
 allocInFunBody ::
   (Allocable fromrep torep inner) =>
@@ -922,7 +915,7 @@
         case t of
           Prim Unit -> pure $ Param attrs pv $ MemPrim Unit
           Acc acc ispace ts u -> pure $ Param attrs pv $ MemAcc acc ispace ts u
-          _ -> error $ "Unexpected WithAcc lambda param: " ++ pretty (Param attrs pv t)
+          _ -> error $ "Unexpected WithAcc lambda param: " ++ prettyString (Param attrs pv t)
       allocInLambda params (lambdaBody lam)
 
     onInput (shape, arrs, op) =
@@ -955,7 +948,7 @@
       (mem, ixfun) <- lookupArraySummary arr
       pure $ mkP attrs p pt shape u mem ixfun is
     onXParam _ p _ =
-      error $ "Cannot handle MkAcc param: " ++ pretty p
+      error $ "Cannot handle MkAcc param: " ++ prettyString p
 
     onYParam _ (Param attrs p (Prim t)) _ =
       pure $ Param attrs p $ MemPrim t
@@ -966,7 +959,7 @@
           ixfun = IxFun.iota base_dims
       pure $ mkP attrs p pt shape u mem ixfun is
     onYParam _ p _ =
-      error $ "Cannot handle MkAcc param: " ++ pretty p
+      error $ "Cannot handle MkAcc param: " ++ prettyString p
 allocInExp e = mapExpM alloc e
   where
     alloc =
diff --git a/src/Futhark/Pass/ExplicitAllocations/GPU.hs b/src/Futhark/Pass/ExplicitAllocations/GPU.hs
--- a/src/Futhark/Pass/ExplicitAllocations/GPU.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/GPU.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -10,10 +8,10 @@
   )
 where
 
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.IR.GPU
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Pass.ExplicitAllocations
 import Futhark.Pass.ExplicitAllocations.SegOp
 
@@ -68,7 +66,7 @@
 handleHostOp (SizeOp op) =
   pure $ Inner $ SizeOp op
 handleHostOp (OtherOp op) =
-  error $ "Cannot allocate memory in SOAC: " ++ pretty op
+  error $ "Cannot allocate memory in SOAC: " ++ prettyString op
 handleHostOp (SegOp op) =
   Inner . SegOp <$> handleSegOp op
 handleHostOp (GPUBody ts (Body _ stms res)) =
diff --git a/src/Futhark/Pass/ExplicitAllocations/MC.hs b/src/Futhark/Pass/ExplicitAllocations/MC.hs
--- a/src/Futhark/Pass/ExplicitAllocations/MC.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/MC.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -31,7 +29,7 @@
 handleMCOp (ParOp par_op op) =
   Inner <$> (ParOp <$> traverse handleSegOp par_op <*> handleSegOp op)
 handleMCOp (OtherOp soac) =
-  error $ "Cannot allocate memory in SOAC: " ++ pretty soac
+  error $ "Cannot allocate memory in SOAC: " ++ prettyString soac
 
 -- | The pass from 'MC' to 'MCMem'.
 explicitAllocations :: Pass MC MCMem
diff --git a/src/Futhark/Pass/ExplicitAllocations/SegOp.hs b/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
--- a/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/SegOp.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -10,7 +8,7 @@
 where
 
 import Futhark.IR.GPUMem
-import qualified Futhark.IR.Mem.IxFun as IxFun
+import Futhark.IR.Mem.IxFun qualified as IxFun
 import Futhark.Pass.ExplicitAllocations
 
 instance SizeSubst (SegOp lvl rep)
diff --git a/src/Futhark/Pass/ExplicitAllocations/Seq.hs b/src/Futhark/Pass/ExplicitAllocations/Seq.hs
--- a/src/Futhark/Pass/ExplicitAllocations/Seq.hs
+++ b/src/Futhark/Pass/ExplicitAllocations/Seq.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-
 module Futhark.Pass.ExplicitAllocations.Seq
   ( explicitAllocations,
     simplifiable,
diff --git a/src/Futhark/Pass/ExtractKernels.hs b/src/Futhark/Pass/ExtractKernels.hs
--- a/src/Futhark/Pass/ExtractKernels.hs
+++ b/src/Futhark/Pass/ExtractKernels.hs
@@ -1,11 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Kernel extraction.
@@ -178,7 +171,7 @@
 import Futhark.Pass.ExtractKernels.StreamKernel
 import Futhark.Pass.ExtractKernels.ToGPU
 import Futhark.Tools
-import qualified Futhark.Transform.FirstOrderTransform as FOT
+import Futhark.Transform.FirstOrderTransform qualified as FOT
 import Futhark.Transform.Rename
 import Futhark.Util.Log
 import Prelude hiding (log)
diff --git a/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs b/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
--- a/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
+++ b/src/Futhark/Pass/ExtractKernels/BlockedKernel.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Pass.ExtractKernels.BlockedKernel
diff --git a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
--- a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
+++ b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
@@ -1,11 +1,4 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# OPTIONS_GHC -Wno-overlapping-patterns -Wno-incomplete-patterns -Wno-incomplete-uni-patterns -Wno-incomplete-record-updates #-}
 
@@ -43,11 +36,11 @@
 import Data.Function ((&))
 import Data.List (find, partition, tails)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
 import Futhark.IR
 import Futhark.IR.SOACS (SOACS)
-import qualified Futhark.IR.SOACS as SOACS
+import Futhark.IR.SOACS qualified as SOACS
 import Futhark.IR.SOACS.SOAC hiding (HistOp, histDest)
 import Futhark.IR.SOACS.Simplify (simpleSOACS, simplifyStms)
 import Futhark.IR.SegOp
@@ -58,7 +51,7 @@
 import Futhark.Pass.ExtractKernels.Interchange
 import Futhark.Tools
 import Futhark.Transform.CopyPropagate
-import qualified Futhark.Transform.FirstOrderTransform as FOT
+import Futhark.Transform.FirstOrderTransform qualified as FOT
 import Futhark.Transform.Rename
 import Futhark.Util
 import Futhark.Util.Log
diff --git a/src/Futhark/Pass/ExtractKernels/Distribution.hs b/src/Futhark/Pass/ExtractKernels/Distribution.hs
--- a/src/Futhark/Pass/ExtractKernels/Distribution.hs
+++ b/src/Futhark/Pass/ExtractKernels/Distribution.hs
@@ -1,10 +1,4 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Pass.ExtractKernels.Distribution
@@ -49,7 +43,7 @@
 import Data.Bifunctor (second)
 import Data.Foldable
 import Data.List (elemIndex, sortOn)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.IR
 import Futhark.IR.SegOp
@@ -82,7 +76,7 @@
 ppTargets (Targets target targets) =
   unlines $ map ppTarget $ targets ++ [target]
   where
-    ppTarget (pat, res) = pretty pat ++ " <- " ++ pretty res
+    ppTarget (pat, res) = prettyString pat ++ " <- " ++ prettyString res
 
 singleTarget :: Target -> Targets
 singleTarget = flip Targets []
@@ -132,9 +126,9 @@
 
 ppLoopNesting :: LoopNesting -> String
 ppLoopNesting (MapNesting _ _ _ params_and_arrs) =
-  pretty (map fst params_and_arrs)
+  prettyString (map fst params_and_arrs)
     ++ " <- "
-    ++ pretty (map snd params_and_arrs)
+    ++ prettyString (map snd params_and_arrs)
 
 loopNestingParams :: LoopNesting -> [Param Type]
 loopNestingParams = map fst . loopNestingParamsAndArrs
@@ -559,10 +553,10 @@
         distributed' <- renameStm kernel_stm
         logMsg $
           "distributing\n"
-            ++ unlines (map pretty $ stmsToList stms)
-            ++ pretty (snd $ innerTarget targets)
+            ++ unlines (map prettyString $ stmsToList stms)
+            ++ prettyString (snd $ innerTarget targets)
             ++ "\nas\n"
-            ++ pretty distributed'
+            ++ prettyString distributed'
             ++ "\ndue to targets\n"
             ++ ppTargets targets
             ++ "\nand with new targets\n"
diff --git a/src/Futhark/Pass/ExtractKernels/ISRWIM.hs b/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
--- a/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
+++ b/src/Futhark/Pass/ExtractKernels/ISRWIM.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Interchanging scans with inner maps.
diff --git a/src/Futhark/Pass/ExtractKernels/Interchange.hs b/src/Futhark/Pass/ExtractKernels/Interchange.hs
--- a/src/Futhark/Pass/ExtractKernels/Interchange.hs
+++ b/src/Futhark/Pass/ExtractKernels/Interchange.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | It is well known that fully parallel loops can always be
diff --git a/src/Futhark/Pass/ExtractKernels/Intragroup.hs b/src/Futhark/Pass/ExtractKernels/Intragroup.hs
--- a/src/Futhark/Pass/ExtractKernels/Intragroup.hs
+++ b/src/Futhark/Pass/ExtractKernels/Intragroup.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Extract limited nested parallelism for execution inside
@@ -10,11 +7,11 @@
 import Control.Monad.Identity
 import Control.Monad.RWS
 import Control.Monad.Trans.Maybe
-import qualified Data.Map.Strict as M
-import qualified Data.Set as S
+import Data.Map.Strict qualified as M
+import Data.Set qualified as S
 import Futhark.Analysis.PrimExp.Convert
 import Futhark.IR.GPU hiding (HistOp)
-import qualified Futhark.IR.GPU.Op as GPU
+import Futhark.IR.GPU.Op qualified as GPU
 import Futhark.IR.SOACS
 import Futhark.MonadFreshNames
 import Futhark.Pass.ExtractKernels.BlockedKernel
@@ -22,7 +19,7 @@
 import Futhark.Pass.ExtractKernels.Distribution
 import Futhark.Pass.ExtractKernels.ToGPU
 import Futhark.Tools
-import qualified Futhark.Transform.FirstOrderTransform as FOT
+import Futhark.Transform.FirstOrderTransform qualified as FOT
 import Futhark.Util.Log
 import Prelude hiding (log)
 
diff --git a/src/Futhark/Pass/ExtractKernels/StreamKernel.hs b/src/Futhark/Pass/ExtractKernels/StreamKernel.hs
--- a/src/Futhark/Pass/ExtractKernels/StreamKernel.hs
+++ b/src/Futhark/Pass/ExtractKernels/StreamKernel.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Pass.ExtractKernels.StreamKernel
@@ -46,7 +44,7 @@
   SubExp ->
   m (SubExp, SubExp)
 numberOfGroups desc w group_size = do
-  max_num_groups_key <- nameFromString . pretty <$> newVName (desc ++ "_num_groups")
+  max_num_groups_key <- nameFromString . prettyString <$> newVName (desc ++ "_num_groups")
   num_groups <-
     letSubExp "num_groups" $
       Op $
diff --git a/src/Futhark/Pass/ExtractKernels/ToGPU.hs b/src/Futhark/Pass/ExtractKernels/ToGPU.hs
--- a/src/Futhark/Pass/ExtractKernels/ToGPU.hs
+++ b/src/Futhark/Pass/ExtractKernels/ToGPU.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module Futhark.Pass.ExtractKernels.ToGPU
@@ -19,7 +17,7 @@
 import Futhark.IR
 import Futhark.IR.GPU
 import Futhark.IR.SOACS (SOACS)
-import qualified Futhark.IR.SOACS.SOAC as SOAC
+import Futhark.IR.SOACS.SOAC qualified as SOAC
 import Futhark.Tools
 
 getSize ::
@@ -28,7 +26,7 @@
   SizeClass ->
   m SubExp
 getSize desc size_class = do
-  size_key <- nameFromString . pretty <$> newVName desc
+  size_key <- nameFromString . prettyString <$> newVName desc
   letSubExp desc $ Op $ SizeOp $ GetSize size_key size_class
 
 segThread ::
diff --git a/src/Futhark/Pass/ExtractMulticore.hs b/src/Futhark/Pass/ExtractMulticore.hs
--- a/src/Futhark/Pass/ExtractMulticore.hs
+++ b/src/Futhark/Pass/ExtractMulticore.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Extraction of parallelism from a SOACs program.  This generates
@@ -15,7 +12,7 @@
 import Futhark.Analysis.Rephrase
 import Futhark.IR
 import Futhark.IR.MC
-import qualified Futhark.IR.MC as MC
+import Futhark.IR.MC qualified as MC
 import Futhark.IR.SOACS hiding
   ( Body,
     Exp,
@@ -24,7 +21,7 @@
     Pat,
     Stm,
   )
-import qualified Futhark.IR.SOACS as SOACS
+import Futhark.IR.SOACS qualified as SOACS
 import Futhark.Pass
 import Futhark.Pass.ExtractKernels.DistributeNests
 import Futhark.Pass.ExtractKernels.ToGPU (injectSOACS)
diff --git a/src/Futhark/Pass/FirstOrderTransform.hs b/src/Futhark/Pass/FirstOrderTransform.hs
--- a/src/Futhark/Pass/FirstOrderTransform.hs
+++ b/src/Futhark/Pass/FirstOrderTransform.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Transform any SOACs to @for@-loops.
diff --git a/src/Futhark/Pass/KernelBabysitting.hs b/src/Futhark/Pass/KernelBabysitting.hs
--- a/src/Futhark/Pass/KernelBabysitting.hs
+++ b/src/Futhark/Pass/KernelBabysitting.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | Do various kernel optimisations - mostly related to coalescing.
@@ -8,7 +7,7 @@
 import Control.Monad.State.Strict
 import Data.Foldable
 import Data.List (elemIndex, isPrefixOf, sort)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.IR
 import Futhark.IR.GPU hiding
diff --git a/src/Futhark/Pass/Simplify.hs b/src/Futhark/Pass/Simplify.hs
--- a/src/Futhark/Pass/Simplify.hs
+++ b/src/Futhark/Pass/Simplify.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 module Futhark.Pass.Simplify
   ( simplify,
     simplifySOACS,
@@ -12,13 +10,13 @@
   )
 where
 
-import qualified Futhark.IR.GPU.Simplify as GPU
-import qualified Futhark.IR.GPUMem as GPUMem
-import qualified Futhark.IR.MC as MC
-import qualified Futhark.IR.MCMem as MCMem
-import qualified Futhark.IR.SOACS.Simplify as SOACS
-import qualified Futhark.IR.Seq as Seq
-import qualified Futhark.IR.SeqMem as SeqMem
+import Futhark.IR.GPU.Simplify qualified as GPU
+import Futhark.IR.GPUMem qualified as GPUMem
+import Futhark.IR.MC qualified as MC
+import Futhark.IR.MCMem qualified as MCMem
+import Futhark.IR.SOACS.Simplify qualified as SOACS
+import Futhark.IR.Seq qualified as Seq
+import Futhark.IR.SeqMem qualified as SeqMem
 import Futhark.IR.Syntax
 import Futhark.Pass
 
diff --git a/src/Futhark/Passes.hs b/src/Futhark/Passes.hs
--- a/src/Futhark/Passes.hs
+++ b/src/Futhark/Passes.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Optimisation pipelines.
 module Futhark.Passes
   ( standardPipeline,
@@ -28,7 +26,7 @@
 import Futhark.Optimise.HistAccs
 import Futhark.Optimise.InPlaceLowering
 import Futhark.Optimise.InliningDeadFun
-import qualified Futhark.Optimise.MemoryBlockMerging as MemoryBlockMerging
+import Futhark.Optimise.MemoryBlockMerging qualified as MemoryBlockMerging
 import Futhark.Optimise.MergeGPUBodies
 import Futhark.Optimise.ReduceDeviceSyncs
 import Futhark.Optimise.Sink
@@ -36,9 +34,9 @@
 import Futhark.Optimise.Unstream
 import Futhark.Pass.AD
 import Futhark.Pass.ExpandAllocations
-import qualified Futhark.Pass.ExplicitAllocations.GPU as GPU
-import qualified Futhark.Pass.ExplicitAllocations.MC as MC
-import qualified Futhark.Pass.ExplicitAllocations.Seq as Seq
+import Futhark.Pass.ExplicitAllocations.GPU qualified as GPU
+import Futhark.Pass.ExplicitAllocations.MC qualified as MC
+import Futhark.Pass.ExplicitAllocations.Seq qualified as Seq
 import Futhark.Pass.ExtractKernels
 import Futhark.Pass.ExtractMulticore
 import Futhark.Pass.FirstOrderTransform
diff --git a/src/Futhark/Pipeline.hs b/src/Futhark/Pipeline.hs
--- a/src/Futhark/Pipeline.hs
+++ b/src/Futhark/Pipeline.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Definition of the core compiler driver building blocks.  The
 -- spine of the compiler is the 'FutharkM' monad, although note that
 -- individual passes are pure functions, and do not use the 'FutharkM'
@@ -33,10 +28,10 @@
 import Control.Monad.Reader
 import Control.Monad.State
 import Control.Monad.Writer.Strict hiding (pass)
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Time.Clock
-import qualified Futhark.Analysis.Alias as Alias
+import Futhark.Analysis.Alias qualified as Alias
 import Futhark.Compiler.Config (Verbosity (..))
 import Futhark.Error
 import Futhark.IR (PrettyRep, Prog)
diff --git a/src/Futhark/Pkg/Info.hs b/src/Futhark/Pkg/Info.hs
--- a/src/Futhark/Pkg/Info.hs
+++ b/src/Futhark/Pkg/Info.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Obtaining information about packages over THE INTERNET!
 module Futhark.Pkg.Info
   ( -- * Package info
@@ -19,22 +17,22 @@
   )
 where
 
-import qualified Codec.Archive.Zip as Zip
+import Codec.Archive.Zip qualified as Zip
 import Control.Monad.IO.Class
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as LBS
+import Data.ByteString qualified as BS
+import Data.ByteString.Lazy qualified as LBS
 import Data.IORef
 import Data.List (foldl', intersperse)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
 import Data.Time (UTCTime, defaultTimeLocale, formatTime, getCurrentTime)
 import Futhark.Pkg.Types
 import Futhark.Util (maybeHead)
 import Futhark.Util.Log
 import System.Exit
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 import System.IO
 import System.Process.ByteString (readProcessWithExitCode)
 
diff --git a/src/Futhark/Pkg/Solve.hs b/src/Futhark/Pkg/Solve.hs
--- a/src/Futhark/Pkg/Solve.hs
+++ b/src/Futhark/Pkg/Solve.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Dependency solver
 --
 -- This is a relatively simple problem due to the choice of the
@@ -15,9 +12,9 @@
 
 import Control.Monad.Free.Church
 import Control.Monad.State
-import qualified Data.Map as M
-import qualified Data.Set as S
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Futhark.Pkg.Info
 import Futhark.Pkg.Types
 import Prelude
diff --git a/src/Futhark/Pkg/Types.hs b/src/Futhark/Pkg/Types.hs
--- a/src/Futhark/Pkg/Types.hs
+++ b/src/Futhark/Pkg/Types.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Types (and a few other simple definitions) for futhark-pkg.
 module Futhark.Pkg.Types
   ( PkgPath,
@@ -41,16 +39,16 @@
 import Data.Either
 import Data.Foldable
 import Data.List (sortOn)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Traversable
 import Data.Versions (SemVer (..), VUnit (..), prettySemVer)
 import Data.Void
 import System.FilePath
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 import Text.Megaparsec hiding (many, some)
 import Text.Megaparsec.Char
 import Prelude
@@ -307,7 +305,7 @@
         comment = Just <$> pComment
         blankLine = some spaceChar >> pure Nothing
 
--- | Parse a text as a 'PkgManifest'.  The 'FilePath' is used for any error messages.
+-- | Parse a pretty as a 'PkgManifest'.  The 'FilePath' is used for any error messages.
 parsePkgManifest :: FilePath -> T.Text -> Either (ParseErrorBundle T.Text Void) PkgManifest
 parsePkgManifest = parse pPkgManifest
 
diff --git a/src/Futhark/Script.hs b/src/Futhark/Script.hs
--- a/src/Futhark/Script.hs
+++ b/src/Futhark/Script.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 -- | FutharkScript is a (tiny) subset of Futhark used to write small
 -- expressions that are evaluated by server executables.  The @futhark
 -- literate@ command is the main user.
@@ -41,17 +37,17 @@
 import Data.Functor
 import Data.IORef
 import Data.List (intersperse)
-import qualified Data.Map as M
-import qualified Data.Set as S
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Data.Traversable
 import Data.Void
-import qualified Futhark.Data.Parser as V
+import Futhark.Data.Parser qualified as V
 import Futhark.Server
 import Futhark.Server.Values (getValue, putValue)
-import qualified Futhark.Test.Values as V
+import Futhark.Test.Values qualified as V
 import Futhark.Util (nubOrd)
-import Futhark.Util.Pretty hiding (float, line, sep, space, string, (</>), (<|>))
+import Futhark.Util.Pretty hiding (line, sep, space, (</>))
 import Language.Futhark.Core (Name, nameFromText, nameToText)
 import Language.Futhark.Tuple (areTupleFields)
 import Text.Megaparsec
@@ -119,28 +115,29 @@
   deriving (Show)
 
 instance Pretty Func where
-  ppr (FuncFut f) = ppr f
-  ppr (FuncBuiltin f) = "$" <> ppr f
+  pretty (FuncFut f) = pretty f
+  pretty (FuncBuiltin f) = "$" <> pretty f
 
 instance Pretty Exp where
-  ppr = pprPrec 0
-  pprPrec _ (ServerVar _ v) = "$" <> ppr v
-  pprPrec _ (Const v) = stack $ map strictText $ T.lines $ V.valueText v
-  pprPrec i (Let pat e1 e2) =
-    parensIf (i > 0) $ "let" <+> pat' <+> equals <+> ppr e1 <+> "in" <+> ppr e2
-    where
-      pat' = case pat of
-        [x] -> ppr x
-        _ -> parens $ commasep $ map ppr pat
-  pprPrec _ (Call v []) = ppr v
-  pprPrec i (Call v args) =
-    parensIf (i > 0) $ ppr v <+> spread (map (align . pprPrec 1) args)
-  pprPrec _ (Tuple vs) =
-    parens $ commasep $ map (align . ppr) vs
-  pprPrec _ (StringLit s) = ppr $ show s
-  pprPrec _ (Record m) = braces $ commasep $ map field m
+  pretty = pprPrec (0 :: Int)
     where
-      field (k, v) = align (ppr k <> equals <> ppr v)
+      pprPrec _ (ServerVar _ v) = "$" <> pretty v
+      pprPrec _ (Const v) = stack $ map pretty $ T.lines $ V.valueText v
+      pprPrec i (Let pat e1 e2) =
+        parensIf (i > 0) $ "let" <+> pat' <+> equals <+> pretty e1 <+> "in" <+> pretty e2
+        where
+          pat' = case pat of
+            [x] -> pretty x
+            _ -> parens $ align $ commasep $ map pretty pat
+      pprPrec _ (Call v []) = pretty v
+      pprPrec i (Call v args) =
+        parensIf (i > 0) $ pretty v <+> hsep (map (align . pprPrec 1) args)
+      pprPrec _ (Tuple vs) =
+        parens $ commasep $ map (align . pretty) vs
+      pprPrec _ (StringLit s) = pretty $ show s
+      pprPrec _ (Record m) = braces $ align $ commasep $ map field m
+        where
+          field (k, v) = align (pretty k <> equals <> pretty v)
 
 type Parser = Parsec Void T.Text
 
@@ -248,13 +245,13 @@
   deriving (Eq, Show)
 
 instance Pretty ScriptValueType where
-  ppr (STValue t) = ppr t
-  ppr (STFun ins outs) =
-    spread $ intersperse "->" (map ppr ins ++ [outs'])
+  pretty (STValue t) = pretty t
+  pretty (STFun ins outs) =
+    hsep $ intersperse "->" (map pretty ins ++ [outs'])
     where
       outs' = case outs of
-        [out] -> strictText out
-        _ -> parens $ commasep $ map strictText outs
+        [out] -> pretty out
+        _ -> parens $ commasep $ map pretty outs
 
 -- | A Haskell-level value or a variable on the server.
 data ValOrVar = VVal V.Value | VVar VarName
@@ -426,7 +423,7 @@
         case V.putValue s of
           Just s' ->
             pure $ V.ValueAtom $ SValue (V.valueTypeTextNoDims (V.valueType s')) $ VVal s'
-          Nothing -> error $ "Unable to write value " ++ pretty s
+          Nothing -> error $ "Unable to write value " ++ prettyString s
       evalExp' _ (Const val) =
         pure $ V.ValueAtom $ SValue (V.valueTypeTextNoDims (V.valueType val)) $ VVal val
       evalExp' vtable (Tuple es) =
diff --git a/src/Futhark/Test.hs b/src/Futhark/Test.hs
--- a/src/Futhark/Test.hs
+++ b/src/Futhark/Test.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Facilities for reading Futhark test programs.  A Futhark test
 -- program is an ordinary Futhark program where an initial comment
 -- block specifies input- and output-sets.
@@ -34,25 +30,25 @@
 import Codec.Compression.Zlib.Internal (DecompressError)
 import Control.Applicative
 import Control.Exception (catch)
-import qualified Control.Exception.Base as E
+import Control.Exception.Base qualified as E
 import Control.Monad
 import Control.Monad.Except
-import qualified Data.Binary as Bin
-import qualified Data.ByteString as SBS
-import qualified Data.ByteString.Lazy as BS
+import Data.Binary qualified as Bin
+import Data.ByteString qualified as SBS
+import Data.ByteString.Lazy qualified as BS
 import Data.Char
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.IO as T
-import qualified Futhark.Script as Script
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.IO qualified as T
+import Futhark.Script qualified as Script
 import Futhark.Server
 import Futhark.Server.Values
 import Futhark.Test.Spec
-import qualified Futhark.Test.Values as V
+import Futhark.Test.Values qualified as V
 import Futhark.Util (isEnvVarAtLeast, pmapIO)
-import Futhark.Util.Pretty (prettyOneLine, prettyText, prettyTextOneLine)
+import Futhark.Util.Pretty (prettyText, prettyTextOneLine)
 import System.Directory
 import System.Exit
 import System.FilePath
@@ -97,7 +93,7 @@
   s <- BS.readFile file
   E.evaluate $ decompress s
 
--- | Extract a pretty representation of some 'Values'.  In the IO
+-- | Extract a prettyString representation of some 'Values'.  In the IO
 -- monad because this might involve reading from a file.  There is no
 -- guarantee that the resulting byte string yields a readable value.
 getValuesBS :: (MonadFail m, MonadIO m) => FutharkExe -> FilePath -> Values -> m BS.ByteString
@@ -116,7 +112,9 @@
 getValuesBS futhark dir (GenValues gens) =
   mconcat <$> mapM (getGenBS futhark dir) gens
 getValuesBS _ _ (ScriptValues e) =
-  fail $ "Cannot get values from FutharkScript expression: " <> prettyOneLine e
+  fail $
+    "Cannot get values from FutharkScript expression: "
+      <> T.unpack (prettyTextOneLine e)
 getValuesBS _ _ (ScriptFile f) =
   fail $ "Cannot get values from FutharkScript file: " <> f
 
diff --git a/src/Futhark/Test/Spec.hs b/src/Futhark/Test/Spec.hs
--- a/src/Futhark/Test/Spec.hs
+++ b/src/Futhark/Test/Spec.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Definition and parsing of a test specification.
 module Futhark.Test.Spec
   ( testSpecFromProgram,
@@ -31,18 +28,18 @@
 import Data.Char
 import Data.Functor
 import Data.List (foldl')
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
 import Data.Void
 import Futhark.Analysis.Metrics.Type
 import Futhark.Data.Parser
-import qualified Futhark.Data.Parser as V
-import qualified Futhark.Script as Script
-import qualified Futhark.Test.Values as V
+import Futhark.Data.Parser qualified as V
+import Futhark.Script qualified as Script
+import Futhark.Test.Values qualified as V
 import Futhark.Util (directoryContents)
-import Futhark.Util.Pretty (prettyOneLine)
+import Futhark.Util.Pretty (prettyTextOneLine)
 import System.Exit
 import System.FilePath
 import System.IO
@@ -279,7 +276,7 @@
     desc _ (GenValues gens) =
       unwords $ map genValueType gens
     desc _ (ScriptValues e) =
-      prettyOneLine e
+      T.unpack $ prettyTextOneLine e
     desc _ (ScriptFile path) =
       path
 
diff --git a/src/Futhark/Test/Values.hs b/src/Futhark/Test/Values.hs
--- a/src/Futhark/Test/Values.hs
+++ b/src/Futhark/Test/Values.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE Strict #-}
-{-# LANGUAGE Trustworthy #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 -- | This module provides an efficient value representation as well as
@@ -16,8 +14,8 @@
   )
 where
 
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Data.Traversable
 import Futhark.Data
 import Futhark.Data.Compare
@@ -25,10 +23,10 @@
 import Futhark.Util.Pretty
 
 instance Pretty Value where
-  ppr = strictText . valueText
+  pretty = pretty . valueText
 
 instance Pretty ValueType where
-  ppr = strictText . valueTypeText
+  pretty = pretty . valueTypeText
 
 -- | The structure of a compound value, parameterised over the actual
 -- values.  For most cases you probably want 'CompoundValue'.
@@ -51,11 +49,11 @@
   traverse f (ValueRecord m) = ValueRecord <$> traverse (traverse f) m
 
 instance Pretty v => Pretty (Compound v) where
-  ppr (ValueAtom v) = ppr v
-  ppr (ValueTuple vs) = parens $ commasep $ map ppr vs
-  ppr (ValueRecord m) = braces $ commasep $ map field $ M.toList m
+  pretty (ValueAtom v) = pretty v
+  pretty (ValueTuple vs) = parens $ commasep $ map pretty vs
+  pretty (ValueRecord m) = braces $ commasep $ map field $ M.toList m
     where
-      field (k, v) = ppr k <> equals <> ppr v
+      field (k, v) = pretty k <> equals <> pretty v
 
 -- | Create a tuple for a non-unit list, and otherwise a 'ValueAtom'
 mkCompound :: [Compound v] -> Compound v
diff --git a/src/Futhark/Tools.hs b/src/Futhark/Tools.hs
--- a/src/Futhark/Tools.hs
+++ b/src/Futhark/Tools.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | An unstructured grab-bag of various tools and inspection
diff --git a/src/Futhark/Transform/CopyPropagate.hs b/src/Futhark/Transform/CopyPropagate.hs
--- a/src/Futhark/Transform/CopyPropagate.hs
+++ b/src/Futhark/Transform/CopyPropagate.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Perform copy propagation.  This is done by invoking the
 -- simplifier with no rules, so hoisting and dead-code elimination may
 -- also take place.
@@ -11,7 +8,7 @@
   )
 where
 
-import qualified Futhark.Analysis.SymbolTable as ST
+import Futhark.Analysis.SymbolTable qualified as ST
 import Futhark.IR
 import Futhark.MonadFreshNames
 import Futhark.Optimise.Simplify
diff --git a/src/Futhark/Transform/FirstOrderTransform.hs b/src/Futhark/Transform/FirstOrderTransform.hs
--- a/src/Futhark/Transform/FirstOrderTransform.hs
+++ b/src/Futhark/Transform/FirstOrderTransform.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
 
 -- | The code generator cannot handle the array combinators (@map@ and
@@ -21,9 +19,9 @@
 import Control.Monad.Except
 import Control.Monad.State
 import Data.List (find, zip4)
-import qualified Data.Map.Strict as M
-import qualified Futhark.Analysis.Alias as Alias
-import qualified Futhark.IR as AST
+import Data.Map.Strict qualified as M
+import Futhark.Analysis.Alias qualified as Alias
+import Futhark.IR qualified as AST
 import Futhark.IR.Prop.Aliases
 import Futhark.IR.SOACS
 import Futhark.MonadFreshNames
diff --git a/src/Futhark/Transform/Rename.hs b/src/Futhark/Transform/Rename.hs
--- a/src/Futhark/Transform/Rename.hs
+++ b/src/Futhark/Transform/Rename.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE Trustworthy #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 -- | This module provides facilities for transforming Futhark programs
@@ -34,7 +29,7 @@
 
 import Control.Monad.Reader
 import Control.Monad.State
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.FreshNames hiding (newName)
 import Futhark.IR.Prop.Names
diff --git a/src/Futhark/Transform/Substitute.hs b/src/Futhark/Transform/Substitute.hs
--- a/src/Futhark/Transform/Substitute.hs
+++ b/src/Futhark/Transform/Substitute.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 -- |
@@ -15,7 +12,7 @@
 where
 
 import Control.Monad.Identity
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Futhark.Analysis.PrimExp
 import Futhark.IR.Prop.Names
 import Futhark.IR.Prop.Scope
diff --git a/src/Futhark/Util.hs b/src/Futhark/Util.hs
--- a/src/Futhark/Util.hs
+++ b/src/Futhark/Util.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE Trustworthy #-}
-
 -- | Non-Futhark-specific utilities.  If you find yourself writing
 -- general functions on generic data structures, consider putting them
 -- here.
@@ -31,26 +29,9 @@
     isEnvVarAtLeast,
     startupTime,
     fancyTerminal,
+    hFancyTerminal,
     runProgramWithExitCode,
     directoryContents,
-    roundFloat,
-    ceilFloat,
-    floorFloat,
-    roundDouble,
-    ceilDouble,
-    floorDouble,
-    lgamma,
-    lgammaf,
-    tgamma,
-    tgammaf,
-    erf,
-    erff,
-    erfc,
-    erfcf,
-    cbrt,
-    cbrtf,
-    hypot,
-    hypotf,
     fromPOSIX,
     toPOSIX,
     trim,
@@ -72,29 +53,29 @@
 import Control.Exception
 import Control.Monad
 import Crypto.Hash.MD5 as MD5
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Base16 as Base16
+import Data.ByteString qualified as BS
+import Data.ByteString.Base16 qualified as Base16
 import Data.Char
 import Data.Either
 import Data.Foldable (fold)
 import Data.Function ((&))
 import Data.List (foldl', genericDrop, genericSplitAt, sortBy)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.Encoding.Error as T
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.Encoding.Error qualified as T
 import Data.Time.Clock (UTCTime, getCurrentTime)
 import Data.Tuple (swap)
 import Numeric
-import qualified System.Directory.Tree as Dir
+import System.Directory.Tree qualified as Dir
 import System.Environment
 import System.Exit
-import qualified System.FilePath as Native
-import qualified System.FilePath.Posix as Posix
-import System.IO (hIsTerminalDevice, stdout)
+import System.FilePath qualified as Native
+import System.FilePath.Posix qualified as Posix
+import System.IO (Handle, hIsTerminalDevice, stdout)
 import System.IO.Error (isDoesNotExistError)
 import System.IO.Unsafe
 import System.Process.ByteString
@@ -202,8 +183,8 @@
   | (bef, x : aft) <- genericSplitAt i xs = Just (bef, x, aft)
   | otherwise = Nothing
 
--- | Compute a hash of a text that is stable across OS versions.
--- Returns the hash as a text as well, ready for human consumption.
+-- | Compute a hash of a pretty that is stable across OS versions.
+-- Returns the hash as a pretty as well, ready for human consumption.
 hashText :: T.Text -> T.Text
 hashText =
   T.decodeUtf8With T.lenientDecode . Base16.encode . MD5.hash . T.encodeUtf8
@@ -235,8 +216,13 @@
 -- | Are we running in a terminal capable of fancy commands and
 -- visualisation?
 fancyTerminal :: Bool
-fancyTerminal = unsafePerformIO $ do
-  isTTY <- hIsTerminalDevice stdout
+fancyTerminal = unsafePerformIO $ hFancyTerminal stdout
+
+-- | Is this handle connected to a terminal capable of fancy commands
+-- and visualisation?
+hFancyTerminal :: Handle -> IO Bool
+hFancyTerminal h = do
+  isTTY <- hIsTerminalDevice h
   isDumb <- (Just "dumb" ==) <$> lookupEnv "TERM"
   pure $ isTTY && not isDumb
 
@@ -266,114 +252,6 @@
   where
     isFile (Dir.File _ path) = Just path
     isFile _ = Nothing
-
-foreign import ccall "nearbyint" c_nearbyint :: Double -> Double
-
-foreign import ccall "nearbyintf" c_nearbyintf :: Float -> Float
-
-foreign import ccall "ceil" c_ceil :: Double -> Double
-
-foreign import ccall "ceilf" c_ceilf :: Float -> Float
-
-foreign import ccall "floor" c_floor :: Double -> Double
-
-foreign import ccall "floorf" c_floorf :: Float -> Float
-
--- | Round a single-precision floating point number correctly.
-roundFloat :: Float -> Float
-roundFloat = c_nearbyintf
-
--- | Round a single-precision floating point number upwards correctly.
-ceilFloat :: Float -> Float
-ceilFloat = c_ceilf
-
--- | Round a single-precision floating point number downwards correctly.
-floorFloat :: Float -> Float
-floorFloat = c_floorf
-
--- | Round a double-precision floating point number correctly.
-roundDouble :: Double -> Double
-roundDouble = c_nearbyint
-
--- | Round a double-precision floating point number upwards correctly.
-ceilDouble :: Double -> Double
-ceilDouble = c_ceil
-
--- | Round a double-precision floating point number downwards correctly.
-floorDouble :: Double -> Double
-floorDouble = c_floor
-
-foreign import ccall "lgamma" c_lgamma :: Double -> Double
-
-foreign import ccall "lgammaf" c_lgammaf :: Float -> Float
-
-foreign import ccall "tgamma" c_tgamma :: Double -> Double
-
-foreign import ccall "tgammaf" c_tgammaf :: Float -> Float
-
--- | The system-level @lgamma()@ function.
-lgamma :: Double -> Double
-lgamma = c_lgamma
-
--- | The system-level @lgammaf()@ function.
-lgammaf :: Float -> Float
-lgammaf = c_lgammaf
-
--- | The system-level @tgamma()@ function.
-tgamma :: Double -> Double
-tgamma = c_tgamma
-
--- | The system-level @tgammaf()@ function.
-tgammaf :: Float -> Float
-tgammaf = c_tgammaf
-
-foreign import ccall "hypot" c_hypot :: Double -> Double -> Double
-
-foreign import ccall "hypotf" c_hypotf :: Float -> Float -> Float
-
--- | The system-level @hypot@ function.
-hypot :: Double -> Double -> Double
-hypot = c_hypot
-
--- | The system-level @hypotf@ function.
-hypotf :: Float -> Float -> Float
-hypotf = c_hypotf
-
-foreign import ccall "erf" c_erf :: Double -> Double
-
-foreign import ccall "erff" c_erff :: Float -> Float
-
-foreign import ccall "erfc" c_erfc :: Double -> Double
-
-foreign import ccall "erfcf" c_erfcf :: Float -> Float
-
--- | The system-level @erf()@ function.
-erf :: Double -> Double
-erf = c_erf
-
--- | The system-level @erff()@ function.
-erff :: Float -> Float
-erff = c_erff
-
--- | The system-level @erfc()@ function.
-erfc :: Double -> Double
-erfc = c_erfc
-
--- | The system-level @erfcf()@ function.
-erfcf :: Float -> Float
-erfcf = c_erfcf
-
-foreign import ccall "cbrt" c_cbrt :: Double -> Double
-
-foreign import ccall "cbrtf" c_cbrtf :: Float -> Float
-
--- | The system-level @cbrt@ function.
-cbrt :: Double -> Double
-cbrt = c_cbrt
-
--- | The system-level @cbrtf@ function.
-cbrtf :: Float -> Float
-cbrtf = c_cbrtf
 
 -- | Turn a POSIX filepath into a filepath for the native system.
 toPOSIX :: Native.FilePath -> Posix.FilePath
diff --git a/src/Futhark/Util/CMath.hs b/src/Futhark/Util/CMath.hs
new file mode 100644
--- /dev/null
+++ b/src/Futhark/Util/CMath.hs
@@ -0,0 +1,148 @@
+-- | Bindings to the C math library.
+--
+-- Follows the naming scheme of the C functions when feasible.
+module Futhark.Util.CMath
+  ( roundFloat,
+    ceilFloat,
+    floorFloat,
+    roundDouble,
+    ceilDouble,
+    floorDouble,
+    nextafterf,
+    nextafter,
+    lgamma,
+    lgammaf,
+    tgamma,
+    tgammaf,
+    erf,
+    erff,
+    erfc,
+    erfcf,
+    cbrt,
+    cbrtf,
+    hypot,
+    hypotf,
+  )
+where
+
+foreign import ccall "nearbyint" c_nearbyint :: Double -> Double
+
+foreign import ccall "nearbyintf" c_nearbyintf :: Float -> Float
+
+foreign import ccall "ceil" c_ceil :: Double -> Double
+
+foreign import ccall "ceilf" c_ceilf :: Float -> Float
+
+foreign import ccall "floor" c_floor :: Double -> Double
+
+foreign import ccall "floorf" c_floorf :: Float -> Float
+
+-- | Round a single-precision floating point number correctly.
+roundFloat :: Float -> Float
+roundFloat = c_nearbyintf
+
+-- | Round a single-precision floating point number upwards correctly.
+ceilFloat :: Float -> Float
+ceilFloat = c_ceilf
+
+-- | Round a single-precision floating point number downwards correctly.
+floorFloat :: Float -> Float
+floorFloat = c_floorf
+
+-- | Round a double-precision floating point number correctly.
+roundDouble :: Double -> Double
+roundDouble = c_nearbyint
+
+-- | Round a double-precision floating point number upwards correctly.
+ceilDouble :: Double -> Double
+ceilDouble = c_ceil
+
+-- | Round a double-precision floating point number downwards correctly.
+floorDouble :: Double -> Double
+floorDouble = c_floor
+
+foreign import ccall "nextafter" c_nextafter :: Double -> Double -> Double
+
+foreign import ccall "nextafterf" c_nextafterf :: Float -> Float -> Float
+
+-- | The next representable single-precision floating-point value in
+-- the given direction.
+nextafterf :: Float -> Float -> Float
+nextafterf = c_nextafterf
+
+-- | The next representable double-precision floating-point value in
+-- the given direction.
+nextafter :: Double -> Double -> Double
+nextafter = c_nextafter
+
+foreign import ccall "lgamma" c_lgamma :: Double -> Double
+
+foreign import ccall "lgammaf" c_lgammaf :: Float -> Float
+
+foreign import ccall "tgamma" c_tgamma :: Double -> Double
+
+foreign import ccall "tgammaf" c_tgammaf :: Float -> Float
+
+-- | The system-level @lgamma()@ function.
+lgamma :: Double -> Double
+lgamma = c_lgamma
+
+-- | The system-level @lgammaf()@ function.
+lgammaf :: Float -> Float
+lgammaf = c_lgammaf
+
+-- | The system-level @tgamma()@ function.
+tgamma :: Double -> Double
+tgamma = c_tgamma
+
+-- | The system-level @tgammaf()@ function.
+tgammaf :: Float -> Float
+tgammaf = c_tgammaf
+
+foreign import ccall "hypot" c_hypot :: Double -> Double -> Double
+
+foreign import ccall "hypotf" c_hypotf :: Float -> Float -> Float
+
+-- | The system-level @hypot@ function.
+hypot :: Double -> Double -> Double
+hypot = c_hypot
+
+-- | The system-level @hypotf@ function.
+hypotf :: Float -> Float -> Float
+hypotf = c_hypotf
+
+foreign import ccall "erf" c_erf :: Double -> Double
+
+foreign import ccall "erff" c_erff :: Float -> Float
+
+foreign import ccall "erfc" c_erfc :: Double -> Double
+
+foreign import ccall "erfcf" c_erfcf :: Float -> Float
+
+-- | The system-level @erf()@ function.
+erf :: Double -> Double
+erf = c_erf
+
+-- | The system-level @erff()@ function.
+erff :: Float -> Float
+erff = c_erff
+
+-- | The system-level @erfc()@ function.
+erfc :: Double -> Double
+erfc = c_erfc
+
+-- | The system-level @erfcf()@ function.
+erfcf :: Float -> Float
+erfcf = c_erfcf
+
+foreign import ccall "cbrt" c_cbrt :: Double -> Double
+
+foreign import ccall "cbrtf" c_cbrtf :: Float -> Float
+
+-- | The system-level @cbrt@ function.
+cbrt :: Double -> Double
+cbrt = c_cbrt
+
+-- | The system-level @cbrtf@ function.
+cbrtf :: Float -> Float
+cbrtf = c_cbrtf
diff --git a/src/Futhark/Util/Console.hs b/src/Futhark/Util/Console.hs
deleted file mode 100644
--- a/src/Futhark/Util/Console.hs
+++ /dev/null
@@ -1,26 +0,0 @@
--- | Some utility functions for working with pretty console output.
-module Futhark.Util.Console
-  ( color,
-    inRed,
-    inYellow,
-    inBold,
-  )
-where
-
-import System.Console.ANSI
-
--- | Surround the given string with the given start/end colour codes.
-color :: [SGR] -> String -> String
-color sgr s = setSGRCode sgr ++ s ++ setSGRCode [Reset]
-
--- | Make the string red.
-inRed :: String -> String
-inRed s = setSGRCode [SetColor Foreground Vivid Red] ++ s ++ setSGRCode [Reset]
-
--- | Make the string yellow.
-inYellow :: String -> String
-inYellow s = setSGRCode [SetColor Foreground Vivid Yellow] ++ s ++ setSGRCode [Reset]
-
--- | Make the string bold.
-inBold :: String -> String
-inBold s = setSGRCode [SetConsoleIntensity BoldIntensity] ++ s ++ setSGRCode [Reset]
diff --git a/src/Futhark/Util/Log.hs b/src/Futhark/Util/Log.hs
--- a/src/Futhark/Util/Log.hs
+++ b/src/Futhark/Util/Log.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Opaque type for an operations log that provides fast O(1)
 -- appends.
 module Futhark.Util.Log
@@ -12,11 +8,11 @@
   )
 where
 
-import qualified Control.Monad.RWS.Lazy
-import qualified Control.Monad.RWS.Strict
+import Control.Monad.RWS.Lazy qualified
+import Control.Monad.RWS.Strict qualified
 import Control.Monad.Writer
-import qualified Data.DList as DL
-import qualified Data.Text as T
+import Data.DList qualified as DL
+import Data.Text qualified as T
 
 -- | An efficiently catenable sequence of log entries.
 newtype Log = Log {unLog :: DL.DList T.Text}
@@ -27,7 +23,7 @@
 instance Monoid Log where
   mempty = Log mempty
 
--- | Transform a log into text.  Every log entry becomes its own line
+-- | Transform a log into pretty.  Every log entry becomes its own line
 -- (or possibly more, in case of multi-line entries).
 toText :: Log -> T.Text
 toText = T.intercalate "\n" . DL.toList . unLog
diff --git a/src/Futhark/Util/Pretty.hs b/src/Futhark/Util/Pretty.hs
--- a/src/Futhark/Util/Pretty.hs
+++ b/src/Futhark/Util/Pretty.hs
@@ -1,16 +1,27 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
--- | A re-export of the prettyprinting library, along with some convenience functions.
+-- | A re-export of the prettyprinting library, along with some
+-- convenience functions.
 module Futhark.Util.Pretty
-  ( module Text.PrettyPrint.Mainland,
-    module Text.PrettyPrint.Mainland.Class,
-    pretty,
-    prettyDoc,
+  ( -- * Rendering to texts
     prettyTuple,
     prettyTupleLines,
+    prettyString,
     prettyText,
     prettyTextOneLine,
-    prettyOneLine,
+    docText,
+    docTextForHandle,
+
+    -- * Rendering to terminal
+    putDoc,
+    hPutDoc,
+    putDocLn,
+    hPutDocLn,
+
+    -- * Building blocks
+    module Prettyprinter,
+    module Prettyprinter.Symbols.Ascii,
+    module Prettyprinter.Render.Terminal,
     apply,
     oneLine,
     annot,
@@ -18,90 +29,164 @@
     textwrap,
     shorten,
     commastack,
+    commasep,
+    semistack,
+    semisep,
+    stack,
+    parensIf,
+    ppTuple',
+
+    -- * Operators
+    (</>),
   )
 where
 
 import Data.Text (Text)
-import qualified Data.Text.Lazy as LT
+import Data.Text qualified as T
 import Numeric.Half
-import Text.PrettyPrint.Mainland hiding (pretty)
-import qualified Text.PrettyPrint.Mainland as PP
-import Text.PrettyPrint.Mainland.Class
+import Prettyprinter
+import Prettyprinter.Render.Terminal (AnsiStyle, Color (..), bgColor, bgColorDull, bold, color, colorDull)
+import Prettyprinter.Render.Terminal qualified
+import Prettyprinter.Render.Text qualified
+import Prettyprinter.Symbols.Ascii
+import System.IO (Handle, hIsTerminalDevice, stdout)
 
--- | Prettyprint a value, wrapped to 80 characters.
-pretty :: Pretty a => a -> String
-pretty = PP.pretty 80 . ppr
+-- | Print a doc with styling to the given file; stripping colors if
+-- the file does not seem to support such things.
+hPutDoc :: Handle -> Doc AnsiStyle -> IO ()
+hPutDoc h d = do
+  colours <- hIsTerminalDevice h
+  if colours
+    then Prettyprinter.Render.Terminal.renderIO h (layouter d)
+    else Prettyprinter.Render.Text.hPutDoc h d
+  where
+    layouter =
+      layoutSmart defaultLayoutOptions {layoutPageWidth = Unbounded}
 
--- | Prettyprint a value to a 'Text', wrapped to 80 characters.
-prettyText :: Pretty a => a -> Text
-prettyText = LT.toStrict . PP.prettyLazyText 80 . ppr
+-- | Like 'hPutDoc', but with a final newline.
+hPutDocLn :: Handle -> Doc AnsiStyle -> IO ()
+hPutDocLn h d = do
+  hPutDoc h d
+  putStrLn ""
 
--- | Prettyprint a value to a 'Text' without any width restriction.
-prettyTextOneLine :: Pretty a => a -> Text
-prettyTextOneLine = LT.toStrict . PP.prettyLazyText 80 . oneLine . ppr
+-- | Like 'hPutDoc', but to stdout.
+putDoc :: Doc AnsiStyle -> IO ()
+putDoc = hPutDoc stdout
 
--- | Prettyprint a value without any width restriction.
-prettyOneLine :: Pretty a => a -> String
-prettyOneLine = ($ "") . displayS . renderCompact . oneLine . ppr
+-- | Like 'putDoc', but with a final newline.
+putDocLn :: Doc AnsiStyle -> IO ()
+putDocLn h = do
+  putDoc h
+  putStrLn ""
 
--- | Re-export of 'PP.pretty'.
-prettyDoc :: Int -> Doc -> String
-prettyDoc = PP.pretty
+-- | Produce text suitable for printing on the given handle.  This
+-- mostly means stripping any control characters if the handle is not
+-- a terminal.
+docTextForHandle :: Handle -> Doc AnsiStyle -> IO T.Text
+docTextForHandle h d = do
+  colours <- hIsTerminalDevice h
+  let sds = layoutSmart defaultLayoutOptions d
+  pure $
+    if colours
+      then Prettyprinter.Render.Terminal.renderStrict sds
+      else Prettyprinter.Render.Text.renderStrict sds
 
-ppTuple' :: Pretty a => [a] -> Doc
-ppTuple' ets = braces $ commasep $ map (align . ppr) ets
+-- | Prettyprint a value to a 'String', appropriately wrapped.
+prettyString :: Pretty a => a -> String
+prettyString = T.unpack . prettyText
 
+-- | Prettyprint a value to a 'Text', appropriately wrapped.
+prettyText :: Pretty a => a -> Text
+prettyText = docText . pretty
+
+-- | Convert a 'Doc' to text.  Thsi ignores any annotations (i.e. it
+-- will be non-coloured output).
+docText :: Doc a -> T.Text
+docText = Prettyprinter.Render.Text.renderStrict . layoutSmart defaultLayoutOptions
+
+-- | Prettyprint a value to a 'Text' on a single line.
+prettyTextOneLine :: Pretty a => a -> Text
+prettyTextOneLine = Prettyprinter.Render.Text.renderStrict . layoutSmart oneLineLayout . group . pretty
+  where
+    oneLineLayout = defaultLayoutOptions {layoutPageWidth = Unbounded}
+
+ppTuple' :: [Doc a] -> Doc a
+ppTuple' ets = braces $ commasep $ map align ets
+
 -- | Prettyprint a list enclosed in curly braces.
-prettyTuple :: Pretty a => [a] -> String
-prettyTuple = PP.pretty 80 . ppTuple'
+prettyTuple :: Pretty a => [a] -> Text
+prettyTuple = docText . ppTuple' . map pretty
 
 -- | Like 'prettyTuple', but put a linebreak after every element.
-prettyTupleLines :: Pretty a => [a] -> String
-prettyTupleLines = PP.pretty 80 . ppTupleLines'
+prettyTupleLines :: Pretty a => [a] -> Text
+prettyTupleLines = docText . ppTupleLines'
   where
-    ppTupleLines' ets = braces $ stack $ punctuate comma $ map (align . ppr) ets
+    ppTupleLines' = braces . hsep . punctuate comma . map (align . pretty)
 
 -- | The document @'apply' ds@ separates @ds@ with commas and encloses them with
 -- parentheses.
-apply :: [Doc] -> Doc
-apply = parens . commasep . map align
+apply :: [Doc a] -> Doc a
+apply = parens . align . commasep . map align
 
 -- | Make sure that the given document is printed on just a single line.
-oneLine :: PP.Doc -> PP.Doc
-oneLine s = PP.text $ PP.displayS (PP.renderCompact s) ""
+oneLine :: Doc a -> Doc a
+oneLine = group
 
--- | Like 'text', but splits the string into words and permits line breaks between all of them.
-textwrap :: String -> Doc
-textwrap = folddoc (<+/>) . map text . words
+-- | Splits the string into words and permits line breaks between all
+-- of them.
+textwrap :: T.Text -> Doc a
+textwrap = fillSep . map pretty . T.words
 
 -- | Stack and prepend a list of 'Doc's to another 'Doc', separated by
 -- a linebreak.  If the list is empty, the second 'Doc' will be
 -- returned without a preceding linebreak.
-annot :: [Doc] -> Doc -> Doc
+annot :: [Doc a] -> Doc a -> Doc a
 annot [] s = s
-annot l s = stack l </> s
+annot l s = vsep (l ++ [s])
 
 -- | Surround the given document with enclosers and add linebreaks and
 -- indents.
-nestedBlock :: String -> String -> Doc -> Doc
-nestedBlock pre post body =
-  text pre
-    </> PP.indent 2 body
-    </> text post
+nestedBlock :: Doc a -> Doc a -> Doc a -> Doc a
+nestedBlock pre post body = vsep [pre, indent 2 body, post]
 
 -- | Prettyprint on a single line up to at most some appropriate
 -- number of characters, with trailing ... if necessary.  Used for
 -- error messages.
-shorten :: Pretty a => a -> Doc
+shorten :: Doc a -> Doc b
 shorten a
-  | length s > 70 = text (take 70 s) <> text "..."
-  | otherwise = text s
+  | T.length s > 70 = pretty (T.take 70 s) <> "..."
+  | otherwise = pretty s
   where
-    s = pretty a
+    s = Prettyprinter.Render.Text.renderStrict $ layoutCompact a
 
 -- | Like 'commasep', but a newline after every comma.
-commastack :: [Doc] -> Doc
-commastack = align . stack . punctuate comma
+commastack :: [Doc a] -> Doc a
+commastack = align . vsep . punctuate comma
 
+-- | Like 'semisep', but a newline after every semicolon.
+semistack :: [Doc a] -> Doc a
+semistack = align . vsep . punctuate semi
+
+-- | Separate with commas.
+commasep :: [Doc a] -> Doc a
+commasep = hsep . punctuate comma
+
+-- | Separate with semicolons.
+semisep :: [Doc a] -> Doc a
+semisep = hsep . punctuate semi
+
+-- | Separate with linebreaks.
+stack :: [Doc a] -> Doc a
+stack = align . mconcat . punctuate line
+
+-- | The document @'parensIf' p d@ encloses the document @d@ in parenthesis if
+-- @p@ is @True@, and otherwise yields just @d@.
+parensIf :: Bool -> Doc a -> Doc a
+parensIf True doc = parens doc
+parensIf False doc = doc
+
 instance Pretty Half where
-  ppr = text . show
+  pretty = viaShow
+
+(</>) :: Doc a -> Doc a -> Doc a
+a </> b = a <> line <> b
diff --git a/src/Futhark/Util/ProgressBar.hs b/src/Futhark/Util/ProgressBar.hs
--- a/src/Futhark/Util/ProgressBar.hs
+++ b/src/Futhark/Util/ProgressBar.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE OverloadedStrings #-}
-
--- | Facilities for generating and otherwise handling text-based progress bars.
+-- | Facilities for generating and otherwise handling pretty-based progress bars.
 module Futhark.Util.ProgressBar
   ( progressBar,
     ProgressBar (..),
@@ -8,7 +6,7 @@
   )
 where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- | Information about a progress bar to render.  The "progress space"
 -- spans from 0 and up to the `progressBarBound`, but can be
diff --git a/src/Futhark/Util/Table.hs b/src/Futhark/Util/Table.hs
--- a/src/Futhark/Util/Table.hs
+++ b/src/Futhark/Util/Table.hs
@@ -1,56 +1,59 @@
 -- | Basic table building for prettier futhark-test output.
 module Futhark.Util.Table
-  ( buildTable,
+  ( hPutTable,
     mkEntry,
     Entry,
+    AnsiStyle,
+    Color (..),
+    color,
   )
 where
 
-import Data.List (intercalate, transpose)
+import Data.List (intersperse, transpose)
 import Futhark.Util (maxinum)
-import Futhark.Util.Console (color)
-import System.Console.ANSI
+import Futhark.Util.Pretty hiding (sep, width)
+import System.IO (Handle)
 
 data RowTemplate = RowTemplate [Int] Int deriving (Show)
 
--- | A table entry. Consists of the content as well a list of
--- SGR commands to color/stylelize the entry.
-type Entry = (String, [SGR])
+-- | A table entry. Consists of the content as well as how it should
+-- be styled..
+data Entry = Entry {entryText :: String, _entryStyle :: AnsiStyle}
 
--- | Makes a table entry with the default SGR mode.
-mkEntry :: String -> (String, [SGR])
-mkEntry s = (s, [])
+-- | Makes a table entry.
+mkEntry :: String -> AnsiStyle -> Entry
+mkEntry = Entry
 
 buildRowTemplate :: [[Entry]] -> Int -> RowTemplate
 buildRowTemplate rows = RowTemplate widths
   where
-    widths = map (maxinum . map (length . fst)) . transpose $ rows
+    widths = map (maxinum . map (length . entryText)) . transpose $ rows
 
-buildRow :: RowTemplate -> [Entry] -> String
-buildRow (RowTemplate widths pad) entries = cells ++ "\n"
+buildRow :: RowTemplate -> [Entry] -> Doc AnsiStyle
+buildRow (RowTemplate widths pad) entries = cells <> hardline
   where
     bar = "\x2502"
-    cells = concatMap buildCell (zip entries widths) ++ bar
-    buildCell ((entry, sgr), width) =
+    cells = mconcat (zipWith buildCell entries widths) <> bar
+    buildCell (Entry entry sgr) width =
       let padding = width - length entry + pad
-       in bar ++ " " ++ color sgr entry ++ replicate padding ' '
+       in bar <> " " <> annotate sgr (pretty entry) <> mconcat (replicate padding " ")
 
-buildSep :: Char -> Char -> Char -> RowTemplate -> String
+buildSep :: Char -> Char -> Char -> RowTemplate -> Doc AnsiStyle
 buildSep lCorner rCorner sep (RowTemplate widths pad) =
   corners . concatMap cellFloor $ widths
   where
-    cellFloor width = replicate (width + pad + 1) '\x2500' ++ [sep]
+    cellFloor width = replicate (width + pad + 1) '\x2500' <> [sep]
     corners [] = ""
-    corners s = [lCorner] ++ init s ++ [rCorner]
+    corners s = pretty lCorner <> pretty (init s) <> pretty rCorner
 
--- | Builds a table from a list of entries and a padding amount that
+-- | Produce a table from a list of entries and a padding amount that
 -- determines padding from the right side of the widest entry in each column.
-buildTable :: [[Entry]] -> Int -> String
-buildTable rows pad = buildTop template ++ sepRows ++ buildBottom template
+hPutTable :: Handle -> [[Entry]] -> Int -> IO ()
+hPutTable h rows pad = hPutDoc h $ buildTop template <> sepRows <> buildBottom template <> hardline
   where
-    sepRows = intercalate (buildFloor template) builtRows
+    sepRows = mconcat $ intersperse (buildFloor template) builtRows
     builtRows = map (buildRow template) rows
     template = buildRowTemplate rows pad
-    buildTop rt = buildSep '\x250C' '\x2510' '\x252C' rt ++ "\n"
-    buildFloor rt = buildSep '\x251C' '\x2524' '\x253C' rt ++ "\n"
+    buildTop rt = buildSep '\x250C' '\x2510' '\x252C' rt <> hardline
+    buildFloor rt = buildSep '\x251C' '\x2524' '\x253C' rt <> hardline
     buildBottom = buildSep '\x2514' '\x2518' '\x2534'
diff --git a/src/Futhark/Version.hs b/src/Futhark/Version.hs
--- a/src/Futhark/Version.hs
+++ b/src/Futhark/Version.hs
@@ -8,12 +8,12 @@
   )
 where
 
-import qualified Data.ByteString.Char8 as BS
+import Data.ByteString.Char8 qualified as BS
 import Data.FileEmbed
 import Data.Version
 import Futhark.Util (trim)
 import GitHash
-import qualified Paths_futhark
+import Paths_futhark qualified
 
 {-# NOINLINE version #-}
 
diff --git a/src/Language/Futhark/Core.hs b/src/Language/Futhark/Core.hs
--- a/src/Language/Futhark/Core.hs
+++ b/src/Language/Futhark/Core.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | This module contains very basic definitions for Futhark - so basic,
 -- that they can be shared between the internal and external
 -- representation.
@@ -15,6 +11,8 @@
     srclocOf,
     locStr,
     locStrRel,
+    locText,
+    locTextRel,
     prettyStacktrace,
 
     -- * Name handling
@@ -28,7 +26,6 @@
     baseName,
     baseString,
     quote,
-    pquote,
 
     -- * Number re-export
     Int8,
@@ -46,7 +43,7 @@
 import Control.Category
 import Data.Int (Int16, Int32, Int64, Int8)
 import Data.String
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Word (Word16, Word32, Word64, Word8)
 import Futhark.Util.Loc
 import Futhark.Util.Pretty
@@ -70,8 +67,8 @@
   mempty = Unique
 
 instance Pretty Uniqueness where
-  ppr Unique = star
-  ppr Nonunique = empty
+  pretty Unique = "*"
+  pretty Nonunique = mempty
 
 -- | The abstract (not really) type representing names in the Futhark
 -- compiler.  'String's, being lists of characters, are very slow,
@@ -80,7 +77,7 @@
   deriving (Show, Eq, Ord, IsString, Semigroup)
 
 instance Pretty Name where
-  ppr = text . nameToString
+  pretty = pretty . nameToString
 
 -- | Convert a name to the corresponding list of characters.
 nameToString :: Name -> String
@@ -136,24 +133,32 @@
         first_part = show line1 ++ ":" ++ show col1
     _ -> locStr b
 
+-- | 'locStr', but for text.
+locText :: Located a => a -> T.Text
+locText = T.pack . locStr
+
+-- | 'locStrRel', but for text.
+locTextRel :: (Located a, Located b) => a -> b -> T.Text
+locTextRel a b = T.pack $ locStrRel a b
+
 -- | Given a list of strings representing entries in the stack trace
 -- and the index of the frame to highlight, produce a final
 -- newline-terminated string for showing to the user.  This string
 -- should also be preceded by a newline.  The most recent stack frame
 -- must come first in the list.
-prettyStacktrace :: Int -> [String] -> String
-prettyStacktrace cur = unlines . zipWith f [(0 :: Int) ..]
+prettyStacktrace :: Int -> [T.Text] -> T.Text
+prettyStacktrace cur = T.unlines . zipWith f [(0 :: Int) ..]
   where
     -- Formatting hack: assume no stack is deeper than 100
     -- elements.  Since Futhark does not support recursion, going
     -- beyond that would require a truly perverse program.
     f i x =
       (if cur == i then "-> " else "   ")
-        ++ '#'
-        : show i
-        ++ (if i > 9 then "" else " ")
-        ++ " "
-        ++ x
+        <> "#"
+        <> T.pack (show i)
+        <> (if i > 9 then "" else " ")
+        <> " "
+        <> x
 
 -- | A name tagged with some integer.  Only the integer is used in
 -- comparisons, no matter the type of @vn@.
@@ -181,9 +186,5 @@
 -- | Enclose a string in the prefered quotes used in error messages.
 -- These are picked to not collide with characters permitted in
 -- identifiers.
-quote :: String -> String
-quote s = "\"" ++ s ++ "\""
-
--- | As 'quote', but works on prettyprinted representation.
-pquote :: Doc -> Doc
-pquote = dquotes
+quote :: T.Text -> T.Text
+quote s = "\"" <> s <> "\""
diff --git a/src/Language/Futhark/FreeVars.hs b/src/Language/Futhark/FreeVars.hs
--- a/src/Language/Futhark/FreeVars.hs
+++ b/src/Language/Futhark/FreeVars.hs
@@ -9,8 +9,8 @@
   )
 where
 
-import qualified Data.Map.Strict as M
-import qualified Data.Set as S
+import Data.Map.Strict qualified as M
+import Data.Set qualified as S
 import Language.Futhark.Prop
 import Language.Futhark.Syntax
 
diff --git a/src/Language/Futhark/Interpreter.hs b/src/Language/Futhark/Interpreter.hs
--- a/src/Language/Futhark/Interpreter.hs
+++ b/src/Language/Futhark/Interpreter.hs
@@ -1,13 +1,10 @@
-{-# LANGUAGE DeriveTraversable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | An interpreter operating on type-checked source Futhark terms.
 -- Relatively slow.
 module Language.Futhark.Interpreter
   ( Ctx (..),
     Env,
     InterpreterError,
+    prettyInterpreterError,
     initialCtx,
     interpretExp,
     interpretDec,
@@ -18,10 +15,14 @@
     BreakReason (..),
     StackFrame (..),
     typeCheckerEnv,
-    Value (ValuePrim, ValueRecord),
+
+    -- * Values
+    Value,
     fromTuple,
     isEmptyArray,
     prettyEmptyArray,
+    prettyValue,
+    valueText,
   )
 where
 
@@ -38,22 +39,25 @@
     foldl',
     genericLength,
     genericTake,
-    intercalate,
     isPrefixOf,
     transpose,
   )
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as M
 import Data.Maybe
 import Data.Monoid hiding (Sum)
+import Data.Text qualified as T
+import Futhark.Data qualified as V
 import Futhark.Util (chunk, maybeHead, splitFromEnd)
 import Futhark.Util.Loc
-import Futhark.Util.Pretty hiding (apply, bool)
-import Language.Futhark hiding (Shape, Value, matchDims)
-import qualified Language.Futhark as F
+import Futhark.Util.Pretty hiding (apply)
+import Language.Futhark hiding (Shape, matchDims)
+import Language.Futhark qualified as F
+import Language.Futhark.Interpreter.Values hiding (Value)
+import Language.Futhark.Interpreter.Values qualified
 import Language.Futhark.Primitive (floatValue, intValue)
-import qualified Language.Futhark.Primitive as P
-import qualified Language.Futhark.Semantic as T
+import Language.Futhark.Primitive qualified as P
+import Language.Futhark.Semantic qualified as T
 import Prelude hiding (break, mod)
 
 data StackFrame = StackFrame
@@ -131,66 +135,9 @@
 extSizeEnv :: EvalM Env
 extSizeEnv = i64Env <$> getSizes
 
-prettyRecord :: Pretty a => M.Map Name a -> Doc
-prettyRecord m
-  | Just vs <- areTupleFields m =
-      parens $ commasep $ map ppr vs
-  | otherwise =
-      braces $ commasep $ map field $ M.toList m
-  where
-    field (k, v) = ppr k <+> equals <+> ppr v
-
 valueStructType :: ValueType -> StructType
 valueStructType = first (ConstSize . fromIntegral)
 
--- | A shape is a tree to accomodate the case of records.  It is
--- parameterised over the representation of dimensions.
-data Shape d
-  = ShapeDim d (Shape d)
-  | ShapeLeaf
-  | ShapeRecord (M.Map Name (Shape d))
-  | ShapeSum (M.Map Name [Shape d])
-  deriving (Eq, Show, Functor, Foldable, Traversable)
-
--- | The shape of an array.
-type ValueShape = Shape Int64
-
-instance Pretty d => Pretty (Shape d) where
-  ppr ShapeLeaf = mempty
-  ppr (ShapeDim d s) = brackets (ppr d) <> ppr s
-  ppr (ShapeRecord m) = prettyRecord m
-  ppr (ShapeSum cs) =
-    mconcat (punctuate (text " | ") cs')
-    where
-      ppConstr (name, fs) = sep $ (text "#" <> ppr name) : map ppr fs
-      cs' = map ppConstr $ M.toList cs
-
-emptyShape :: ValueShape -> Bool
-emptyShape (ShapeDim d s) = d == 0 || emptyShape s
-emptyShape _ = False
-
-typeShape :: M.Map VName (Shape d) -> TypeBase d () -> Shape d
-typeShape shapes = go
-  where
-    go (Array _ _ shape et) =
-      foldr ShapeDim (go (Scalar et)) $ shapeDims shape
-    go (Scalar (Record fs)) =
-      ShapeRecord $ M.map go fs
-    go (Scalar (Sum cs)) =
-      ShapeSum $ M.map (map go) cs
-    go (Scalar (TypeVar _ _ (QualName [] v) []))
-      | Just shape <- M.lookup v shapes =
-          shape
-    go _ =
-      ShapeLeaf
-
-structTypeShape :: M.Map VName ValueShape -> StructType -> Shape (Maybe Int64)
-structTypeShape shapes = fmap dim . typeShape shapes'
-  where
-    dim (ConstSize d) = Just $ fromIntegral d
-    dim _ = Nothing
-    shapes' = M.map (fmap $ ConstSize . fromIntegral) shapes
-
 resolveTypeParams :: [VName] -> StructType -> StructType -> Env
 resolveTypeParams names = match
   where
@@ -242,55 +189,6 @@
       | d1 `elem` names = M.singleton d1 d2
     matchDims _ _ = mempty
 
--- | A fully evaluated Futhark value.
-data Value
-  = ValuePrim !PrimValue
-  | ValueArray ValueShape !(Array Int Value)
-  | -- Stores the full shape.
-    ValueRecord (M.Map Name Value)
-  | ValueFun (Value -> EvalM Value)
-  | -- Stores the full shape.
-    ValueSum ValueShape Name [Value]
-  | -- The update function and the array.
-    ValueAcc (Value -> Value -> EvalM Value) !(Array Int Value)
-
-instance Eq Value where
-  ValuePrim (SignedValue x) == ValuePrim (SignedValue y) =
-    P.doCmpEq (P.IntValue x) (P.IntValue y)
-  ValuePrim (UnsignedValue x) == ValuePrim (UnsignedValue y) =
-    P.doCmpEq (P.IntValue x) (P.IntValue y)
-  ValuePrim (FloatValue x) == ValuePrim (FloatValue y) =
-    P.doCmpEq (P.FloatValue x) (P.FloatValue y)
-  ValuePrim (BoolValue x) == ValuePrim (BoolValue y) =
-    P.doCmpEq (P.BoolValue x) (P.BoolValue y)
-  ValueArray _ x == ValueArray _ y = x == y
-  ValueRecord x == ValueRecord y = x == y
-  ValueSum _ n1 vs1 == ValueSum _ n2 vs2 = n1 == n2 && vs1 == vs2
-  ValueAcc _ x == ValueAcc _ y = x == y
-  _ == _ = False
-
-instance Pretty Value where
-  ppr = pprPrec 0
-  pprPrec _ (ValuePrim v) = ppr v
-  pprPrec _ (ValueArray _ a) =
-    let elements = elems a -- [Value]
-        (x : _) = elements
-        separator = case x of
-          ValueArray _ _ -> comma <> line
-          _ -> comma <> space
-     in brackets $ cat $ punctuate separator (map ppr elements)
-  pprPrec _ (ValueRecord m) = prettyRecord m
-  pprPrec _ ValueFun {} = text "#<fun>"
-  pprPrec _ ValueAcc {} = text "#<acc>"
-  pprPrec p (ValueSum _ n vs) =
-    parensIf (p > 0) $ text "#" <> sep (ppr n : map (pprPrec 1) vs)
-
-valueShape :: Value -> ValueShape
-valueShape (ValueArray shape _) = shape
-valueShape (ValueRecord fs) = ShapeRecord $ M.map valueShape fs
-valueShape (ValueSum shape _ _) = shape
-valueShape _ = ShapeLeaf
-
 checkShape :: Shape (Maybe Int64) -> ValueShape -> Maybe ValueShape
 checkShape (ShapeDim Nothing shape1) (ShapeDim d2 shape2) =
   ShapeDim d2 <$> checkShape shape1 shape2
@@ -312,61 +210,27 @@
 checkShape _ shape2 =
   Just shape2
 
--- | Does the value correspond to an empty array?
-isEmptyArray :: Value -> Bool
-isEmptyArray = emptyShape . valueShape
-
--- | String representation of an empty array with the provided element
--- type.  This is pretty ad-hoc - don't expect good results unless the
--- element type is a primitive.
-prettyEmptyArray :: TypeBase () () -> Value -> String
-prettyEmptyArray t v =
-  "empty(" ++ dims (valueShape v) ++ pretty t' ++ ")"
-  where
-    t' = stripArray (arrayRank t) t
-    dims (ShapeDim n rowshape) =
-      "[" ++ pretty n ++ "]" ++ dims rowshape
-    dims _ = ""
-
--- | Create an array value; failing if that would result in an
--- irregular array.
-mkArray :: TypeBase Int64 () -> [Value] -> Maybe Value
-mkArray t [] =
-  pure $ toArray (typeShape mempty t) []
-mkArray _ (v : vs) = do
-  let v_shape = valueShape v
-  guard $ all ((== v_shape) . valueShape) vs
-  pure $ toArray' v_shape $ v : vs
-
-arrayLength :: Integral int => Array Int Value -> int
-arrayLength = fromIntegral . (+ 1) . snd . bounds
-
-toTuple :: [Value] -> Value
-toTuple = ValueRecord . M.fromList . zip tupleFieldNames
-
-fromTuple :: Value -> Maybe [Value]
-fromTuple (ValueRecord m) = areTupleFields m
-fromTuple _ = Nothing
+type Value = Language.Futhark.Interpreter.Values.Value EvalM
 
 asInteger :: Value -> Integer
 asInteger (ValuePrim (SignedValue v)) = P.valueIntegral v
 asInteger (ValuePrim (UnsignedValue v)) =
   toInteger (P.valueIntegral (P.doZExt v Int64) :: Word64)
-asInteger v = error $ "Unexpectedly not an integer: " ++ pretty v
+asInteger v = error $ "Unexpectedly not an integer: " <> show v
 
 asInt :: Value -> Int
 asInt = fromIntegral . asInteger
 
 asSigned :: Value -> IntValue
 asSigned (ValuePrim (SignedValue v)) = v
-asSigned v = error $ "Unexpected not a signed integer: " ++ pretty v
+asSigned v = error $ "Unexpected not a signed integer: " <> show v
 
 asInt64 :: Value -> Int64
 asInt64 = fromIntegral . asInteger
 
 asBool :: Value -> Bool
 asBool (ValuePrim (BoolValue x)) = x
-asBool v = error $ "Unexpectedly not a boolean: " ++ pretty v
+asBool v = error $ "Unexpectedly not a boolean: " <> show v
 
 lookupInEnv ::
   (Env -> M.Map VName x) ->
@@ -417,8 +281,12 @@
 -- | An error occurred during interpretation due to an error in the
 -- user program.  Actual interpreter errors will be signaled with an
 -- IO exception ('error').
-newtype InterpreterError = InterpreterError String
+newtype InterpreterError = InterpreterError T.Text
 
+-- | Prettyprint the error for human consumption.
+prettyInterpreterError :: InterpreterError -> Doc AnsiStyle
+prettyInterpreterError (InterpreterError e) = pretty e
+
 valEnv :: M.Map VName (Maybe T.BoundV, Value) -> Env
 valEnv m =
   Env
@@ -454,16 +322,17 @@
       )
 
 instance Show InterpreterError where
-  show (InterpreterError s) = s
+  show (InterpreterError s) = T.unpack s
 
-bad :: SrcLoc -> Env -> String -> EvalM a
+bad :: SrcLoc -> Env -> T.Text -> EvalM a
 bad loc env s = stacking loc env $ do
-  ss <- map (locStr . srclocOf) <$> stacktrace
-  liftF $ ExtOpError $ InterpreterError $ "Error at\n" ++ prettyStacktrace 0 ss ++ s
+  ss <- map (locText . srclocOf) <$> stacktrace
+  liftF . ExtOpError . InterpreterError $
+    "Error at\n" <> prettyStacktrace 0 ss <> s
 
 trace :: String -> Value -> EvalM ()
 trace w v = do
-  liftF $ ExtOpTrace w (prettyOneLine v) ()
+  liftF $ ExtOpTrace w (T.unpack $ docText $ oneLine $ prettyValue v) ()
 
 typeCheckerEnv :: Env -> T.Env
 typeCheckerEnv env =
@@ -488,19 +357,11 @@
 
 fromArray :: Value -> (ValueShape, [Value])
 fromArray (ValueArray shape as) = (shape, elems as)
-fromArray v = error $ "Expected array value, but found: " ++ pretty v
-
-toArray :: ValueShape -> [Value] -> Value
-toArray shape vs = ValueArray shape (listArray (0, length vs - 1) vs)
-
-toArray' :: ValueShape -> [Value] -> Value
-toArray' rowshape vs = ValueArray shape (listArray (0, length vs - 1) vs)
-  where
-    shape = ShapeDim (genericLength vs) rowshape
+fromArray v = error $ "Expected array value, but found: " <> show v
 
 apply :: SrcLoc -> Env -> Value -> Value -> EvalM Value
 apply loc env (ValueFun f) v = stacking loc env (f v)
-apply _ _ f _ = error $ "Cannot apply non-function: " ++ pretty f
+apply _ _ f _ = error $ "Cannot apply non-function: " <> show f
 
 apply2 :: SrcLoc -> Env -> Value -> Value -> Value -> EvalM Value
 apply2 loc env f x y = stacking loc env $ do
@@ -511,7 +372,7 @@
 matchPat env p v = do
   m <- runMaybeT $ patternMatch env p v
   case m of
-    Nothing -> error $ "matchPat: missing case for " ++ pretty p ++ " and " ++ pretty v
+    Nothing -> error $ "matchPat: missing case for " <> prettyString p ++ " and " <> show v
     Just env' -> pure env'
 
 patternMatch :: Env -> Pat -> Value -> MaybeT EvalM Env
@@ -548,20 +409,20 @@
   | IndexingSlice (Maybe Int64) (Maybe Int64) (Maybe Int64)
 
 instance Pretty Indexing where
-  ppr (IndexingFix i) = ppr i
-  ppr (IndexingSlice i j (Just s)) =
-    maybe mempty ppr i
-      <> text ":"
-      <> maybe mempty ppr j
-      <> text ":"
-      <> ppr s
-  ppr (IndexingSlice i (Just j) s) =
-    maybe mempty ppr i
-      <> text ":"
-      <> ppr j
-      <> maybe mempty ((text ":" <>) . ppr) s
-  ppr (IndexingSlice i Nothing Nothing) =
-    maybe mempty ppr i <> text ":"
+  pretty (IndexingFix i) = pretty i
+  pretty (IndexingSlice i j (Just s)) =
+    maybe mempty pretty i
+      <> ":"
+      <> maybe mempty pretty j
+      <> ":"
+      <> pretty s
+  pretty (IndexingSlice i (Just j) s) =
+    maybe mempty pretty i
+      <> ":"
+      <> pretty j
+      <> maybe mempty ((":" <>) . pretty) s
+  pretty (IndexingSlice i Nothing Nothing) =
+    maybe mempty pretty i <> ":"
 
 indexesFor ::
   Maybe Int64 ->
@@ -676,9 +537,9 @@
   let oob =
         bad loc env $
           "Index ["
-            <> intercalate ", " (map pretty is)
+            <> T.intercalate ", " (map prettyText is)
             <> "] out of bounds for array of shape "
-            <> pretty (valueShape arr)
+            <> prettyText (valueShape arr)
             <> "."
   maybe oob pure $ indexArray is arr
 
@@ -728,14 +589,14 @@
       size_env <- extSizeEnv
       v $ evalType (size_env <> env) t
     Just (TermValue _ v) -> pure v
-    _ -> error $ "\"" <> pretty qv <> "\" is not bound to a value."
+    _ -> error $ "\"" <> prettyString qv <> "\" is not bound to a value."
 
 typeValueShape :: Env -> StructType -> EvalM ValueShape
 typeValueShape env t = do
   size_env <- extSizeEnv
   let t' = evalType (size_env <> env) t
   case traverse dim $ typeShape mempty t' of
-    Nothing -> error $ "typeValueShape: failed to fully evaluate type " ++ pretty t'
+    Nothing -> error $ "typeValueShape: failed to fully evaluate type " <> prettyString t'
     Just shape -> pure shape
   where
     dim (ConstSize x) = Just $ fromIntegral x
@@ -860,30 +721,32 @@
 
     badRange start' maybe_second' end' =
       "Range "
-        ++ pretty start'
-        ++ ( case maybe_second' of
+        <> prettyText start'
+        <> ( case maybe_second' of
                Nothing -> ""
-               Just second' -> ".." ++ pretty second'
+               Just second' -> ".." <> prettyText second'
            )
-        ++ ( case end' of
-               DownToExclusive x -> "..>" ++ pretty x
-               ToInclusive x -> "..." ++ pretty x
-               UpToExclusive x -> "..<" ++ pretty x
+        <> ( case end' of
+               DownToExclusive x -> "..>" <> prettyText x
+               ToInclusive x -> "..." <> prettyText x
+               UpToExclusive x -> "..<" <> prettyText x
            )
-        ++ " is invalid."
+        <> " is invalid."
 evalAppExp env t (Coerce e te loc) = do
   v <- eval env e
   case checkShape (structTypeShape (envShapes env) t) (valueShape v) of
     Just _ -> pure v
     Nothing ->
-      bad loc env $
-        "Value `" <> pretty v <> "` of shape `"
-          ++ pretty (valueShape v)
-          ++ "` cannot match shape of type `"
-            <> pretty te
-            <> "` (`"
-            <> pretty t
-            <> "`)"
+      bad loc env . docText $
+        "Value `"
+          <> prettyValue v
+          <> "` of shape `"
+          <> pretty (valueShape v)
+          <> "` cannot match shape of type `"
+          <> pretty te
+          <> "` (`"
+          <> pretty t
+          <> "`)"
 evalAppExp env _ (LetPat sizes p e body _) = do
   v <- eval env e
   env' <- matchPat env p v
@@ -994,7 +857,7 @@
   match v (NE.toList cs)
   where
     match _ [] =
-      error "Pat match failure."
+      error "Pattern match failure."
     match v (c : cs') = do
       c' <- evalCase v env c
       case c' of
@@ -1003,7 +866,8 @@
 
 eval :: Env -> Exp -> EvalM Value
 eval _ (Literal v _) = pure $ ValuePrim v
-eval env (Hole (Info t) loc) = bad loc env $ "Hole of type: " <> prettyOneLine t
+eval env (Hole (Info t) loc) =
+  bad loc env $ "Hole of type: " <> prettyTextOneLine t
 eval env (Parens e _) = eval env e
 eval env (QualParens (qv, _) e loc) = do
   m <- evalModuleVar env qv
@@ -1045,12 +909,12 @@
       pure $ ValuePrim $ UnsignedValue $ intValue it v
     Scalar (Prim (FloatType ft)) ->
       pure $ ValuePrim $ FloatValue $ floatValue ft v
-    _ -> error $ "eval: nonsensical type for integer literal: " ++ pretty t
+    _ -> error $ "eval: nonsensical type for integer literal: " <> prettyString t
 eval _ (FloatLit v (Info t) _) =
   case t of
     Scalar (Prim (FloatType ft)) ->
       pure $ ValuePrim $ FloatValue $ floatValue ft v
-    _ -> error $ "eval: nonsensical type for float literal: " ++ pretty t
+    _ -> error $ "eval: nonsensical type for float literal: " <> prettyString t
 eval env (Negate e _) = do
   ev <- eval env e
   ValuePrim <$> case ev of
@@ -1065,14 +929,14 @@
     ValuePrim (FloatValue (Float16Value v)) -> pure $ FloatValue $ Float16Value (-v)
     ValuePrim (FloatValue (Float32Value v)) -> pure $ FloatValue $ Float32Value (-v)
     ValuePrim (FloatValue (Float64Value v)) -> pure $ FloatValue $ Float64Value (-v)
-    _ -> error $ "Cannot negate " ++ pretty ev
+    _ -> error $ "Cannot negate " <> show ev
 eval env (Not e _) = do
   ev <- eval env e
   ValuePrim <$> case ev of
     ValuePrim (BoolValue b) -> pure $ BoolValue $ not b
     ValuePrim (SignedValue iv) -> pure $ SignedValue $ P.doComplement iv
     ValuePrim (UnsignedValue iv) -> pure $ UnsignedValue $ P.doComplement iv
-    _ -> error $ "Cannot logically negate " ++ pretty ev
+    _ -> error $ "Cannot logically negate " <> show ev
 eval env (Update src is v loc) =
   maybe oob pure
     =<< writeArray <$> mapM (evalDimIndex env) is <*> eval env src <*> eval env v
@@ -1182,7 +1046,7 @@
 evalModuleVar env qv =
   case lookupVar qv env of
     Just (TermModule m) -> pure m
-    _ -> error $ quote (pretty qv) <> " is not bound to a module."
+    _ -> error $ prettyString qv <> " is not bound to a module."
 
 evalModExp :: Env -> ModExp -> EvalM Module
 evalModExp _ (ModImport _ (Info f) _) = do
@@ -1377,48 +1241,48 @@
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y] -> f x y
-            _ -> error $ "Expected pair; got: " ++ pretty v
+            _ -> error $ "Expected pair; got: " <> show v
     fun3t f =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z] -> f x y z
-            _ -> error $ "Expected triple; got: " ++ pretty v
+            _ -> error $ "Expected triple; got: " <> show v
 
     fun5t f =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z, a, b] -> f x y z a b
-            _ -> error $ "Expected pentuple; got: " ++ pretty v
+            _ -> error $ "Expected pentuple; got: " <> show v
 
     fun6t f =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z, a, b, c] -> f x y z a b c
-            _ -> error $ "Expected sextuple; got: " ++ pretty v
+            _ -> error $ "Expected sextuple; got: " <> show v
 
     fun7t f =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z, a, b, c, d] -> f x y z a b c d
-            _ -> error $ "Expected septuple; got: " ++ pretty v
+            _ -> error $ "Expected septuple; got: " <> show v
 
     fun8t f =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z, a, b, c, d, e] -> f x y z a b c d e
-            _ -> error $ "Expected sextuple; got: " ++ pretty v
+            _ -> error $ "Expected sextuple; got: " <> show v
 
     fun10t fun =
       TermValue Nothing $
         ValueFun $ \v ->
           case fromTuple v of
             Just [x, y, z, a, b, c, d, e, f, g] -> fun x y z a b c d e f g
-            _ -> error $ "Expected octuple; got: " ++ pretty v
+            _ -> error $ "Expected octuple; got: " <> show v
 
     bopDef fs = fun2 $ \x y ->
       case (x, y) of
@@ -1427,12 +1291,12 @@
               breakOnNaN [x', y'] z
               pure $ ValuePrim z
         _ ->
-          bad noLoc mempty $
-            "Cannot apply operator to arguments "
-              <> quote (pretty x)
-              <> " and "
-              <> quote (pretty y)
-              <> "."
+          bad noLoc mempty . docText $
+            "Cannot apply operator to arguments"
+              <+> dquotes (prettyValue x)
+              <+> "and"
+              <+> dquotes (prettyValue y)
+                <> "."
       where
         bopDef' (valf, retf, op) (x, y) = do
           x' <- valf x
@@ -1446,10 +1310,10 @@
               breakOnNaN [x'] r
               pure $ ValuePrim r
         _ ->
-          bad noLoc mempty $
-            "Cannot apply function to argument "
-              <> quote (pretty x)
-              <> "."
+          bad noLoc mempty . docText $
+            "Cannot apply function to argument"
+              <+> dquotes (prettyValue x)
+                <> "."
       where
         unopDef' (valf, retf, op) x = do
           x' <- valf x
@@ -1464,10 +1328,9 @@
               breakOnNaN [x, y] z
               pure $ ValuePrim z
         _ ->
-          bad noLoc mempty $
-            "Cannot apply operator to argument "
-              <> quote (pretty v)
-              <> "."
+          bad noLoc mempty . docText $
+            "Cannot apply operator to argument"
+              <+> dquotes (prettyValue v) <> "."
 
     def "!" =
       Just $
@@ -1559,13 +1422,13 @@
               ++ floatCmp P.FCmpLe
               ++ boolCmp P.CmpLle
     def s
-      | Just bop <- find ((s ==) . pretty) P.allBinOps =
+      | Just bop <- find ((s ==) . prettyString) P.allBinOps =
           Just $ tbopDef $ P.doBinOp bop
-      | Just unop <- find ((s ==) . pretty) P.allCmpOps =
+      | Just unop <- find ((s ==) . prettyString) P.allCmpOps =
           Just $ tbopDef $ \x y -> P.BoolValue <$> P.doCmpOp unop x y
-      | Just cop <- find ((s ==) . pretty) P.allConvOps =
+      | Just cop <- find ((s ==) . prettyString) P.allConvOps =
           Just $ unopDef [(getV, Just . putV, P.doConvOp cop)]
-      | Just unop <- find ((s ==) . pretty) P.allUnOps =
+      | Just unop <- find ((s ==) . prettyString) P.allUnOps =
           Just $ unopDef [(getV, Just . putV, P.doUnOp unop)]
       | Just (pts, _, f) <- M.lookup s P.primFuns =
           case length pts of
@@ -1580,21 +1443,21 @@
                         breakOnNaN vs res
                         pure $ ValuePrim res
                   _ ->
-                    error $ "Cannot apply " ++ pretty s ++ " to " ++ pretty x
+                    error $ "Cannot apply " <> prettyString s ++ " to " <> show x
       | "sign_" `isPrefixOf` s =
           Just $
             fun1 $ \x ->
               case x of
                 (ValuePrim (UnsignedValue x')) ->
                   pure $ ValuePrim $ SignedValue x'
-                _ -> error $ "Cannot sign: " ++ pretty x
+                _ -> error $ "Cannot sign: " <> show x
       | "unsign_" `isPrefixOf` s =
           Just $
             fun1 $ \x ->
               case x of
                 (ValuePrim (SignedValue x')) ->
                   pure $ ValuePrim $ UnsignedValue x'
-                _ -> error $ "Cannot unsign: " ++ pretty x
+                _ -> error $ "Cannot unsign: " <> show x
     def s
       | "map_stream" `isPrefixOf` s =
           Just $ fun2t stream
@@ -1608,11 +1471,11 @@
               | Just rowshape <- typeRowShape ret_t ->
                   toArray' rowshape <$> mapM (apply noLoc mempty f) (snd $ fromArray xs)
               | otherwise ->
-                  error $ "Bad pure type: " ++ pretty ret_t
+                  error $ "Bad pure type: " <> prettyString ret_t
             _ ->
               error $
                 "Invalid arguments to map intrinsic:\n"
-                  ++ unlines [pretty t, pretty v]
+                  ++ unlines [prettyString t, show v]
       where
         typeRowShape = sequenceA . structTypeShape mempty . stripArray 1
     def s | "reduce" `isPrefixOf` s = Just $
@@ -1634,7 +1497,7 @@
                 foldl' update arr' $
                   zip (map asInt $ snd $ fromArray is) (snd $ fromArray vs)
           _ ->
-            error $ "scatter expects array, but got: " ++ pretty arr
+            error $ "scatter expects array, but got: " <> show arr
       where
         update arr' (i, v) =
           if i >= 0 && i < arrayLength arr'
@@ -1648,7 +1511,7 @@
               foldl' update arr $
                 zip (map fromTuple $ snd $ fromArray is) (snd $ fromArray vs)
           _ ->
-            error $ "scatter_2d expects array, but got: " ++ pretty arr
+            error $ "scatter_2d expects array, but got: " <> show arr
       where
         update :: Value -> (Maybe [Value], Value) -> Value
         update arr (Just idxs@[_, _], v) =
@@ -1663,7 +1526,7 @@
               foldl' update arr $
                 zip (map fromTuple $ snd $ fromArray is) (snd $ fromArray vs)
           _ ->
-            error $ "scatter_3d expects array, but got: " ++ pretty arr
+            error $ "scatter_3d expects array, but got: " <> show arr
       where
         update :: Value -> (Maybe [Value], Value) -> Value
         update arr (Just idxs@[_, _, _], v) =
@@ -1735,9 +1598,9 @@
                 ValueAcc _ dest_arr' ->
                   pure $ ValueArray dest_shape dest_arr'
                 _ ->
-                  error $ "scatter_stream produced: " ++ pretty acc'
+                  error $ "scatter_stream produced: " <> show acc'
           _ ->
-            error $ "scatter_stream expects array, but got: " ++ pretty (dest, vs)
+            error $ "scatter_stream expects array, but got: " <> prettyString (show vs, show vs)
     def "hist_stream" = Just $
       fun5t $ \dest op _ne f vs ->
         case (dest, vs) of
@@ -1750,9 +1613,9 @@
                 ValueAcc _ dest_arr' ->
                   pure $ ValueArray dest_shape dest_arr'
                 _ ->
-                  error $ "hist_stream produced: " ++ pretty acc'
+                  error $ "hist_stream produced: " <> show acc'
           _ ->
-            error $ "hist_stream expects array, but got: " ++ pretty (dest, vs)
+            error $ "hist_stream expects array, but got: " <> prettyString (show dest, show vs)
     def "acc_write" = Just $
       fun3t $ \acc i v ->
         case (acc, i) of
@@ -1766,7 +1629,7 @@
                   pure $ ValueAcc op $ acc_arr // [(fromIntegral i', res)]
                 else pure acc
           _ ->
-            error $ "acc_write invalid arguments: " ++ pretty (acc, i, v)
+            error $ "acc_write invalid arguments: " <> prettyString (show acc, show i, show v)
     --
     def "flat_index_2d" = Just . fun6t $ \arr offset n1 s1 n2 s2 -> do
       let offset' = asInt64 offset
@@ -1785,7 +1648,7 @@
         Just arr' -> pure arr'
         Nothing ->
           bad mempty mempty $
-            "Index out of bounds: " ++ pretty [(n1', s1', n2', s2')]
+            "Index out of bounds: " <> prettyText [((n1', s1'), (n2', s2'))]
     --
     def "flat_update_2d" = Just . fun5t $ \arr offset s1 s2 v -> do
       let offset' = asInt64 offset
@@ -1801,7 +1664,7 @@
             Just arr' -> pure arr'
             Nothing ->
               bad mempty mempty $
-                "Index out of bounds: " ++ pretty [(n1, s1', n2, s2')]
+                "Index out of bounds: " <> prettyText [((n1, s1'), (n2, s2'))]
         s -> error $ "flat_update_2d: invalid arg shape: " ++ show s
     --
     def "flat_index_3d" = Just . fun8t $ \arr offset n1 s1 n2 s2 n3 s3 -> do
@@ -1824,7 +1687,7 @@
         Just arr' -> pure arr'
         Nothing ->
           bad mempty mempty $
-            "Index out of bounds: " ++ pretty [(n1', s1', n2', s2', n3', s3')]
+            "Index out of bounds: " <> prettyText [((n1', s1'), (n2', s2'), (n3', s3'))]
     --
     def "flat_update_3d" = Just . fun6t $ \arr offset s1 s2 s3 v -> do
       let offset' = asInt64 offset
@@ -1841,7 +1704,7 @@
             Just arr' -> pure arr'
             Nothing ->
               bad mempty mempty $
-                "Index out of bounds: " ++ pretty [(n1, s1', n2, s2', n3, s3')]
+                "Index out of bounds: " <> prettyText [((n1, s1'), (n2, s2'), (n3, s3'))]
         s -> error $ "flat_update_3d: invalid arg shape: " ++ show s
     --
     def "flat_index_4d" = Just . fun10t $ \arr offset n1 s1 n2 s2 n3 s3 n4 s4 -> do
@@ -1867,7 +1730,7 @@
         Just arr' -> pure arr'
         Nothing ->
           bad mempty mempty $
-            "Index out of bounds: " ++ pretty [(n1', s1', n2', s2', n3', s3', n4', s4')]
+            "Index out of bounds: " <> prettyText [(((n1', s1'), (n2', s2')), ((n3', s3'), (n4', s4')))]
     --
     def "flat_update_4d" = Just . fun7t $ \arr offset s1 s2 s3 s4 v -> do
       let offset' = asInt64 offset
@@ -1885,7 +1748,7 @@
             Just arr' -> pure arr'
             Nothing ->
               bad mempty mempty $
-                "Index out of bounds: " ++ pretty [(n1, s1', n2, s2', n3, s3', n4, s4')]
+                "Index out of bounds: " <> prettyText [(((n1, s1'), (n2, s2')), ((n3, s3'), (n4, s4')))]
         s -> error $ "flat_update_4d: invalid arg shape: " ++ show s
     --
     def "unzip" = Just $
@@ -1898,7 +1761,7 @@
         pure $ toTuple $ listPair $ unzip $ map (fromPair . fromTuple) $ snd $ fromArray x
       where
         fromPair (Just [x, y]) = (x, y)
-        fromPair l = error $ "Not a pair: " ++ pretty l
+        fromPair _ = error "Not a pair"
     def "zip" = Just $
       fun2t $ \xs ys -> do
         let ShapeDim _ xs_rowshape = valueShape xs
@@ -1946,11 +1809,11 @@
           then
             bad mempty mempty $
               "Cannot unflatten array of shape ["
-                <> pretty xs_size
+                <> prettyText xs_size
                 <> "] to array of shape ["
-                <> pretty (asInt64 n)
+                <> prettyText (asInt64 n)
                 <> "]["
-                <> pretty (asInt64 m)
+                <> prettyText (asInt64 m)
                 <> "]"
           else pure $ toArray shape $ map (toArray rowshape) $ chunk (asInt m) xs'
     def "vjp2" = Just $
@@ -1970,7 +1833,7 @@
     stream f arg@(ValueArray _ xs) =
       let n = ValuePrim $ SignedValue $ Int64Value $ arrayLength xs
        in apply2 noLoc mempty f n arg
-    stream _ arg = error $ "Cannot stream: " ++ pretty arg
+    stream _ arg = error $ "Cannot stream: " <> show arg
 
 interpretExp :: Ctx -> Exp -> F ExtOp Value
 interpretExp ctx e = runEvalM (ctxImports ctx) $ eval (ctxEnv ctx) e
@@ -1996,29 +1859,46 @@
 ctxWithImports :: [Env] -> Ctx -> Ctx
 ctxWithImports envs ctx = ctx {ctxEnv = mconcat (reverse envs) <> ctxEnv ctx}
 
-checkEntryArgs :: VName -> [F.Value] -> StructType -> Either String ()
+valueType :: V.Value -> ValueType
+valueType v =
+  let V.ValueType shape pt = V.valueType v
+   in arrayOf mempty (F.Shape (map fromIntegral shape)) (Scalar (Prim (toPrim pt)))
+  where
+    toPrim V.I8 = Signed Int8
+    toPrim V.I16 = Signed Int16
+    toPrim V.I32 = Signed Int32
+    toPrim V.I64 = Signed Int64
+    toPrim V.U8 = Unsigned Int8
+    toPrim V.U16 = Unsigned Int16
+    toPrim V.U32 = Unsigned Int32
+    toPrim V.U64 = Unsigned Int64
+    toPrim V.Bool = Bool
+    toPrim V.F16 = FloatType Float16
+    toPrim V.F32 = FloatType Float32
+    toPrim V.F64 = FloatType Float64
+
+checkEntryArgs :: VName -> [V.Value] -> StructType -> Either T.Text ()
 checkEntryArgs entry args entry_t
   | args_ts == param_ts =
       pure ()
   | otherwise =
-      Left $
-        pretty $
-          expected
-            </> "Got input of types"
-            </> indent 2 (stack (map ppr args_ts))
+      Left . docText $
+        expected
+          </> "Got input of types"
+          </> indent 2 (stack (map pretty args_ts))
   where
     (param_ts, _) = unfoldFunType entry_t
     args_ts = map (valueStructType . valueType) args
     expected
       | null param_ts =
-          "Entry point " <> pquote (pprName entry) <> " is not a function."
+          "Entry point " <> dquotes (prettyName entry) <> " is not a function."
       | otherwise =
-          "Entry point " <> pquote (pprName entry) <> " expects input of type(s)"
-            </> indent 2 (stack (map ppr param_ts))
+          "Entry point " <> dquotes (prettyName entry) <> " expects input of type(s)"
+            </> indent 2 (stack (map pretty param_ts))
 
 -- | Execute the named function on the given arguments; may fail
 -- horribly if these are ill-typed.
-interpretFunction :: Ctx -> VName -> [F.Value] -> Either String (F ExtOp Value)
+interpretFunction :: Ctx -> VName -> [V.Value] -> Either T.Text (F ExtOp Value)
 interpretFunction ctx fname vs = do
   ft <- case lookupVar (qualName fname) $ ctxEnv ctx of
     Just (TermValue (Just (T.BoundV _ t)) _) ->
@@ -2026,11 +1906,9 @@
     Just (TermPoly (Just (T.BoundV _ t)) _) ->
       updateType (map valueType vs) t
     _ ->
-      Left $ "Unknown function `" <> prettyName fname <> "`."
+      Left $ "Unknown function `" <> nameToText (toName fname) <> "`."
 
-  vs' <- case mapM convertValue vs of
-    Just vs' -> Right vs'
-    Nothing -> Left "Invalid input: irregular array."
+  let vs' = map fromDataValue vs
 
   checkEntryArgs fname vs ft
 
@@ -2046,7 +1924,7 @@
       Right t
 
     -- FIXME: we don't check array sizes.
-    checkInput :: ValueType -> StructType -> Either String ()
+    checkInput :: ValueType -> StructType -> Either T.Text ()
     checkInput (Scalar (Prim vt)) (Scalar (Prim pt))
       | vt /= pt = badPrim vt pt
     checkInput (Array _ _ _ (Prim vt)) (Array _ _ _ (Prim pt))
@@ -2055,12 +1933,9 @@
       Right ()
 
     badPrim vt pt =
-      Left . pretty $
+      Left . docText $
         "Invalid argument type."
           </> "Expected:"
-          <+> align (ppr pt)
+          <+> align (pretty pt)
           </> "Got:     "
-          <+> align (ppr vt)
-
-    convertValue (F.PrimValue p) = Just $ ValuePrim p
-    convertValue (F.ArrayValue arr t) = mkArray t =<< mapM convertValue (elems arr)
+          <+> align (pretty vt)
diff --git a/src/Language/Futhark/Interpreter/Values.hs b/src/Language/Futhark/Interpreter/Values.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Futhark/Interpreter/Values.hs
@@ -0,0 +1,266 @@
+-- | The value representation used in the interpreter.
+--
+-- Kept simple and free of unnecessary operational details (in
+-- particular, no references to the interpreter monad).
+module Language.Futhark.Interpreter.Values
+  ( -- * Shapes
+    Shape (..),
+    ValueShape,
+    typeShape,
+    structTypeShape,
+
+    -- * Values
+    Value (..),
+    valueShape,
+    prettyValue,
+    valueText,
+    fromTuple,
+    arrayLength,
+    isEmptyArray,
+    prettyEmptyArray,
+    toArray,
+    toArray',
+    toTuple,
+
+    -- * Conversion
+    fromDataValue,
+  )
+where
+
+import Data.Array
+import Data.List (genericLength)
+import Data.Map qualified as M
+import Data.Maybe
+import Data.Monoid hiding (Sum)
+import Data.Text qualified as T
+import Data.Vector.Storable qualified as SVec
+import Futhark.Data qualified as V
+import Futhark.Util (chunk)
+import Futhark.Util.Pretty
+import Language.Futhark hiding (Shape, matchDims)
+import Language.Futhark.Primitive qualified as P
+import Prelude hiding (break, mod)
+
+prettyRecord :: (a -> Doc ann) -> M.Map Name a -> Doc ann
+prettyRecord p m
+  | Just vs <- areTupleFields m =
+      parens $ align $ vsep $ punctuate comma $ map p vs
+  | otherwise =
+      braces $ align $ vsep $ punctuate comma $ map field $ M.toList m
+  where
+    field (k, v) = pretty k <+> equals <+> p v
+
+-- | A shape is a tree to accomodate the case of records.  It is
+-- parameterised over the representation of dimensions.
+data Shape d
+  = ShapeDim d (Shape d)
+  | ShapeLeaf
+  | ShapeRecord (M.Map Name (Shape d))
+  | ShapeSum (M.Map Name [Shape d])
+  deriving (Eq, Show, Functor, Foldable, Traversable)
+
+-- | The shape of an array.
+type ValueShape = Shape Int64
+
+instance Pretty d => Pretty (Shape d) where
+  pretty ShapeLeaf = mempty
+  pretty (ShapeDim d s) = brackets (pretty d) <> pretty s
+  pretty (ShapeRecord m) = prettyRecord pretty m
+  pretty (ShapeSum cs) =
+    mconcat (punctuate " | " cs')
+    where
+      ppConstr (name, fs) = sep $ ("#" <> pretty name) : map pretty fs
+      cs' = map ppConstr $ M.toList cs
+
+emptyShape :: ValueShape -> Bool
+emptyShape (ShapeDim d s) = d == 0 || emptyShape s
+emptyShape _ = False
+
+typeShape :: M.Map VName (Shape d) -> TypeBase d () -> Shape d
+typeShape shapes = go
+  where
+    go (Array _ _ shape et) =
+      foldr ShapeDim (go (Scalar et)) $ shapeDims shape
+    go (Scalar (Record fs)) =
+      ShapeRecord $ M.map go fs
+    go (Scalar (Sum cs)) =
+      ShapeSum $ M.map (map go) cs
+    go (Scalar (TypeVar _ _ (QualName [] v) []))
+      | Just shape <- M.lookup v shapes =
+          shape
+    go _ =
+      ShapeLeaf
+
+structTypeShape :: M.Map VName ValueShape -> StructType -> Shape (Maybe Int64)
+structTypeShape shapes = fmap dim . typeShape shapes'
+  where
+    dim (ConstSize d) = Just $ fromIntegral d
+    dim _ = Nothing
+    shapes' = M.map (fmap $ ConstSize . fromIntegral) shapes
+
+-- | A fully evaluated Futhark value.
+data Value m
+  = ValuePrim !PrimValue
+  | ValueArray ValueShape !(Array Int (Value m))
+  | -- Stores the full shape.
+    ValueRecord (M.Map Name (Value m))
+  | ValueFun (Value m -> m (Value m))
+  | -- Stores the full shape.
+    ValueSum ValueShape Name [Value m]
+  | -- The update function and the array.
+    ValueAcc (Value m -> Value m -> m (Value m)) !(Array Int (Value m))
+
+instance Show (Value m) where
+  show (ValuePrim v) = "ValuePrim " <> show v <> ""
+  show (ValueArray shape vs) = unwords ["ValueArray", show shape, show vs]
+  show (ValueRecord fs) = "ValueRecord " <> show fs
+  show (ValueSum shape c vs) = unwords ["ValueSum", show shape, show c, show vs]
+  show ValueFun {} = "ValueFun _"
+  show ValueAcc {} = "ValueAcc _"
+
+instance Eq (Value m) where
+  ValuePrim (SignedValue x) == ValuePrim (SignedValue y) =
+    P.doCmpEq (P.IntValue x) (P.IntValue y)
+  ValuePrim (UnsignedValue x) == ValuePrim (UnsignedValue y) =
+    P.doCmpEq (P.IntValue x) (P.IntValue y)
+  ValuePrim (FloatValue x) == ValuePrim (FloatValue y) =
+    P.doCmpEq (P.FloatValue x) (P.FloatValue y)
+  ValuePrim (BoolValue x) == ValuePrim (BoolValue y) =
+    P.doCmpEq (P.BoolValue x) (P.BoolValue y)
+  ValueArray _ x == ValueArray _ y = x == y
+  ValueRecord x == ValueRecord y = x == y
+  ValueSum _ n1 vs1 == ValueSum _ n2 vs2 = n1 == n2 && vs1 == vs2
+  ValueAcc _ x == ValueAcc _ y = x == y
+  _ == _ = False
+
+prettyValueWith :: (PrimValue -> Doc a) -> Value m -> Doc a
+prettyValueWith pprPrim = pprPrec 0
+  where
+    pprPrec _ (ValuePrim v) = pprPrim v
+    pprPrec _ (ValueArray _ a) =
+      let elements = elems a -- [Value]
+          separator = case elements of
+            ValueArray _ _ : _ -> vsep
+            _ -> hsep
+       in brackets $ align $ separator $ punctuate comma $ map pprElem elements
+    pprPrec _ (ValueRecord m) = prettyRecord (pprPrec 0) m
+    pprPrec _ ValueFun {} = "#<fun>"
+    pprPrec _ ValueAcc {} = "#<acc>"
+    pprPrec p (ValueSum _ n vs) =
+      parensIf (p > (0 :: Int)) $ "#" <> sep (pretty n : map (pprPrec 1) vs)
+    pprElem v@ValueArray {} = pprPrec 0 v
+    pprElem v = group $ pprPrec 0 v
+
+-- | Prettyprint value.
+prettyValue :: Value m -> Doc a
+prettyValue = prettyValueWith pprPrim
+  where
+    pprPrim (UnsignedValue (Int8Value v)) = pretty v
+    pprPrim (UnsignedValue (Int16Value v)) = pretty v
+    pprPrim (UnsignedValue (Int32Value v)) = pretty v
+    pprPrim (UnsignedValue (Int64Value v)) = pretty v
+    pprPrim (SignedValue (Int8Value v)) = pretty v
+    pprPrim (SignedValue (Int16Value v)) = pretty v
+    pprPrim (SignedValue (Int32Value v)) = pretty v
+    pprPrim (SignedValue (Int64Value v)) = pretty v
+    pprPrim (BoolValue True) = "true"
+    pprPrim (BoolValue False) = "false"
+    pprPrim (FloatValue (Float16Value v)) = pprFloat "f16." v
+    pprPrim (FloatValue (Float32Value v)) = pprFloat "f32." v
+    pprPrim (FloatValue (Float64Value v)) = pprFloat "f64." v
+    pprFloat t v
+      | isInfinite v, v >= 0 = t <> "inf"
+      | isInfinite v, v < 0 = "-" <> t <> "inf"
+      | isNaN v = t <> "nan"
+      | otherwise = pretty $ show v
+
+-- | The value in the textual format.
+valueText :: Value m -> T.Text
+valueText = docText . prettyValueWith pretty
+
+valueShape :: Value m -> ValueShape
+valueShape (ValueArray shape _) = shape
+valueShape (ValueRecord fs) = ShapeRecord $ M.map valueShape fs
+valueShape (ValueSum shape _ _) = shape
+valueShape _ = ShapeLeaf
+
+-- | Does the value correspond to an empty array?
+isEmptyArray :: Value m -> Bool
+isEmptyArray = emptyShape . valueShape
+
+-- | String representation of an empty array with the provided element
+-- type.  This is pretty ad-hoc - don't expect good results unless the
+-- element type is a primitive.
+prettyEmptyArray :: TypeBase () () -> Value m -> T.Text
+prettyEmptyArray t v =
+  "empty(" <> dims (valueShape v) <> prettyText t' <> ")"
+  where
+    t' = stripArray (arrayRank t) t
+    dims (ShapeDim n rowshape) =
+      "[" <> prettyText n <> "]" <> dims rowshape
+    dims _ = ""
+
+toArray :: ValueShape -> [Value m] -> Value m
+toArray shape vs = ValueArray shape (listArray (0, length vs - 1) vs)
+
+toArray' :: ValueShape -> [Value m] -> Value m
+toArray' rowshape vs = ValueArray shape (listArray (0, length vs - 1) vs)
+  where
+    shape = ShapeDim (genericLength vs) rowshape
+
+arrayLength :: Integral int => Array Int (Value m) -> int
+arrayLength = fromIntegral . (+ 1) . snd . bounds
+
+toTuple :: [Value m] -> Value m
+toTuple = ValueRecord . M.fromList . zip tupleFieldNames
+
+fromTuple :: Value m -> Maybe [Value m]
+fromTuple (ValueRecord m) = areTupleFields m
+fromTuple _ = Nothing
+
+fromDataShape :: V.Vector Int -> ValueShape
+fromDataShape = foldr (ShapeDim . fromIntegral) ShapeLeaf . SVec.toList
+
+fromDataValueWith ::
+  SVec.Storable a =>
+  (a -> PrimValue) ->
+  SVec.Vector Int ->
+  SVec.Vector a ->
+  Value m
+fromDataValueWith f shape vector =
+  if SVec.null shape
+    then ValuePrim $ f $ SVec.head vector
+    else
+      toArray (fromDataShape shape)
+        . map (fromDataValueWith f shape' . SVec.fromList)
+        $ chunk (SVec.product shape') (SVec.toList vector)
+  where
+    shape' = SVec.tail shape
+
+-- | Convert a Futhark value in the externally observable data format
+-- to an interpreter value.
+fromDataValue :: V.Value -> Value m
+fromDataValue (V.I8Value shape vector) =
+  fromDataValueWith (SignedValue . Int8Value) shape vector
+fromDataValue (V.I16Value shape vector) =
+  fromDataValueWith (SignedValue . Int16Value) shape vector
+fromDataValue (V.I32Value shape vector) =
+  fromDataValueWith (SignedValue . Int32Value) shape vector
+fromDataValue (V.I64Value shape vector) =
+  fromDataValueWith (SignedValue . Int64Value) shape vector
+fromDataValue (V.U8Value shape vector) =
+  fromDataValueWith (UnsignedValue . Int8Value . fromIntegral) shape vector
+fromDataValue (V.U16Value shape vector) =
+  fromDataValueWith (UnsignedValue . Int16Value . fromIntegral) shape vector
+fromDataValue (V.U32Value shape vector) =
+  fromDataValueWith (UnsignedValue . Int32Value . fromIntegral) shape vector
+fromDataValue (V.U64Value shape vector) =
+  fromDataValueWith (UnsignedValue . Int64Value . fromIntegral) shape vector
+fromDataValue (V.F16Value shape vector) =
+  fromDataValueWith (FloatValue . Float16Value) shape vector
+fromDataValue (V.F32Value shape vector) =
+  fromDataValueWith (FloatValue . Float32Value) shape vector
+fromDataValue (V.F64Value shape vector) =
+  fromDataValueWith (FloatValue . Float64Value) shape vector
+fromDataValue (V.BoolValue shape vector) =
+  fromDataValueWith BoolValue shape vector
diff --git a/src/Language/Futhark/Parser.hs b/src/Language/Futhark/Parser.hs
--- a/src/Language/Futhark/Parser.hs
+++ b/src/Language/Futhark/Parser.hs
@@ -1,19 +1,15 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Interface to the Futhark parser.
 module Language.Futhark.Parser
   ( parseFuthark,
     parseExp,
     parseModExp,
     parseType,
-    parseValue,
-    parseValues,
     parseDecOrExpIncrM,
     SyntaxError (..),
   )
 where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Language.Futhark.Parser.Parser
 import Language.Futhark.Prop
 import Language.Futhark.Syntax
@@ -49,23 +45,6 @@
   T.Text ->
   Either SyntaxError UncheckedTypeExp
 parseType = parse futharkType
-
--- | Parse any Futhark value from the given 'String', using the 'FilePath'
--- as the source name for error messages.
-parseValue ::
-  FilePath ->
-  T.Text ->
-  Either SyntaxError Value
-parseValue = parse anyValue
-
--- | Parse several Futhark values (separated by anything) from the given
--- 'String', using the 'FilePath' as the source name for error
--- messages.
-parseValues ::
-  FilePath ->
-  T.Text ->
-  Either SyntaxError [Value]
-parseValues = parse anyValues
 
 -- | Parse an Futhark expression incrementally from monadic actions, using the
 -- 'FilePath' as the source name for error messages.
diff --git a/src/Language/Futhark/Parser/Lexer.x b/src/Language/Futhark/Parser/Lexer.x
--- a/src/Language/Futhark/Parser/Lexer.x
+++ b/src/Language/Futhark/Parser/Lexer.x
@@ -1,7 +1,4 @@
 {
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE Trustworthy #-}
 {-# OPTIONS_GHC -w #-}
 -- | The Futhark lexer.  Takes a string, produces a list of tokens with position information.
 module Language.Futhark.Parser.Lexer
diff --git a/src/Language/Futhark/Parser/Lexer/Tokens.hs b/src/Language/Futhark/Parser/Lexer/Tokens.hs
--- a/src/Language/Futhark/Parser/Lexer/Tokens.hs
+++ b/src/Language/Futhark/Parser/Lexer/Tokens.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE Strict #-}
 
 -- | Definition of the tokens used in the lexer.
@@ -24,14 +22,14 @@
   )
 where
 
-import qualified Data.ByteString.Lazy as BS
+import Data.ByteString.Lazy qualified as BS
 import Data.Char (digitToInt, ord)
 import Data.Either
 import Data.List (find, foldl')
 import Data.Loc (Loc (..), Pos (..))
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import qualified Data.Text.Read as T
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.Read qualified as T
 import Language.Futhark.Core
   ( Int16,
     Int32,
@@ -170,7 +168,7 @@
 indexing :: (Loc, T.Text) -> Alex Name
 indexing (loc, s) = case keyword s of
   ID v -> pure v
-  _ -> alexError loc $ "Cannot index keyword '" ++ T.unpack s ++ "'."
+  _ -> alexError loc $ "Cannot index keyword '" <> s <> "'."
 
 mkQualId :: T.Text -> Alex ([Name], Name)
 mkQualId s = case reverse $ T.splitOn "." s of
diff --git a/src/Language/Futhark/Parser/Lexer/Wrapper.hs b/src/Language/Futhark/Parser/Lexer/Wrapper.hs
--- a/src/Language/Futhark/Parser/Lexer/Wrapper.hs
+++ b/src/Language/Futhark/Parser/Lexer/Wrapper.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE BangPatterns #-}
 {-# OPTIONS_GHC -funbox-strict-fields #-}
 
 -- | Utility definitions used by the lexer.  None of the default Alex
@@ -22,10 +21,11 @@
 where
 
 import Control.Applicative (liftA)
-import qualified Data.ByteString.Internal as BS (w2c)
-import qualified Data.ByteString.Lazy as BS
+import Data.ByteString.Internal qualified as BS (w2c)
+import Data.ByteString.Lazy qualified as BS
 import Data.Int (Int64)
 import Data.Loc (Loc, Pos (..))
+import Data.Text qualified as T
 import Data.Word (Word8)
 
 type Byte = Word8
@@ -90,10 +90,10 @@
 
 newtype Alex a = Alex {unAlex :: AlexState -> Either LexerError (AlexState, a)}
 
-data LexerError = LexerError Loc String
+data LexerError = LexerError Loc T.Text
 
 instance Show LexerError where
-  show (LexerError _ s) = s
+  show (LexerError _ s) = T.unpack s
 
 instance Functor Alex where
   fmap = liftA
@@ -126,7 +126,7 @@
     } of
     state@AlexState {} -> Right (state, ())
 
-alexError :: Loc -> String -> Alex a
+alexError :: Loc -> T.Text -> Alex a
 alexError loc message = Alex $ const $ Left $ LexerError loc message
 
 alexGetStartCode :: Alex Int
diff --git a/src/Language/Futhark/Parser/Monad.hs b/src/Language/Futhark/Parser/Monad.hs
--- a/src/Language/Futhark/Parser/Monad.hs
+++ b/src/Language/Futhark/Parser/Monad.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Utility functions and definitions used in the Happy-generated
 -- parser.  They are defined here because the @.y@ file is opaque to
 -- linters and other tools.  In particular, we cannot enable warnings
@@ -16,14 +13,10 @@
     lexer,
     mustBeEmpty,
     arrayFromList,
-    combArrayElements,
     binOp,
     binOpName,
     mustBe,
-    floatNegate,
-    intNegate,
     primNegate,
-    primTypeFromName,
     applyExp,
     patternExp,
     addDocSpec,
@@ -48,11 +41,10 @@
 import Control.Monad.Except
 import Control.Monad.Trans.State
 import Data.Array hiding (index)
-import qualified Data.Map.Strict as M
 import Data.Monoid
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Util.Loc
-import Futhark.Util.Pretty hiding (line)
+import Futhark.Util.Pretty hiding (line, line')
 import Language.Futhark.Parser.Lexer
 import Language.Futhark.Parser.Lexer.Wrapper (LexerError (..))
 import Language.Futhark.Pretty ()
@@ -83,9 +75,9 @@
 addAttrSpec :: AttrInfo Name -> UncheckedSpec -> UncheckedSpec
 addAttrSpec _attr dec = dec
 
-mustBe :: L Token -> String -> ParserMonad ()
+mustBe :: L Token -> T.Text -> ParserMonad ()
 mustBe (L _ (ID got)) expected
-  | nameToString got == expected = pure ()
+  | nameToText got == expected = pure ()
 mustBe (L loc _) expected =
   parseErrorAt loc . Just $
     "Only the keyword '" <> expected <> "' may appear here."
@@ -94,7 +86,7 @@
 mustBeEmpty _ (Array _ _ (Shape dims) _)
   | 0 `elem` dims = pure ()
 mustBeEmpty loc t =
-  parseErrorAt loc $ Just $ pretty t ++ " is not an empty array."
+  parseErrorAt loc $ Just $ prettyText t <> " is not an empty array."
 
 data ParserState = ParserState
   { _parserFile :: FilePath,
@@ -132,19 +124,6 @@
 getNoLines (Value x) = Right x
 getNoLines (GetLine f) = getNoLines $ f Nothing
 
-combArrayElements :: Value -> [Value] -> Either SyntaxError Value
-combArrayElements = foldM comb
-  where
-    comb x y
-      | valueType x == valueType y = Right x
-      | otherwise =
-          Left . SyntaxError NoLoc $
-            "Elements "
-              <> pretty x
-              <> " and "
-              <> pretty y
-              <> " cannot exist in same array."
-
 arrayFromList :: [a] -> Array Int a
 arrayFromList l = listArray (0, length l - 1) l
 
@@ -155,10 +134,10 @@
   foldM op (head es) (tail es)
   where
     op (AppExp (Index e is floc) _) (ArrayLit xs _ xloc) =
-      parseErrorAt (srcspan floc xloc) . Just . pretty $
+      parseErrorAt (srcspan floc xloc) . Just . docText $
         "Incorrect syntax for multi-dimensional indexing."
           </> "Use"
-          <+> align (ppr index)
+          <+> align (pretty index)
       where
         index = AppExp (Index e (is ++ map DimFix xs) xloc) NoInfo
     op f x =
@@ -195,11 +174,6 @@
 putTokens :: ([L Token], Pos) -> ParserMonad ()
 putTokens l = lift $ modify $ \env -> env {parserLexical = l}
 
-primTypeFromName :: Loc -> Name -> ParserMonad PrimType
-primTypeFromName loc s = maybe boom pure $ M.lookup s namesToPrimTypes
-  where
-    boom = parseErrorAt loc $ Just $ "No type named " ++ nameToString s
-
 intNegate :: IntValue -> IntValue
 intNegate (Int8Value v) = Int8Value (-v)
 intNegate (Int16Value v) = Int16Value (-v)
@@ -255,9 +229,9 @@
 
 parseError :: (L Token, [String]) -> ParserMonad a
 parseError (L loc EOF, expected) =
-  parseErrorAt (locOf loc) . Just . unlines $
+  parseErrorAt (locOf loc) . Just . T.unlines $
     [ "Unexpected end of file.",
-      "Expected one of the following: " ++ unwords expected
+      "Expected one of the following: " <> T.unwords (map T.pack expected)
     ]
 parseError (L loc DOC {}, _) =
   parseErrorAt (locOf loc) $
@@ -266,12 +240,12 @@
   input <- lift $ gets parserInput
   let ~(Loc (Pos _ _ _ beg) (Pos _ _ _ end)) = locOf loc
       tok_src = T.take (end - beg + 1) $ T.drop beg input
-  parseErrorAt loc . Just . unlines $
-    [ "Unexpected token: '" <> T.unpack tok_src <> "'",
-      "Expected one of the following: " <> unwords expected
+  parseErrorAt loc . Just . T.unlines $
+    [ "Unexpected token: '" <> tok_src <> "'",
+      "Expected one of the following: " <> T.unwords (map T.pack expected)
     ]
 
-parseErrorAt :: Located loc => loc -> Maybe String -> ParserMonad a
+parseErrorAt :: Located loc => loc -> Maybe T.Text -> ParserMonad a
 parseErrorAt loc Nothing = throwError $ SyntaxError (locOf loc) "Syntax error."
 parseErrorAt loc (Just s) = throwError $ SyntaxError (locOf loc) s
 
@@ -291,7 +265,7 @@
 --- Now for the parser interface.
 
 -- | A syntax error.
-data SyntaxError = SyntaxError {syntaxErrorLoc :: Loc, syntaxErrorMsg :: String}
+data SyntaxError = SyntaxError {syntaxErrorLoc :: Loc, syntaxErrorMsg :: T.Text}
 
 lexerErrToParseErr :: LexerError -> SyntaxError
 lexerErrToParseErr (LexerError loc msg) = SyntaxError loc msg
diff --git a/src/Language/Futhark/Parser/Parser.y b/src/Language/Futhark/Parser/Parser.y
--- a/src/Language/Futhark/Parser/Parser.y
+++ b/src/Language/Futhark/Parser/Parser.y
@@ -1,7 +1,4 @@
 {
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Futhark parser written with Happy.
 module Language.Futhark.Parser.Parser
   ( prog
@@ -9,8 +6,6 @@
   , declaration
   , modExpression
   , futharkType
-  , anyValue
-  , anyValues
   , parse
   , ReadLineMonad (..)
   , getLinesFromM
@@ -51,8 +46,6 @@
 %name expression Exp
 %name modExpression ModExp
 %name declaration Dec
-%name anyValue Value
-%name anyValues CatValues
 
 %tokentype { L Token }
 %error { parseError }
@@ -424,14 +417,14 @@
         -- Some error cases
         | def '(' Pat ',' Pats1 ')' '=' Exp
           {% parseErrorAt (srcspan $2 $6) $ Just $
-             unlines ["Cannot bind patterns at top level.",
-                      "Bind a single name instead."]
+             T.unlines ["Cannot bind patterns at top level.",
+                        "Bind a single name instead."]
           }
 
         | let '(' Pat ',' Pats1 ')' '=' Exp
           {% parseErrorAt (srcspan $2 $6) $ Just $
-             unlines ["Cannot bind patterns at top level.",
-                      "Bind a single name instead."]
+             T.unlines ["Cannot bind patterns at top level.",
+                        "Bind a single name instead."]
           }
 
 TypeAbbr :: { TypeBindBase NoInfo Name }
@@ -464,8 +457,8 @@
          -- Errors
          | '[' SizeExp ']' %prec bottom
            {% parseErrorAt (srcspan $1 $>) $ Just $
-                unlines ["missing array row type.",
-                         "Did you mean []"  ++ pretty $2 ++ "?"]
+                T.unlines ["missing array row type.",
+                           "Did you mean []"  <> prettyText $2 <> "?"]
            }
 
 SumType :: { UncheckedTypeExp }
@@ -938,94 +931,3 @@
 Attrs :: { [AttrInfo Name] }
        : AttrInfo           { [$1] }
        | AttrInfo ',' Attrs { $1 : $3 }
-
-Value :: { Value }
-Value : IntValue { $1 }
-      | FloatValue { $1 }
-      | StringValue { $1 }
-      | BoolValue { $1 }
-      | ArrayValue { $1 }
-
-CatValues :: { [Value] }
-CatValues : Value CatValues { $1 : $2 }
-          |                 { [] }
-
-PrimType :: { PrimType }
-         : id {% let L loc (ID s) = $1 in primTypeFromName loc s }
-
-IntValue :: { Value }
-         : SignedLit { PrimValue (SignedValue (fst $1)) }
-         | '-' SignedLit { PrimValue (SignedValue (intNegate (fst $2))) }
-         | UnsignedLit { PrimValue (UnsignedValue (fst $1)) }
-
-FloatValue :: { Value }
-         : FloatLit     { PrimValue (FloatValue (fst $1)) }
-         | '-' FloatLit { PrimValue (FloatValue (floatNegate (fst $2))) }
-
-StringValue :: { Value }
-StringValue : stringlit  { let L pos (STRINGLIT s) = $1 in
-                           ArrayValue (arrayFromList $ map (PrimValue . UnsignedValue . Int8Value . fromIntegral) $ BS.unpack $ T.encodeUtf8 s) $ Scalar $ Prim $ Signed Int32 }
-
-BoolValue :: { Value }
-BoolValue : true           { PrimValue $ BoolValue True }
-          | false          { PrimValue $ BoolValue False }
-
-SignedLit :: { (IntValue, Loc) }
-          : i8lit   { let L loc (I8LIT num)  = $1 in (Int8Value num, loc) }
-          | i16lit  { let L loc (I16LIT num) = $1 in (Int16Value num, loc) }
-          | i32lit  { let L loc (I32LIT num) = $1 in (Int32Value num, loc) }
-          | i64lit  { let L loc (I64LIT num) = $1 in (Int64Value num, loc) }
-          | intlit  { let L loc (INTLIT num) = $1 in (Int32Value $ fromInteger num, loc) }
-          | charlit { let L loc (CHARLIT char) = $1 in (Int32Value $ fromIntegral $ ord char, loc) }
-
-UnsignedLit :: { (IntValue, Loc) }
-            : u8lit  { let L pos (U8LIT num)  = $1 in (Int8Value $ fromIntegral num, pos) }
-            | u16lit { let L pos (U16LIT num) = $1 in (Int16Value $ fromIntegral num, pos) }
-            | u32lit { let L pos (U32LIT num) = $1 in (Int32Value $ fromIntegral num, pos) }
-            | u64lit { let L pos (U64LIT num) = $1 in (Int64Value $ fromIntegral num, pos) }
-
-FloatLit :: { (FloatValue, Loc) }
-         : f16lit { let L loc (F16LIT num) = $1 in (Float16Value num, loc) }
-         | f32lit { let L loc (F32LIT num) = $1 in (Float32Value num, loc) }
-         | f64lit { let L loc (F64LIT num) = $1 in (Float64Value num, loc) }
-         | QualName {% let (qn, loc) = $1 in
-                       case qn of
-                         QualName ["f16"] "inf" -> pure (Float16Value (1/0), loc)
-                         QualName ["f16"] "nan" -> pure (Float16Value (0/0), loc)
-                         QualName ["f32"] "inf" -> pure (Float32Value (1/0), loc)
-                         QualName ["f32"] "nan" -> pure (Float32Value (0/0), loc)
-                         QualName ["f64"] "inf" -> pure (Float64Value (1/0), loc)
-                         QualName ["f64"] "nan" -> pure (Float64Value (0/0), loc)
-                         _ -> parseErrorAt (snd $1) Nothing
-                    }
-         | floatlit { let L loc (FLOATLIT num) = $1 in (Float64Value num, loc) }
-
-ArrayValue :: { Value }
-ArrayValue :  '[' Value ']'
-             {% pure $ ArrayValue (arrayFromList [$2]) $
-                arrayOf Unique (Shape [1]) (valueType $2)
-             }
-           |  '[' Value ',' Values ']'
-             {% case combArrayElements $2 $4 of
-                  Left e -> throwError e
-                  Right v -> pure $ ArrayValue (arrayFromList $ $2:$4) $
-                             arrayOf Unique (Shape [1+fromIntegral (length $4)]) (valueType v)
-             }
-           | id '(' ValueType ')'
-             {% ($1 `mustBe` "empty") >> mustBeEmpty (srcspan $2 $4) $3 >> pure (ArrayValue (listArray (0,-1) []) $3) }
-
-           -- Errors
-           | '[' ']'
-             {% emptyArrayError $1 }
-
-Dim :: { Int64 }
-Dim : intlit { let L _ (INTLIT num) = $1 in fromInteger num }
-
-ValueType :: { ValueType }
-ValueType : '[' Dim ']' ValueType  { arrayOf Nonunique (Shape [$2]) $4 }
-          | '[' Dim ']' PrimType { arrayOf Nonunique (Shape [$2]) (Scalar (Prim $4)) }
-
-Values :: { [Value] }
-Values : Value ',' Values { $1 : $3 }
-       | Value            { [$1] }
-       |                  { [] }
diff --git a/src/Language/Futhark/Prelude.hs b/src/Language/Futhark/Prelude.hs
--- a/src/Language/Futhark/Prelude.hs
+++ b/src/Language/Futhark/Prelude.hs
@@ -1,7 +1,5 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE Trustworthy #-}
 
 -- | The Futhark Prelude Library embedded embedded as strings read
 -- during compilation of the Futhark compiler.  The advantage is that
@@ -10,10 +8,10 @@
 module Language.Futhark.Prelude (prelude) where
 
 import Data.FileEmbed
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
 import Futhark.Util (toPOSIX)
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 
 -- | Prelude embedded as 'T.Text' values, one for every file.
 prelude :: [(Posix.FilePath, T.Text)]
diff --git a/src/Language/Futhark/Pretty.hs b/src/Language/Futhark/Pretty.hs
--- a/src/Language/Futhark/Pretty.hs
+++ b/src/Language/Futhark/Pretty.hs
@@ -1,28 +1,23 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 -- | Futhark prettyprinter.  This module defines 'Pretty' instances
 -- for the AST defined in "Language.Futhark.Syntax".
 module Language.Futhark.Pretty
-  ( pretty,
+  ( prettyString,
     prettyTuple,
     leadingOperator,
     IsName (..),
-    prettyName,
     Annot (..),
   )
 where
 
 import Control.Monad
-import Data.Array
 import Data.Char (chr)
 import Data.Functor
 import Data.List (intersperse)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Monoid hiding (Sum)
 import Data.Ord
@@ -42,23 +37,22 @@
 -- VNames, we in fact only define it inside the modules for the core
 -- language (as an orphan instance).
 class IsName v where
-  pprName :: v -> Doc
+  prettyName :: v -> Doc a
+  toName :: v -> Name
 
 -- | Depending on the environment variable FUTHARK_COMPILER_DEBUGGING,
 -- VNames are printed as either the name with an internal tag, or just
 -- the base name.
 instance IsName VName where
-  pprName
+  prettyName
     | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 1 =
-        \(VName vn i) -> ppr vn <> text "_" <> text (show i)
-    | otherwise = ppr . baseName
+        \(VName vn i) -> pretty vn <> "_" <> pretty (show i)
+    | otherwise = pretty . baseName
+  toName = baseName
 
 instance IsName Name where
-  pprName = ppr
-
--- | Prettyprint a name to a string.
-prettyName :: IsName v => v -> String
-prettyName = prettyDoc 80 . pprName
+  prettyName = pretty
+  toName = id
 
 -- | Class for type constructors that represent annotations.  Used in
 -- the prettyprinter to either print the original AST, or the computed
@@ -73,125 +67,129 @@
 instance Annot Info where
   unAnnot = Just . unInfo
 
-instance Pretty Value where
-  ppr (PrimValue bv) = ppr bv
-  ppr (ArrayValue a t)
-    | [] <- elems a = text "empty" <> parens (ppr t)
-    | Array {} <- t = brackets $ commastack $ map ppr $ elems a
-    | otherwise = brackets $ commasep $ map ppr $ elems a
-
 instance Pretty PrimValue where
-  ppr (UnsignedValue (Int8Value v)) =
-    text (show (fromIntegral v :: Word8)) <> text "u8"
-  ppr (UnsignedValue (Int16Value v)) =
-    text (show (fromIntegral v :: Word16)) <> text "u16"
-  ppr (UnsignedValue (Int32Value v)) =
-    text (show (fromIntegral v :: Word32)) <> text "u32"
-  ppr (UnsignedValue (Int64Value v)) =
-    text (show (fromIntegral v :: Word64)) <> text "u64"
-  ppr (SignedValue v) = ppr v
-  ppr (BoolValue True) = text "true"
-  ppr (BoolValue False) = text "false"
-  ppr (FloatValue v) = ppr v
+  pretty (UnsignedValue (Int8Value v)) =
+    pretty (show (fromIntegral v :: Word8)) <> "u8"
+  pretty (UnsignedValue (Int16Value v)) =
+    pretty (show (fromIntegral v :: Word16)) <> "u16"
+  pretty (UnsignedValue (Int32Value v)) =
+    pretty (show (fromIntegral v :: Word32)) <> "u32"
+  pretty (UnsignedValue (Int64Value v)) =
+    pretty (show (fromIntegral v :: Word64)) <> "u64"
+  pretty (SignedValue v) = pretty v
+  pretty (BoolValue True) = "true"
+  pretty (BoolValue False) = "false"
+  pretty (FloatValue v) = pretty v
 
 instance Pretty Size where
-  ppr (AnySize Nothing) = mempty
-  ppr (AnySize (Just v)) = text "?" <> pprName v
-  ppr (NamedSize v) = ppr v
-  ppr (ConstSize n) = ppr n
+  pretty (AnySize Nothing) = mempty
+  pretty (AnySize (Just v)) = "?" <> prettyName v
+  pretty (NamedSize v) = pretty v
+  pretty (ConstSize n) = pretty n
 
 instance IsName vn => Pretty (SizeExp vn) where
-  ppr SizeExpAny = mempty
-  ppr (SizeExpNamed v _) = ppr v
-  ppr (SizeExpConst n _) = ppr n
+  pretty SizeExpAny = mempty
+  pretty (SizeExpNamed v _) = pretty v
+  pretty (SizeExpConst n _) = pretty n
 
 instance Pretty (Shape Size) where
-  ppr (Shape ds) = mconcat (map (brackets . ppr) ds)
+  pretty (Shape ds) = mconcat (map (brackets . pretty) ds)
 
 instance Pretty (Shape ()) where
-  ppr (Shape ds) = mconcat $ replicate (length ds) $ text "[]"
+  pretty (Shape ds) = mconcat $ replicate (length ds) "[]"
 
 instance Pretty (Shape Int64) where
-  ppr (Shape ds) = mconcat (map (brackets . ppr) ds)
+  pretty (Shape ds) = mconcat (map (brackets . pretty) ds)
 
 instance Pretty (Shape Bool) where
-  ppr (Shape ds) = mconcat (map (brackets . ppr) ds)
+  pretty (Shape ds) = mconcat (map (brackets . pretty) ds)
 
+prettyRetType :: Pretty (Shape dim) => Int -> RetTypeBase dim as -> Doc a
+prettyRetType p (RetType [] t) =
+  prettyType p t
+prettyRetType _ (RetType dims t) =
+  "?"
+    <> mconcat (map (brackets . prettyName) dims)
+    <> "."
+    <> pretty t
+
 instance Pretty (Shape dim) => Pretty (RetTypeBase dim as) where
-  ppr = pprPrec 0
-  pprPrec p (RetType [] t) = pprPrec p t
-  pprPrec _ (RetType dims t) =
-    text "?" <> mconcat (map (brackets . pprName) dims) <> text "." <> ppr t
+  pretty = prettyRetType 0
 
+prettyScalarType :: Pretty (Shape dim) => Int -> ScalarTypeBase dim as -> Doc a
+prettyScalarType _ (Prim et) = pretty et
+prettyScalarType p (TypeVar _ u v targs) =
+  parensIf (not (null targs) && p > 3) $
+    pretty u <> hsep (pretty v : map (prettyTypeArg 3) targs)
+prettyScalarType _ (Record fs)
+  | Just ts <- areTupleFields fs =
+      group $ parens $ align $ mconcat $ punctuate ("," <> line) $ map pretty ts
+  | otherwise =
+      group $ braces $ align $ mconcat $ punctuate ("," <> line) fs'
+  where
+    ppField (name, t) = pretty (nameToString name) <> colon <+> align (pretty t)
+    fs' = map ppField $ M.toList fs
+prettyScalarType p (Arrow _ (Named v) t1 t2) =
+  parensIf (p > 1) $
+    parens (prettyName v <> colon <+> align (pretty t1)) <+> "->" <+> prettyRetType 1 t2
+prettyScalarType p (Arrow _ Unnamed t1 t2) =
+  parensIf (p > 1) $ prettyType 2 t1 <+> "->" <+> prettyRetType 1 t2
+prettyScalarType p (Sum cs) =
+  parensIf (p > 0) $
+    group (align (mconcat $ punctuate (" |" <> line) cs'))
+  where
+    ppConstr (name, fs) = sep $ ("#" <> pretty name) : map (prettyType 2) fs
+    cs' = map ppConstr $ M.toList cs
+
 instance Pretty (Shape dim) => Pretty (ScalarTypeBase dim as) where
-  ppr = pprPrec 0
-  pprPrec _ (Prim et) = ppr et
-  pprPrec p (TypeVar _ u v targs) =
-    parensIf (not (null targs) && p > 3) $
-      ppr u <> ppr v <+> spread (map (pprPrec 3) targs)
-  pprPrec _ (Record fs)
-    | Just ts <- areTupleFields fs =
-        oneLine (parens $ commasep $ map ppr ts)
-          <|> parens (align $ mconcat $ punctuate (text "," <> line) $ map ppr ts)
-    | otherwise =
-        oneLine (braces $ commasep fs')
-          <|> braces (align $ mconcat $ punctuate (text "," <> line) fs')
-    where
-      ppField (name, t) = text (nameToString name) <> colon <+> align (ppr t)
-      fs' = map ppField $ M.toList fs
-  pprPrec p (Arrow _ (Named v) t1 t2) =
-    parensIf (p > 1) $
-      parens (pprName v <> colon <+> align (ppr t1)) <+/> text "->" <+> pprPrec 1 t2
-  pprPrec p (Arrow _ Unnamed t1 t2) =
-    parensIf (p > 1) $ pprPrec 2 t1 <+/> text "->" <+> pprPrec 1 t2
-  pprPrec p (Sum cs) =
-    parensIf (p > 0) $
-      oneLine (mconcat $ punctuate (text " | ") cs')
-        <|> align (mconcat $ punctuate (text " |" <> line) cs')
-    where
-      ppConstr (name, fs) = sep $ (text "#" <> ppr name) : map (pprPrec 2) fs
-      cs' = map ppConstr $ M.toList cs
+  pretty = prettyScalarType 0
 
+prettyType :: Pretty (Shape dim) => Int -> TypeBase dim as -> Doc a
+prettyType _ (Array _ u shape at) =
+  pretty u <> pretty shape <> align (prettyScalarType 1 at)
+prettyType p (Scalar t) =
+  prettyScalarType p t
+
 instance Pretty (Shape dim) => Pretty (TypeBase dim as) where
-  ppr = pprPrec 0
-  pprPrec _ (Array _ u shape at) = ppr u <> ppr shape <> align (pprPrec 1 at)
-  pprPrec p (Scalar t) = pprPrec p t
+  pretty = prettyType 0
 
+prettyTypeArg :: Pretty (Shape dim) => Int -> TypeArg dim -> Doc a
+prettyTypeArg _ (TypeArgDim d _) = pretty $ Shape [d]
+prettyTypeArg p (TypeArgType t _) = prettyType p t
+
 instance Pretty (Shape dim) => Pretty (TypeArg dim) where
-  ppr = pprPrec 0
-  pprPrec _ (TypeArgDim d _) = ppr $ Shape [d]
-  pprPrec p (TypeArgType t _) = pprPrec p t
+  pretty = prettyTypeArg 0
 
 instance (Eq vn, IsName vn) => Pretty (TypeExp vn) where
-  ppr (TEUnique t _) = text "*" <> ppr t
-  ppr (TEArray d at _) = brackets (ppr d) <> ppr at
-  ppr (TETuple ts _) = parens $ commasep $ map ppr ts
-  ppr (TERecord fs _) = braces $ commasep $ map ppField fs
+  pretty (TEUnique t _) = "*" <> pretty t
+  pretty (TEArray d at _) = brackets (pretty d) <> pretty at
+  pretty (TETuple ts _) = parens $ commasep $ map pretty ts
+  pretty (TERecord fs _) = braces $ commasep $ map ppField fs
     where
-      ppField (name, t) = text (nameToString name) <> colon <+> ppr t
-  ppr (TEVar name _) = ppr name
-  ppr (TEApply t arg _) = ppr t <+> ppr arg
-  ppr (TEArrow (Just v) t1 t2 _) = parens v' <+> text "->" <+> ppr t2
+      ppField (name, t) = pretty (nameToString name) <> colon <+> pretty t
+  pretty (TEVar name _) = pretty name
+  pretty (TEApply t arg _) = pretty t <+> pretty arg
+  pretty (TEArrow (Just v) t1 t2 _) = parens v' <+> "->" <+> pretty t2
     where
-      v' = pprName v <> colon <+> ppr t1
-  ppr (TEArrow Nothing t1 t2 _) = ppr t1 <+> text "->" <+> ppr t2
-  ppr (TESum cs _) =
-    align $ cat $ punctuate (text " |" <> softline) $ map ppConstr cs
+      v' = prettyName v <> colon <+> pretty t1
+  pretty (TEArrow Nothing t1 t2 _) = pretty t1 <+> "->" <+> pretty t2
+  pretty (TESum cs _) =
+    align $ cat $ punctuate (" |" <> softline) $ map ppConstr cs
     where
-      ppConstr (name, fs) = text "#" <> ppr name <+> sep (map ppr fs)
-  ppr (TEDim dims te _) =
-    text "?" <> mconcat (map (brackets . pprName) dims) <> text "." <> ppr te
+      ppConstr (name, fs) = "#" <> pretty name <+> sep (map pretty fs)
+  pretty (TEDim dims te _) =
+    "?" <> mconcat (map (brackets . prettyName) dims) <> "." <> pretty te
 
 instance (Eq vn, IsName vn) => Pretty (TypeArgExp vn) where
-  ppr (TypeArgExpDim d _) = brackets $ ppr d
-  ppr (TypeArgExpType t) = ppr t
+  pretty (TypeArgExpDim d _) = brackets $ pretty d
+  pretty (TypeArgExpType t) = pretty t
 
 instance IsName vn => Pretty (QualName vn) where
-  ppr (QualName names name) =
-    mconcat $ punctuate (text ".") $ map pprName names ++ [pprName name]
+  pretty (QualName names name) =
+    mconcat $ punctuate "." $ map prettyName names ++ [prettyName name]
 
 instance IsName vn => Pretty (IdentBase f vn) where
-  ppr = pprName . identName
+  pretty = prettyName . identName
 
 hasArrayLit :: ExpBase ty vn -> Bool
 hasArrayLit ArrayLit {} = True
@@ -199,349 +197,353 @@
 hasArrayLit _ = False
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (DimIndexBase f vn) where
-  ppr (DimFix e) = ppr e
-  ppr (DimSlice i j (Just s)) =
-    maybe mempty ppr i
-      <> text ":"
-      <> maybe mempty ppr j
-      <> text ":"
-      <> ppr s
-  ppr (DimSlice i (Just j) s) =
-    maybe mempty ppr i
-      <> text ":"
-      <> ppr j
-      <> maybe mempty ((text ":" <>) . ppr) s
-  ppr (DimSlice i Nothing Nothing) =
-    maybe mempty ppr i <> text ":"
+  pretty (DimFix e) = pretty e
+  pretty (DimSlice i j (Just s)) =
+    maybe mempty pretty i
+      <> ":"
+      <> maybe mempty pretty j
+      <> ":"
+      <> pretty s
+  pretty (DimSlice i (Just j) s) =
+    maybe mempty pretty i
+      <> ":"
+      <> pretty j
+      <> maybe mempty ((":" <>) . pretty) s
+  pretty (DimSlice i Nothing Nothing) =
+    maybe mempty pretty i <> ":"
 
 instance IsName vn => Pretty (SizeBinder vn) where
-  ppr (SizeBinder v _) = brackets $ pprName v
+  pretty (SizeBinder v _) = brackets $ prettyName v
 
-letBody :: (Eq vn, IsName vn, Annot f) => ExpBase f vn -> Doc
-letBody body@(AppExp LetPat {} _) = ppr body
-letBody body@(AppExp LetFun {} _) = ppr body
-letBody body = text "in" <+> align (ppr body)
+letBody :: (Eq vn, IsName vn, Annot f) => ExpBase f vn -> Doc a
+letBody body@(AppExp LetPat {} _) = pretty body
+letBody body@(AppExp LetFun {} _) = pretty body
+letBody body = "in" <+> align (pretty body)
 
-instance (Eq vn, IsName vn, Annot f) => Pretty (AppExpBase f vn) where
-  ppr = pprPrec (-1)
-  pprPrec p (Coerce e t _) =
-    parensIf (p /= -1) $ pprPrec 0 e <+> text ":>" <+> align (pprPrec 0 t)
-  pprPrec p (BinOp (bop, _) _ (x, _) (y, _) _) = prettyBinOp p bop x y
-  pprPrec _ (Match e cs _) = text "match" <+> ppr e </> (stack . map ppr) (NE.toList cs)
-  pprPrec _ (DoLoop sizeparams pat initexp form loopbody _) =
-    text "loop"
-      <+> align
-        ( spread (map (brackets . pprName) sizeparams)
-            <+/> ppr pat
-            <+> equals
-            <+/> ppr initexp
-            <+/> ppr form
-            <+> text "do"
-        )
-      </> indent 2 (ppr loopbody)
-  pprPrec _ (Index e idxs _) =
-    pprPrec 9 e <> brackets (commasep (map ppr idxs))
-  pprPrec p (LetPat sizes pat e body _) =
-    parensIf (p /= -1) $
-      align $
-        text "let"
-          <+> spread (map ppr sizes)
-          <+> align (ppr pat)
-          <+> ( if linebreak
-                  then equals </> indent 2 (ppr e)
-                  else equals <+> align (ppr e)
-              )
-          </> letBody body
-    where
-      linebreak = case e of
-        AppExp {} -> True
-        Attr {} -> True
-        ArrayLit {} -> False
-        _ -> hasArrayLit e
-  pprPrec _ (LetFun fname (tparams, params, retdecl, rettype, e) body _) =
-    text "let"
-      <+> pprName fname
-      <+> spread (map ppr tparams ++ map ppr params)
-        <> retdecl'
-      <+> equals
-      </> indent 2 (ppr e)
-      </> letBody body
-    where
-      retdecl' = case (ppr <$> unAnnot rettype) `mplus` (ppr <$> retdecl) of
-        Just rettype' -> colon <+> align rettype'
-        Nothing -> mempty
-  pprPrec _ (LetWith dest src idxs ve body _)
-    | dest == src =
-        text "let"
-          <+> ppr dest <> list (map ppr idxs)
-          <+> equals
-          <+> align (ppr ve)
-          </> letBody body
-    | otherwise =
-        text "let"
-          <+> ppr dest
+prettyAppExp :: (Eq vn, IsName vn, Annot f) => Int -> AppExpBase f vn -> Doc a
+prettyAppExp p (Coerce e t _) =
+  parensIf (p /= -1) $ prettyExp 0 e <+> ":>" <+> align (pretty t)
+prettyAppExp p (BinOp (bop, _) _ (x, _) (y, _) _) = prettyBinOp p bop x y
+prettyAppExp _ (Match e cs _) = "match" <+> pretty e </> (stack . map pretty) (NE.toList cs)
+prettyAppExp _ (DoLoop sizeparams pat initexp form loopbody _) =
+  "loop"
+    <+> align
+      ( hsep (map (brackets . prettyName) sizeparams ++ [pretty pat])
           <+> equals
-          <+> ppr src
-          <+> text "with"
-          <+> brackets (commasep (map ppr idxs))
-          <+> text "="
-          <+> align (ppr ve)
-          </> letBody body
-  pprPrec p (Range start maybe_step end _) =
-    parensIf (p /= -1) $
-      ppr start
-        <> maybe mempty ((text ".." <>) . ppr) maybe_step
-        <> case end of
-          DownToExclusive end' -> text "..>" <> ppr end'
-          ToInclusive end' -> text "..." <> ppr end'
-          UpToExclusive end' -> text "..<" <> ppr end'
-  pprPrec _ (If c t f _) =
-    text "if"
-      <+> ppr c
-      </> text "then"
-      <+> align (ppr t)
-      </> text "else"
-      <+> align (ppr f)
-  pprPrec p (Apply f arg _ _) =
-    parensIf (p >= 10) $ pprPrec 0 f <+/> pprPrec 10 arg
+          <+> pretty initexp
+          <+> pretty form
+          <+> "do"
+      )
+    </> indent 2 (pretty loopbody)
+prettyAppExp _ (Index e idxs _) =
+  prettyExp 9 e <> brackets (commasep (map pretty idxs))
+prettyAppExp p (LetPat sizes pat e body _) =
+  parensIf (p /= -1) . align $
+    hsep ("let" : map pretty sizes ++ [align (pretty pat)])
+      <+> ( if linebreak
+              then equals </> indent 2 (pretty e)
+              else equals <+> align (pretty e)
+          )
+      </> letBody body
+  where
+    linebreak = case e of
+      AppExp {} -> True
+      Attr {} -> True
+      ArrayLit {} -> False
+      _ -> hasArrayLit e
+prettyAppExp _ (LetFun fname (tparams, params, retdecl, rettype, e) body _) =
+  "let"
+    <+> prettyName fname
+    <+> hsep (map pretty tparams ++ map pretty params)
+      <> retdecl'
+    <+> equals
+    </> indent 2 (pretty e)
+    </> letBody body
+  where
+    retdecl' = case (pretty <$> unAnnot rettype) `mplus` (pretty <$> retdecl) of
+      Just rettype' -> colon <+> align rettype'
+      Nothing -> mempty
+prettyAppExp _ (LetWith dest src idxs ve body _)
+  | dest == src =
+      "let"
+        <+> pretty dest <> list (map pretty idxs)
+        <+> equals
+        <+> align (pretty ve)
+        </> letBody body
+  | otherwise =
+      "let"
+        <+> pretty dest
+        <+> equals
+        <+> pretty src
+        <+> "with"
+        <+> brackets (commasep (map pretty idxs))
+        <+> "="
+        <+> align (pretty ve)
+        </> letBody body
+prettyAppExp p (Range start maybe_step end _) =
+  parensIf (p /= -1) $
+    pretty start
+      <> maybe mempty ((".." <>) . pretty) maybe_step
+      <> case end of
+        DownToExclusive end' -> "..>" <> pretty end'
+        ToInclusive end' -> "..." <> pretty end'
+        UpToExclusive end' -> "..<" <> pretty end'
+prettyAppExp _ (If c t f _) =
+  "if"
+    <+> pretty c
+    </> "then"
+    <+> align (pretty t)
+    </> "else"
+    <+> align (pretty f)
+prettyAppExp p (Apply f arg _ _) =
+  parensIf (p >= 10) $ prettyExp 0 f <+> prettyExp 10 arg
 
+instance (Eq vn, IsName vn, Annot f) => Pretty (AppExpBase f vn) where
+  pretty = prettyAppExp (-1)
+
+prettyExp :: (Eq vn, IsName vn, Annot f) => Int -> ExpBase f vn -> Doc a
+prettyExp _ (Var name t _) = pretty name <> inst
+  where
+    inst = case unAnnot t of
+      Just t'
+        | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
+            "@" <> parens (align $ pretty t')
+      _ -> mempty
+prettyExp _ (Hole t _) = "???" <> inst
+  where
+    inst = case unAnnot t of
+      Just t'
+        | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
+            "@" <> parens (align $ pretty t')
+      _ -> mempty
+prettyExp _ (Parens e _) = align $ parens $ pretty e
+prettyExp _ (QualParens (v, _) e _) = pretty v <> "." <> align (parens $ pretty e)
+prettyExp p (Ascript e t _) =
+  parensIf (p /= -1) $ prettyExp 0 e <+> ":" <+> align (pretty t)
+prettyExp _ (Literal v _) = pretty v
+prettyExp _ (IntLit v _ _) = pretty v
+prettyExp _ (FloatLit v _ _) = pretty v
+prettyExp _ (TupLit es _)
+  | any hasArrayLit es = parens $ commastack $ map pretty es
+  | otherwise = parens $ commasep $ map pretty es
+prettyExp _ (RecordLit fs _)
+  | any fieldArray fs = braces $ commastack $ map pretty fs
+  | otherwise = braces $ commasep $ map pretty fs
+  where
+    fieldArray (RecordFieldExplicit _ e _) = hasArrayLit e
+    fieldArray RecordFieldImplicit {} = False
+prettyExp _ (ArrayLit es info _) =
+  brackets (commasep $ map pretty es) <> info'
+  where
+    info' = case unAnnot info of
+      Just t
+        | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
+            "@" <> parens (align $ pretty t)
+      _ -> mempty
+prettyExp _ (StringLit s _) =
+  pretty $ show $ map (chr . fromIntegral) s
+prettyExp _ (Project k e _ _) = pretty e <> "." <> pretty k
+prettyExp _ (Negate e _) = "-" <> pretty e
+prettyExp _ (Not e _) = "-" <> pretty e
+prettyExp _ (Update src idxs ve _) =
+  pretty src
+    <+> "with"
+    <+> brackets (commasep (map pretty idxs))
+    <+> "="
+    <+> align (pretty ve)
+prettyExp _ (RecordUpdate src fs ve _ _) =
+  pretty src
+    <+> "with"
+    <+> mconcat (intersperse "." (map pretty fs))
+    <+> "="
+    <+> align (pretty ve)
+prettyExp _ (Assert e1 e2 _ _) = "assert" <+> prettyExp 10 e1 <+> prettyExp 10 e2
+prettyExp p (Lambda params body rettype _ _) =
+  parensIf (p /= -1) $
+    "\\" <> hsep (map pretty params) <> ppAscription rettype
+      <+> "->"
+      </> indent 2 (pretty body)
+prettyExp _ (OpSection binop _ _) =
+  parens $ pretty binop
+prettyExp _ (OpSectionLeft binop _ x _ _ _) =
+  parens $ pretty x <+> ppBinOp binop
+prettyExp _ (OpSectionRight binop _ x _ _ _) =
+  parens $ ppBinOp binop <+> pretty x
+prettyExp _ (ProjectSection fields _ _) =
+  parens $ mconcat $ map p fields
+  where
+    p name = "." <> pretty name
+prettyExp _ (IndexSection idxs _ _) =
+  parens $ "." <> brackets (commasep (map pretty idxs))
+prettyExp _ (Constr n cs _ _) = "#" <> pretty n <+> sep (map pretty cs)
+prettyExp _ (Attr attr e _) =
+  "#[" <> pretty attr <> "]" </> prettyExp (-1) e
+prettyExp i (AppExp e _) = prettyAppExp i e
+
 instance (Eq vn, IsName vn, Annot f) => Pretty (ExpBase f vn) where
-  ppr = pprPrec (-1)
-  pprPrec _ (Var name t _) = ppr name <> inst
-    where
-      inst = case unAnnot t of
-        Just t'
-          | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
-              text "@" <> parens (align $ ppr t')
-        _ -> mempty
-  pprPrec _ (Hole t _) = "???" <> inst
-    where
-      inst = case unAnnot t of
-        Just t'
-          | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
-              text "@" <> parens (align $ ppr t')
-        _ -> mempty
-  pprPrec _ (Parens e _) = align $ parens $ ppr e
-  pprPrec _ (QualParens (v, _) e _) = ppr v <> text "." <> align (parens $ ppr e)
-  pprPrec p (Ascript e t _) =
-    parensIf (p /= -1) $ pprPrec 0 e <+> text ":" <+> align (pprPrec 0 t)
-  pprPrec _ (Literal v _) = ppr v
-  pprPrec _ (IntLit v _ _) = ppr v
-  pprPrec _ (FloatLit v _ _) = ppr v
-  pprPrec _ (TupLit es _)
-    | any hasArrayLit es = parens $ commastack $ map ppr es
-    | otherwise = parens $ commasep $ map ppr es
-  pprPrec _ (RecordLit fs _)
-    | any fieldArray fs = braces $ commastack $ map ppr fs
-    | otherwise = braces $ commasep $ map ppr fs
-    where
-      fieldArray (RecordFieldExplicit _ e _) = hasArrayLit e
-      fieldArray RecordFieldImplicit {} = False
-  pprPrec _ (ArrayLit es info _) =
-    brackets (commasep $ map ppr es) <> info'
-    where
-      info' = case unAnnot info of
-        Just t
-          | isEnvVarAtLeast "FUTHARK_COMPILER_DEBUGGING" 2 ->
-              text "@" <> parens (align $ ppr t)
-        _ -> mempty
-  pprPrec _ (StringLit s _) =
-    text $ show $ map (chr . fromIntegral) s
-  pprPrec _ (Project k e _ _) = ppr e <> text "." <> ppr k
-  pprPrec _ (Negate e _) = text "-" <> ppr e
-  pprPrec _ (Not e _) = text "-" <> ppr e
-  pprPrec _ (Update src idxs ve _) =
-    ppr src
-      <+> text "with"
-      <+> brackets (commasep (map ppr idxs))
-      <+> text "="
-      <+> align (ppr ve)
-  pprPrec _ (RecordUpdate src fs ve _ _) =
-    ppr src
-      <+> text "with"
-      <+> mconcat (intersperse (text ".") (map ppr fs))
-      <+> text "="
-      <+> align (ppr ve)
-  pprPrec _ (Assert e1 e2 _ _) = text "assert" <+> pprPrec 10 e1 <+> pprPrec 10 e2
-  pprPrec p (Lambda params body rettype _ _) =
-    parensIf (p /= -1) $
-      text "\\" <> spread (map ppr params) <> ppAscription rettype
-        <+> text "->"
-        </> indent 2 (ppr body)
-  pprPrec _ (OpSection binop _ _) =
-    parens $ ppr binop
-  pprPrec _ (OpSectionLeft binop _ x _ _ _) =
-    parens $ ppr x <+> ppBinOp binop
-  pprPrec _ (OpSectionRight binop _ x _ _ _) =
-    parens $ ppBinOp binop <+> ppr x
-  pprPrec _ (ProjectSection fields _ _) =
-    parens $ mconcat $ map p fields
-    where
-      p name = text "." <> ppr name
-  pprPrec _ (IndexSection idxs _ _) =
-    parens $ text "." <> brackets (commasep (map ppr idxs))
-  pprPrec _ (Constr n cs _ _) = text "#" <> ppr n <+> sep (map ppr cs)
-  pprPrec _ (Attr attr e _) =
-    text "#[" <> ppr attr <> text "]" </> pprPrec (-1) e
-  pprPrec i (AppExp e _) = pprPrec i e
+  pretty = prettyExp (-1)
 
 instance IsName vn => Pretty (AttrAtom vn) where
-  ppr (AtomName v) = ppr v
-  ppr (AtomInt x) = ppr x
+  pretty (AtomName v) = pretty v
+  pretty (AtomInt x) = pretty x
 
 instance IsName vn => Pretty (AttrInfo vn) where
-  ppr (AttrAtom attr _) = ppr attr
-  ppr (AttrComp f attrs _) = ppr f <> parens (commasep $ map ppr attrs)
+  pretty (AttrAtom attr _) = pretty attr
+  pretty (AttrComp f attrs _) = pretty f <> parens (commasep $ map pretty attrs)
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (FieldBase f vn) where
-  ppr (RecordFieldExplicit name e _) = ppr name <> equals <> ppr e
-  ppr (RecordFieldImplicit name _ _) = pprName name
+  pretty (RecordFieldExplicit name e _) = pretty name <> equals <> pretty e
+  pretty (RecordFieldImplicit name _ _) = prettyName name
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (CaseBase f vn) where
-  ppr (CasePat p e _) = text "case" <+> ppr p <+> text "->" </> indent 2 (ppr e)
+  pretty (CasePat p e _) = "case" <+> pretty p <+> "->" </> indent 2 (pretty e)
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (LoopFormBase f vn) where
-  ppr (For i ubound) =
-    text "for" <+> ppr i <+> text "<" <+> align (ppr ubound)
-  ppr (ForIn x e) =
-    text "for" <+> ppr x <+> text "in" <+> ppr e
-  ppr (While cond) =
-    text "while" <+> ppr cond
+  pretty (For i ubound) =
+    "for" <+> pretty i <+> "<" <+> align (pretty ubound)
+  pretty (ForIn x e) =
+    "for" <+> pretty x <+> "in" <+> pretty e
+  pretty (While cond) =
+    "while" <+> pretty cond
 
 instance Pretty PatLit where
-  ppr (PatLitInt x) = ppr x
-  ppr (PatLitFloat f) = ppr f
-  ppr (PatLitPrim v) = ppr v
+  pretty (PatLitInt x) = pretty x
+  pretty (PatLitFloat f) = pretty f
+  pretty (PatLitPrim v) = pretty v
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (PatBase f vn) where
-  ppr (PatAscription p t _) = ppr p <> colon <+> align (ppr t)
-  ppr (PatParens p _) = parens $ ppr p
-  ppr (Id v t _) = case unAnnot t of
-    Just t' -> parens $ pprName v <> colon <+> align (ppr t')
-    Nothing -> pprName v
-  ppr (TuplePat pats _) = parens $ commasep $ map ppr pats
-  ppr (RecordPat fs _) = braces $ commasep $ map ppField fs
+  pretty (PatAscription p t _) = pretty p <> colon <+> align (pretty t)
+  pretty (PatParens p _) = parens $ pretty p
+  pretty (Id v t _) = case unAnnot t of
+    Just t' -> parens $ prettyName v <> colon <+> align (pretty t')
+    Nothing -> prettyName v
+  pretty (TuplePat pats _) = parens $ commasep $ map pretty pats
+  pretty (RecordPat fs _) = braces $ commasep $ map ppField fs
     where
-      ppField (name, t) = text (nameToString name) <> equals <> ppr t
-  ppr (Wildcard t _) = case unAnnot t of
-    Just t' -> parens $ text "_" <> colon <+> ppr t'
-    Nothing -> text "_"
-  ppr (PatLit e _ _) = ppr e
-  ppr (PatConstr n _ ps _) = text "#" <> ppr n <+> sep (map ppr ps)
-  ppr (PatAttr attr p _) = text "#[" <> ppr attr <> text "]" <+/> ppr p
+      ppField (name, t) = pretty (nameToString name) <> equals <> pretty t
+  pretty (Wildcard t _) = case unAnnot t of
+    Just t' -> parens $ "_" <> colon <+> pretty t'
+    Nothing -> "_"
+  pretty (PatLit e _ _) = pretty e
+  pretty (PatConstr n _ ps _) = "#" <> pretty n <+> sep (map pretty ps)
+  pretty (PatAttr attr p _) = "#[" <> pretty attr <> "]" </> pretty p
 
-ppAscription :: Pretty t => Maybe t -> Doc
+ppAscription :: Pretty t => Maybe t -> Doc a
 ppAscription Nothing = mempty
-ppAscription (Just t) = colon <> align (ppr t)
+ppAscription (Just t) = colon <> align (pretty t)
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (ProgBase f vn) where
-  ppr = stack . punctuate line . map ppr . progDecs
+  pretty = stack . punctuate line . map pretty . progDecs
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (DecBase f vn) where
-  ppr (ValDec dec) = ppr dec
-  ppr (TypeDec dec) = ppr dec
-  ppr (SigDec sig) = ppr sig
-  ppr (ModDec sd) = ppr sd
-  ppr (OpenDec x _) = text "open" <+> ppr x
-  ppr (LocalDec dec _) = text "local" <+> ppr dec
-  ppr (ImportDec x _ _) = text "import" <+> ppr x
+  pretty (ValDec dec) = pretty dec
+  pretty (TypeDec dec) = pretty dec
+  pretty (SigDec sig) = pretty sig
+  pretty (ModDec sd) = pretty sd
+  pretty (OpenDec x _) = "open" <+> pretty x
+  pretty (LocalDec dec _) = "local" <+> pretty dec
+  pretty (ImportDec x _ _) = "import" <+> pretty x
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (ModExpBase f vn) where
-  ppr (ModVar v _) = ppr v
-  ppr (ModParens e _) = parens $ ppr e
-  ppr (ModImport v _ _) = text "import" <+> ppr (show v)
-  ppr (ModDecs ds _) = nestedBlock "{" "}" (stack $ punctuate line $ map ppr ds)
-  ppr (ModApply f a _ _ _) = parens $ ppr f <+> parens (ppr a)
-  ppr (ModAscript me se _ _) = ppr me <> colon <+> ppr se
-  ppr (ModLambda param maybe_sig body _) =
-    text "\\" <> ppr param <> maybe_sig'
-      <+> text "->"
-      </> indent 2 (ppr body)
+  pretty (ModVar v _) = pretty v
+  pretty (ModParens e _) = parens $ pretty e
+  pretty (ModImport v _ _) = "import" <+> pretty (show v)
+  pretty (ModDecs ds _) = nestedBlock "{" "}" (stack $ punctuate line $ map pretty ds)
+  pretty (ModApply f a _ _ _) = parens $ pretty f <+> parens (pretty a)
+  pretty (ModAscript me se _ _) = pretty me <> colon <+> pretty se
+  pretty (ModLambda param maybe_sig body _) =
+    "\\" <> pretty param <> maybe_sig'
+      <+> "->"
+      </> indent 2 (pretty body)
     where
       maybe_sig' = case maybe_sig of
         Nothing -> mempty
-        Just (sig, _) -> colon <+> ppr sig
+        Just (sig, _) -> colon <+> pretty sig
 
 instance Pretty Liftedness where
-  ppr Unlifted = text ""
-  ppr SizeLifted = text "~"
-  ppr Lifted = text "^"
+  pretty Unlifted = ""
+  pretty SizeLifted = "~"
+  pretty Lifted = "^"
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (TypeBindBase f vn) where
-  ppr (TypeBind name l params te rt _ _) =
-    text "type" <> ppr l
-      <+> pprName name
-      <+> spread (map ppr params)
+  pretty (TypeBind name l params te rt _ _) =
+    "type" <> pretty l
+      <+> prettyName name
+      <+> hsep (map pretty params)
       <+> equals
-      <+> maybe (ppr te) ppr (unAnnot rt)
+      <+> maybe (pretty te) pretty (unAnnot rt)
 
 instance (Eq vn, IsName vn) => Pretty (TypeParamBase vn) where
-  ppr (TypeParamDim name _) = brackets $ pprName name
-  ppr (TypeParamType l name _) = text "'" <> ppr l <> pprName name
+  pretty (TypeParamDim name _) = brackets $ prettyName name
+  pretty (TypeParamType l name _) = "'" <> pretty l <> prettyName name
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (ValBindBase f vn) where
-  ppr (ValBind entry name retdecl rettype tparams args body _ attrs _) =
-    mconcat (map ((<> line) . ppr) attrs)
-      <> text fun
-      <+> pprName name
-      <+> align (sep (map ppr tparams ++ map ppr args))
-        <> retdecl'
-        <> text " ="
-      </> indent 2 (ppr body)
+  pretty (ValBind entry name retdecl rettype tparams args body _ attrs _) =
+    mconcat (map ((<> line) . pretty) attrs)
+      <> fun
+      <+> align
+        ( sep
+            ( prettyName name
+                : map pretty tparams
+                ++ map pretty args
+                ++ retdecl'
+                ++ ["="]
+            )
+        )
+      </> indent 2 (pretty body)
     where
       fun
         | isJust entry = "entry"
         | otherwise = "def"
-      retdecl' = case (ppr <$> unAnnot rettype) `mplus` (ppr <$> retdecl) of
-        Just rettype' -> colon <+> align rettype'
+      retdecl' = case (pretty <$> unAnnot rettype) `mplus` (pretty <$> retdecl) of
+        Just rettype' -> [colon <+> align rettype']
         Nothing -> mempty
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (SpecBase f vn) where
-  ppr (TypeAbbrSpec tpsig) = ppr tpsig
-  ppr (TypeSpec l name ps _ _) =
-    text "type" <> ppr l <+> pprName name <+> spread (map ppr ps)
-  ppr (ValSpec name tparams vtype _ _ _) =
-    text "val" <+> pprName name <+> spread (map ppr tparams) <> colon <+> ppr vtype
-  ppr (ModSpec name sig _ _) =
-    text "module" <+> pprName name <> colon <+> ppr sig
-  ppr (IncludeSpec e _) =
-    text "include" <+> ppr e
+  pretty (TypeAbbrSpec tpsig) = pretty tpsig
+  pretty (TypeSpec l name ps _ _) =
+    "type" <> pretty l <+> prettyName name <+> hsep (map pretty ps)
+  pretty (ValSpec name tparams vtype _ _ _) =
+    "val" <+> prettyName name <+> hsep (map pretty tparams) <> colon <+> pretty vtype
+  pretty (ModSpec name sig _ _) =
+    "module" <+> prettyName name <> colon <+> pretty sig
+  pretty (IncludeSpec e _) =
+    "include" <+> pretty e
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (SigExpBase f vn) where
-  ppr (SigVar v _ _) = ppr v
-  ppr (SigParens e _) = parens $ ppr e
-  ppr (SigSpecs ss _) = nestedBlock "{" "}" (stack $ punctuate line $ map ppr ss)
-  ppr (SigWith s (TypeRef v ps td _) _) =
-    ppr s <+> text "with" <+> ppr v <+> spread (map ppr ps) <> text " =" <+> ppr td
-  ppr (SigArrow (Just v) e1 e2 _) =
-    parens (pprName v <> colon <+> ppr e1) <+> text "->" <+> ppr e2
-  ppr (SigArrow Nothing e1 e2 _) =
-    ppr e1 <+> text "->" <+> ppr e2
+  pretty (SigVar v _ _) = pretty v
+  pretty (SigParens e _) = parens $ pretty e
+  pretty (SigSpecs ss _) = nestedBlock "{" "}" (stack $ punctuate line $ map pretty ss)
+  pretty (SigWith s (TypeRef v ps td _) _) =
+    pretty s <+> "with" <+> pretty v <+> hsep (map pretty ps) <> " =" <+> pretty td
+  pretty (SigArrow (Just v) e1 e2 _) =
+    parens (prettyName v <> colon <+> pretty e1) <+> "->" <+> pretty e2
+  pretty (SigArrow Nothing e1 e2 _) =
+    pretty e1 <+> "->" <+> pretty e2
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (SigBindBase f vn) where
-  ppr (SigBind name e _ _) =
-    text "module type" <+> pprName name <+> equals <+> ppr e
+  pretty (SigBind name e _ _) =
+    "module type" <+> prettyName name <+> equals <+> pretty e
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (ModParamBase f vn) where
-  ppr (ModParam pname psig _ _) =
-    parens (pprName pname <> colon <+> ppr psig)
+  pretty (ModParam pname psig _ _) =
+    parens (prettyName pname <> colon <+> pretty psig)
 
 instance (Eq vn, IsName vn, Annot f) => Pretty (ModBindBase f vn) where
-  ppr (ModBind name ps sig e _ _) =
-    text "module" <+> pprName name <+> spread (map ppr ps) <+> sig' <> text " =" <+> ppr e
+  pretty (ModBind name ps sig e _ _) =
+    "module" <+> prettyName name <+> hsep (map pretty ps) <+> sig' <> " =" <+> pretty e
     where
       sig' = case sig of
         Nothing -> mempty
-        Just (s, _) -> colon <+> ppr s <> text " "
+        Just (s, _) -> colon <+> pretty s <> " "
 
-ppBinOp :: IsName v => QualName v -> Doc
+ppBinOp :: IsName v => QualName v -> Doc a
 ppBinOp bop =
   case leading of
-    Backtick -> text "`" <> ppr bop <> text "`"
-    _ -> ppr bop
+    Backtick -> "`" <> pretty bop <> "`"
+    _ -> pretty bop
   where
-    leading =
-      leadingOperator $ nameFromString $ pretty $ pprName $ qualLeaf bop
+    leading = leadingOperator $ toName $ qualLeaf bop
 
 prettyBinOp ::
   (Eq vn, IsName vn, Annot f) =>
@@ -549,17 +551,17 @@
   QualName vn ->
   ExpBase f vn ->
   ExpBase f vn ->
-  Doc
+  Doc a
 prettyBinOp p bop x y =
   parensIf (p > symPrecedence) $
-    pprPrec symPrecedence x
-      <+/> bop'
-      <+> pprPrec symRPrecedence y
+    prettyExp symPrecedence x
+      <+> bop'
+      <+> prettyExp symRPrecedence y
   where
     bop' = case leading of
-      Backtick -> text "`" <> ppr bop <> text "`"
-      _ -> ppr bop
-    leading = leadingOperator $ nameFromString $ pretty $ pprName $ qualLeaf bop
+      Backtick -> "`" <> pretty bop <> "`"
+      _ -> pretty bop
+    leading = leadingOperator $ toName $ qualLeaf bop
     symPrecedence = precedence leading
     symRPrecedence = rprecedence leading
     precedence PipeRight = -1
diff --git a/src/Language/Futhark/Primitive.hs b/src/Language/Futhark/Primitive.hs
--- a/src/Language/Futhark/Primitive.hs
+++ b/src/Language/Futhark/Primitive.hs
@@ -1,6 +1,4 @@
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
 
 -- | Definitions of primitive types, the values that inhabit these
 -- types, and operations on these values.  A primitive value can also
@@ -114,8 +112,8 @@
 where
 
 import Control.Category
-import qualified Data.Binary.Get as G
-import qualified Data.Binary.Put as P
+import Data.Binary.Get qualified as G
+import Data.Binary.Put qualified as P
 import Data.Bits
   ( complement,
     countLeadingZeros,
@@ -129,30 +127,11 @@
   )
 import Data.Fixed (mod') -- Weird location.
 import Data.Int (Int16, Int32, Int64, Int8)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Word (Word16, Word32, Word64, Word8)
 import Foreign.C.Types (CUShort (..))
-import Futhark.Util
-  ( cbrt,
-    cbrtf,
-    ceilDouble,
-    ceilFloat,
-    convFloat,
-    erf,
-    erfc,
-    erfcf,
-    erff,
-    floorDouble,
-    floorFloat,
-    hypot,
-    hypotf,
-    lgamma,
-    lgammaf,
-    roundDouble,
-    roundFloat,
-    tgamma,
-    tgammaf,
-  )
+import Futhark.Util (convFloat)
+import Futhark.Util.CMath
 import Futhark.Util.Pretty
 import Numeric.Half
 import Prelude hiding (id, (.))
@@ -168,10 +147,10 @@
   deriving (Eq, Ord, Show, Enum, Bounded)
 
 instance Pretty IntType where
-  ppr Int8 = text "i8"
-  ppr Int16 = text "i16"
-  ppr Int32 = text "i32"
-  ppr Int64 = text "i64"
+  pretty Int8 = "i8"
+  pretty Int16 = "i16"
+  pretty Int32 = "i32"
+  pretty Int64 = "i64"
 
 -- | A list of all integer types.
 allIntTypes :: [IntType]
@@ -185,9 +164,9 @@
   deriving (Eq, Ord, Show, Enum, Bounded)
 
 instance Pretty FloatType where
-  ppr Float16 = text "f16"
-  ppr Float32 = text "f32"
-  ppr Float64 = text "f64"
+  pretty Float16 = "f16"
+  pretty Float32 = "f32"
+  pretty Float64 = "f64"
 
 -- | A list of all floating-point types.
 allFloatTypes :: [FloatType]
@@ -228,10 +207,10 @@
   maxBound = Unit
 
 instance Pretty PrimType where
-  ppr (IntType t) = ppr t
-  ppr (FloatType t) = ppr t
-  ppr Bool = text "bool"
-  ppr Unit = text "unit"
+  pretty (IntType t) = pretty t
+  pretty (FloatType t) = pretty t
+  pretty Bool = "bool"
+  pretty Unit = "unit"
 
 -- | A list of all primitive types.
 allPrimTypes :: [PrimType]
@@ -249,10 +228,10 @@
   deriving (Eq, Ord, Show)
 
 instance Pretty IntValue where
-  ppr (Int8Value v) = text $ show v ++ "i8"
-  ppr (Int16Value v) = text $ show v ++ "i16"
-  ppr (Int32Value v) = text $ show v ++ "i32"
-  ppr (Int64Value v) = text $ show v ++ "i64"
+  pretty (Int8Value v) = pretty $ show v ++ "i8"
+  pretty (Int16Value v) = pretty $ show v ++ "i16"
+  pretty (Int32Value v) = pretty $ show v ++ "i32"
+  pretty (Int64Value v) = pretty $ show v ++ "i64"
 
 -- | Create an t'IntValue' from a type and an 'Integer'.
 intValue :: Integral int => IntType -> int -> IntValue
@@ -314,21 +293,21 @@
   (>=) = flip (<=)
 
 instance Pretty FloatValue where
-  ppr (Float16Value v)
-    | isInfinite v, v >= 0 = text "f16.inf"
-    | isInfinite v, v < 0 = text "-f16.inf"
-    | isNaN v = text "f16.nan"
-    | otherwise = text $ show v ++ "f16"
-  ppr (Float32Value v)
-    | isInfinite v, v >= 0 = text "f32.inf"
-    | isInfinite v, v < 0 = text "-f32.inf"
-    | isNaN v = text "f32.nan"
-    | otherwise = text $ show v ++ "f32"
-  ppr (Float64Value v)
-    | isInfinite v, v >= 0 = text "f64.inf"
-    | isInfinite v, v < 0 = text "-f64.inf"
-    | isNaN v = text "f64.nan"
-    | otherwise = text $ show v ++ "f64"
+  pretty (Float16Value v)
+    | isInfinite v, v >= 0 = "f16.inf"
+    | isInfinite v, v < 0 = "-f16.inf"
+    | isNaN v = "f16.nan"
+    | otherwise = pretty $ show v ++ "f16"
+  pretty (Float32Value v)
+    | isInfinite v, v >= 0 = "f32.inf"
+    | isInfinite v, v < 0 = "-f32.inf"
+    | isNaN v = "f32.nan"
+    | otherwise = pretty $ show v ++ "f32"
+  pretty (Float64Value v)
+    | isInfinite v, v >= 0 = "f64.inf"
+    | isInfinite v, v < 0 = "-f64.inf"
+    | isNaN v = "f64.nan"
+    | otherwise = pretty $ show v ++ "f64"
 
 -- | Create a t'FloatValue' from a type and a 'Rational'.
 floatValue :: Real num => FloatType -> num -> FloatValue
@@ -352,11 +331,11 @@
   deriving (Eq, Ord, Show)
 
 instance Pretty PrimValue where
-  ppr (IntValue v) = ppr v
-  ppr (BoolValue True) = text "true"
-  ppr (BoolValue False) = text "false"
-  ppr (FloatValue v) = ppr v
-  ppr UnitValue = text "()"
+  pretty (IntValue v) = pretty v
+  pretty (BoolValue True) = "true"
+  pretty (BoolValue False) = "false"
+  pretty (FloatValue v) = pretty v
+  pretty UnitValue = "()"
 
 -- | The type of a basic value.
 primValueType :: PrimValue -> PrimType
@@ -1268,6 +1247,10 @@
       f32 "floor32" floorFloat,
       f64 "floor64" floorDouble,
       --
+      f16_2 "nextafter16" (\x y -> convFloat $ nextafterf (convFloat x) (convFloat y)),
+      f32_2 "nextafter32" nextafterf,
+      f64_2 "nextafter64" nextafter,
+      --
       f16 "gamma16" $ convFloat . tgammaf . convFloat,
       f32 "gamma32" tgammaf,
       f64 "gamma64" tgamma,
@@ -1545,6 +1528,27 @@
     f16 s f = (s, ([FloatType Float16], FloatType Float16, f16PrimFun f))
     f32 s f = (s, ([FloatType Float32], FloatType Float32, f32PrimFun f))
     f64 s f = (s, ([FloatType Float64], FloatType Float64, f64PrimFun f))
+    f16_2 s f =
+      ( s,
+        ( [FloatType Float16, FloatType Float16],
+          FloatType Float16,
+          f16PrimFun2 f
+        )
+      )
+    f32_2 s f =
+      ( s,
+        ( [FloatType Float32, FloatType Float32],
+          FloatType Float32,
+          f32PrimFun2 f
+        )
+      )
+    f64_2 s f =
+      ( s,
+        ( [FloatType Float64, FloatType Float64],
+          FloatType Float64,
+          f64PrimFun2 f
+        )
+      )
     f16_3 s f =
       ( s,
         ( [FloatType Float16, FloatType Float16, FloatType Float16],
@@ -1595,6 +1599,30 @@
       Just $ FloatValue $ Float64Value $ f x
     f64PrimFun _ _ = Nothing
 
+    f16PrimFun2
+      f
+      [ FloatValue (Float16Value a),
+        FloatValue (Float16Value b)
+        ] =
+        Just $ FloatValue $ Float16Value $ f a b
+    f16PrimFun2 _ _ = Nothing
+
+    f32PrimFun2
+      f
+      [ FloatValue (Float32Value a),
+        FloatValue (Float32Value b)
+        ] =
+        Just $ FloatValue $ Float32Value $ f a b
+    f32PrimFun2 _ _ = Nothing
+
+    f64PrimFun2
+      f
+      [ FloatValue (Float64Value a),
+        FloatValue (Float64Value b)
+        ] =
+        Just $ FloatValue $ Float64Value $ f a b
+    f64PrimFun2 _ _ = Nothing
+
     f16PrimFun3
       f
       [ FloatValue (Float16Value a),
@@ -1733,74 +1761,74 @@
 -- Prettyprinting instances
 
 instance Pretty BinOp where
-  ppr (Add t OverflowWrap) = taggedI "add" t
-  ppr (Add t OverflowUndef) = taggedI "add_nw" t
-  ppr (Sub t OverflowWrap) = taggedI "sub" t
-  ppr (Sub t OverflowUndef) = taggedI "sub_nw" t
-  ppr (Mul t OverflowWrap) = taggedI "mul" t
-  ppr (Mul t OverflowUndef) = taggedI "mul_nw" t
-  ppr (FAdd t) = taggedF "fadd" t
-  ppr (FSub t) = taggedF "fsub" t
-  ppr (FMul t) = taggedF "fmul" t
-  ppr (UDiv t Safe) = taggedI "udiv_safe" t
-  ppr (UDiv t Unsafe) = taggedI "udiv" t
-  ppr (UDivUp t Safe) = taggedI "udiv_up_safe" t
-  ppr (UDivUp t Unsafe) = taggedI "udiv_up" t
-  ppr (UMod t Safe) = taggedI "umod_safe" t
-  ppr (UMod t Unsafe) = taggedI "umod" t
-  ppr (SDiv t Safe) = taggedI "sdiv_safe" t
-  ppr (SDiv t Unsafe) = taggedI "sdiv" t
-  ppr (SDivUp t Safe) = taggedI "sdiv_up_safe" t
-  ppr (SDivUp t Unsafe) = taggedI "sdiv_up" t
-  ppr (SMod t Safe) = taggedI "smod_safe" t
-  ppr (SMod t Unsafe) = taggedI "smod" t
-  ppr (SQuot t Safe) = taggedI "squot_safe" t
-  ppr (SQuot t Unsafe) = taggedI "squot" t
-  ppr (SRem t Safe) = taggedI "srem_safe" t
-  ppr (SRem t Unsafe) = taggedI "srem" t
-  ppr (FDiv t) = taggedF "fdiv" t
-  ppr (FMod t) = taggedF "fmod" t
-  ppr (SMin t) = taggedI "smin" t
-  ppr (UMin t) = taggedI "umin" t
-  ppr (FMin t) = taggedF "fmin" t
-  ppr (SMax t) = taggedI "smax" t
-  ppr (UMax t) = taggedI "umax" t
-  ppr (FMax t) = taggedF "fmax" t
-  ppr (Shl t) = taggedI "shl" t
-  ppr (LShr t) = taggedI "lshr" t
-  ppr (AShr t) = taggedI "ashr" t
-  ppr (And t) = taggedI "and" t
-  ppr (Or t) = taggedI "or" t
-  ppr (Xor t) = taggedI "xor" t
-  ppr (Pow t) = taggedI "pow" t
-  ppr (FPow t) = taggedF "fpow" t
-  ppr LogAnd = text "logand"
-  ppr LogOr = text "logor"
+  pretty (Add t OverflowWrap) = taggedI "add" t
+  pretty (Add t OverflowUndef) = taggedI "add_nw" t
+  pretty (Sub t OverflowWrap) = taggedI "sub" t
+  pretty (Sub t OverflowUndef) = taggedI "sub_nw" t
+  pretty (Mul t OverflowWrap) = taggedI "mul" t
+  pretty (Mul t OverflowUndef) = taggedI "mul_nw" t
+  pretty (FAdd t) = taggedF "fadd" t
+  pretty (FSub t) = taggedF "fsub" t
+  pretty (FMul t) = taggedF "fmul" t
+  pretty (UDiv t Safe) = taggedI "udiv_safe" t
+  pretty (UDiv t Unsafe) = taggedI "udiv" t
+  pretty (UDivUp t Safe) = taggedI "udiv_up_safe" t
+  pretty (UDivUp t Unsafe) = taggedI "udiv_up" t
+  pretty (UMod t Safe) = taggedI "umod_safe" t
+  pretty (UMod t Unsafe) = taggedI "umod" t
+  pretty (SDiv t Safe) = taggedI "sdiv_safe" t
+  pretty (SDiv t Unsafe) = taggedI "sdiv" t
+  pretty (SDivUp t Safe) = taggedI "sdiv_up_safe" t
+  pretty (SDivUp t Unsafe) = taggedI "sdiv_up" t
+  pretty (SMod t Safe) = taggedI "smod_safe" t
+  pretty (SMod t Unsafe) = taggedI "smod" t
+  pretty (SQuot t Safe) = taggedI "squot_safe" t
+  pretty (SQuot t Unsafe) = taggedI "squot" t
+  pretty (SRem t Safe) = taggedI "srem_safe" t
+  pretty (SRem t Unsafe) = taggedI "srem" t
+  pretty (FDiv t) = taggedF "fdiv" t
+  pretty (FMod t) = taggedF "fmod" t
+  pretty (SMin t) = taggedI "smin" t
+  pretty (UMin t) = taggedI "umin" t
+  pretty (FMin t) = taggedF "fmin" t
+  pretty (SMax t) = taggedI "smax" t
+  pretty (UMax t) = taggedI "umax" t
+  pretty (FMax t) = taggedF "fmax" t
+  pretty (Shl t) = taggedI "shl" t
+  pretty (LShr t) = taggedI "lshr" t
+  pretty (AShr t) = taggedI "ashr" t
+  pretty (And t) = taggedI "and" t
+  pretty (Or t) = taggedI "or" t
+  pretty (Xor t) = taggedI "xor" t
+  pretty (Pow t) = taggedI "pow" t
+  pretty (FPow t) = taggedF "fpow" t
+  pretty LogAnd = "logand"
+  pretty LogOr = "logor"
 
 instance Pretty CmpOp where
-  ppr (CmpEq t) = text "eq_" <> ppr t
-  ppr (CmpUlt t) = taggedI "ult" t
-  ppr (CmpUle t) = taggedI "ule" t
-  ppr (CmpSlt t) = taggedI "slt" t
-  ppr (CmpSle t) = taggedI "sle" t
-  ppr (FCmpLt t) = taggedF "lt" t
-  ppr (FCmpLe t) = taggedF "le" t
-  ppr CmpLlt = text "llt"
-  ppr CmpLle = text "lle"
+  pretty (CmpEq t) = "eq_" <> pretty t
+  pretty (CmpUlt t) = taggedI "ult" t
+  pretty (CmpUle t) = taggedI "ule" t
+  pretty (CmpSlt t) = taggedI "slt" t
+  pretty (CmpSle t) = taggedI "sle" t
+  pretty (FCmpLt t) = taggedF "lt" t
+  pretty (FCmpLe t) = taggedF "le" t
+  pretty CmpLlt = "llt"
+  pretty CmpLle = "lle"
 
 instance Pretty ConvOp where
-  ppr op = convOp (convOpFun op) from to
+  pretty op = convOp (convOpFun op) from to
     where
       (from, to) = convOpType op
 
 instance Pretty UnOp where
-  ppr Not = text "not"
-  ppr (Abs t) = taggedI "abs" t
-  ppr (FAbs t) = taggedF "fabs" t
-  ppr (SSignum t) = taggedI "ssignum" t
-  ppr (USignum t) = taggedI "usignum" t
-  ppr (FSignum t) = taggedF "fsignum" t
-  ppr (Complement t) = taggedI "complement" t
+  pretty Not = "not"
+  pretty (Abs t) = taggedI "abs" t
+  pretty (FAbs t) = taggedF "fabs" t
+  pretty (SSignum t) = taggedI "ssignum" t
+  pretty (USignum t) = taggedI "usignum" t
+  pretty (FSignum t) = taggedF "fsignum" t
+  pretty (Complement t) = taggedI "complement" t
 
 -- | The human-readable name for a 'ConvOp'.  This is used to expose
 -- the 'ConvOp' in the @intrinsics@ module of a Futhark program.
@@ -1817,24 +1845,24 @@
 convOpFun FToB {} = "ftob"
 convOpFun BToF {} = "btof"
 
-taggedI :: String -> IntType -> Doc
-taggedI s Int8 = text $ s ++ "8"
-taggedI s Int16 = text $ s ++ "16"
-taggedI s Int32 = text $ s ++ "32"
-taggedI s Int64 = text $ s ++ "64"
+taggedI :: String -> IntType -> Doc a
+taggedI s Int8 = pretty $ s ++ "8"
+taggedI s Int16 = pretty $ s ++ "16"
+taggedI s Int32 = pretty $ s ++ "32"
+taggedI s Int64 = pretty $ s ++ "64"
 
-taggedF :: String -> FloatType -> Doc
-taggedF s Float16 = text $ s ++ "16"
-taggedF s Float32 = text $ s ++ "32"
-taggedF s Float64 = text $ s ++ "64"
+taggedF :: String -> FloatType -> Doc a
+taggedF s Float16 = pretty $ s ++ "16"
+taggedF s Float32 = pretty $ s ++ "32"
+taggedF s Float64 = pretty $ s ++ "64"
 
-convOp :: (Pretty from, Pretty to) => String -> from -> to -> Doc
-convOp s from to = text s <> text "_" <> ppr from <> text "_" <> ppr to
+convOp :: (Pretty from, Pretty to) => String -> from -> to -> Doc a
+convOp s from to = pretty s <> "_" <> pretty from <> "_" <> pretty to
 
 -- | True if signed.  Only makes a difference for integer types.
 prettySigned :: Bool -> PrimType -> String
-prettySigned True (IntType it) = 'u' : drop 1 (pretty it)
-prettySigned _ t = pretty t
+prettySigned True (IntType it) = 'u' : drop 1 (prettyString it)
+prettySigned _ t = prettyString t
 
 mul_hi8 :: IntValue -> IntValue -> Int8
 mul_hi8 a b =
diff --git a/src/Language/Futhark/Primitive/Parse.hs b/src/Language/Futhark/Primitive/Parse.hs
--- a/src/Language/Futhark/Primitive/Parse.hs
+++ b/src/Language/Futhark/Primitive/Parse.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Parsers for primitive values and types.
 module Language.Futhark.Primitive.Parse
   ( pPrimValue,
@@ -17,13 +15,13 @@
 
 import Data.Char (isAlphaNum)
 import Data.Functor
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Void
-import Futhark.Util.Pretty hiding (empty)
+import Futhark.Util.Pretty
 import Language.Futhark.Primitive
 import Text.Megaparsec
 import Text.Megaparsec.Char
-import qualified Text.Megaparsec.Char.Lexer as L
+import Text.Megaparsec.Char.Lexer qualified as L
 
 -- | Is this character a valid member of an identifier?
 constituent :: Char -> Bool
diff --git a/src/Language/Futhark/Prop.hs b/src/Language/Futhark/Prop.hs
--- a/src/Language/Futhark/Prop.hs
+++ b/src/Language/Futhark/Prop.hs
@@ -1,8 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
 -- | This module provides various simple ways to query and manipulate
 -- fundamental Futhark terms, such as types and values.  The intent is to
 -- keep "Futhark.Language.Syntax" simple, and put whatever embellishments
@@ -17,7 +12,6 @@
     namesToPrimTypes,
     qualName,
     qualify,
-    valueType,
     primValueType,
     leadingOperator,
     progImports,
@@ -109,13 +103,13 @@
 import Data.Foldable
 import Data.List (genericLength, isPrefixOf, sortOn)
 import Data.Loc (Loc (..), posFile)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Ord
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Util (maxinum)
 import Futhark.Util.Pretty
-import qualified Language.Futhark.Primitive as Primitive
+import Language.Futhark.Primitive qualified as Primitive
 import Language.Futhark.Syntax
 import Language.Futhark.Traversals
 import Language.Futhark.Tuple
@@ -453,11 +447,6 @@
 primValueType (FloatValue v) = FloatType $ floatValueType v
 primValueType BoolValue {} = Bool
 
--- | The type of the value.
-valueType :: Value -> ValueType
-valueType (PrimValue bv) = Scalar $ Prim $ primValueType bv
-valueType (ArrayValue _ t) = t
-
 -- | The type of an Futhark term.  The aliasing will refer to itself, if
 -- the term is a non-tuple-typed variable.
 typeOf :: ExpBase Info VName -> PatType
@@ -666,7 +655,7 @@
 namesToPrimTypes :: M.Map Name PrimType
 namesToPrimTypes =
   M.fromList
-    [ (nameFromString $ pretty t, t)
+    [ (nameFromString $ prettyString t, t)
       | t <-
           Bool
             : map Signed [minBound .. maxBound]
@@ -1112,32 +1101,32 @@
     primFun (name, (ts, t, _)) =
       (name, IntrinsicMonoFun (map unPrim ts) $ unPrim t)
 
-    unOpFun bop = (pretty bop, IntrinsicMonoFun [t] t)
+    unOpFun bop = (prettyString bop, IntrinsicMonoFun [t] t)
       where
         t = unPrim $ Primitive.unOpType bop
 
-    binOpFun bop = (pretty bop, IntrinsicMonoFun [t, t] t)
+    binOpFun bop = (prettyString bop, IntrinsicMonoFun [t, t] t)
       where
         t = unPrim $ Primitive.binOpType bop
 
-    cmpOpFun bop = (pretty bop, IntrinsicMonoFun [t, t] Bool)
+    cmpOpFun bop = (prettyString bop, IntrinsicMonoFun [t, t] Bool)
       where
         t = unPrim $ Primitive.cmpOpType bop
 
-    convOpFun cop = (pretty cop, IntrinsicMonoFun [unPrim ft] $ unPrim tt)
+    convOpFun cop = (prettyString cop, IntrinsicMonoFun [unPrim ft] $ unPrim tt)
       where
         (ft, tt) = Primitive.convOpType cop
 
-    signFun t = ("sign_" ++ pretty t, IntrinsicMonoFun [Unsigned t] $ Signed t)
+    signFun t = ("sign_" ++ prettyString t, IntrinsicMonoFun [Unsigned t] $ Signed t)
 
-    unsignFun t = ("unsign_" ++ pretty t, IntrinsicMonoFun [Signed t] $ Unsigned t)
+    unsignFun t = ("unsign_" ++ prettyString t, IntrinsicMonoFun [Signed t] $ Unsigned t)
 
     unPrim (Primitive.IntType t) = Signed t
     unPrim (Primitive.FloatType t) = FloatType t
     unPrim Primitive.Bool = Bool
     unPrim Primitive.Unit = Bool
 
-    intrinsicPrim t = (pretty t, IntrinsicType Unlifted [] $ Scalar $ Prim t)
+    intrinsicPrim t = (prettyString t, IntrinsicType Unlifted [] $ Scalar $ Prim t)
 
     anyIntType =
       map Signed [minBound .. maxBound]
@@ -1150,7 +1139,7 @@
     mkIntrinsicBinOp :: BinOp -> Maybe (String, Intrinsic)
     mkIntrinsicBinOp op = do
       op' <- intrinsicBinOp op
-      pure (pretty op, op')
+      pure (prettyString op, op')
 
     binOp ts = Just $ IntrinsicOverloadedFun ts [Nothing, Nothing] Nothing
     ordering = Just $ IntrinsicOverloadedFun anyPrimType [Nothing, Nothing] (Just Bool)
@@ -1313,7 +1302,7 @@
   maybe Backtick snd $
     find ((`isPrefixOf` s') . fst) $
       sortOn (Down . length . fst) $
-        zip (map pretty operators) operators
+        zip (map prettyString operators) operators
   where
     s' = nameToString s
     operators :: [BinOp]
diff --git a/src/Language/Futhark/Query.hs b/src/Language/Futhark/Query.hs
--- a/src/Language/Futhark/Query.hs
+++ b/src/Language/Futhark/Query.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 -- | Facilities for answering queries about a program, such as "what
 -- appears at this source location", or "where is this name bound".
 -- The intent is that this is used as a building block for IDE-like
@@ -16,12 +14,12 @@
 import Control.Monad
 import Control.Monad.State
 import Data.List (find)
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.Util.Loc (Loc (..), Pos (..))
 import Language.Futhark
 import Language.Futhark.Semantic
 import Language.Futhark.Traversals
-import qualified System.FilePath.Posix as Posix
+import System.FilePath.Posix qualified as Posix
 
 -- | What a name is bound to.
 data BoundTo
diff --git a/src/Language/Futhark/Semantic.hs b/src/Language/Futhark/Semantic.hs
--- a/src/Language/Futhark/Semantic.hs
+++ b/src/Language/Futhark/Semantic.hs
@@ -6,6 +6,7 @@
     mkImportFrom,
     includeToFilePath,
     includeToString,
+    includeToText,
     FileModule (..),
     Imports,
     Namespace (..),
@@ -20,13 +21,14 @@
   )
 where
 
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
+import Data.Text qualified as T
 import Futhark.Util (dropLast, fromPOSIX, toPOSIX)
 import Futhark.Util.Loc
 import Futhark.Util.Pretty
 import Language.Futhark
-import qualified System.FilePath as Native
-import qualified System.FilePath.Posix as Posix
+import System.FilePath qualified as Native
+import System.FilePath.Posix qualified as Posix
 import Prelude hiding (mod)
 
 -- | Canonical reference to a Futhark code file.  Does not include the
@@ -68,6 +70,11 @@
 includeToString :: ImportName -> String
 includeToString (ImportName s _) = Posix.normalise s
 
+-- | Produce a human-readable canonicalized text from an
+-- 'ImportName'.
+includeToText :: ImportName -> T.Text
+includeToText (ImportName s _) = T.pack $ Posix.normalise s
+
 -- | The result of type checking some file.  Can be passed to further
 -- invocations of the type checker.
 data FileModule = FileModule
@@ -147,22 +154,22 @@
     Env (vt1 <> vt2) (tt1 <> tt2) (st1 <> st2) (mt1 <> mt2) (nt1 <> nt2)
 
 instance Pretty Namespace where
-  ppr Term = text "name"
-  ppr Type = text "type"
-  ppr Signature = text "module type"
+  pretty Term = "name"
+  pretty Type = "type"
+  pretty Signature = "module type"
 
 instance Monoid Env where
   mempty = Env mempty mempty mempty mempty mempty
 
 instance Pretty MTy where
-  ppr = ppr . mtyMod
+  pretty = pretty . mtyMod
 
 instance Pretty Mod where
-  ppr (ModEnv e) = ppr e
-  ppr (ModFun (FunSig _ mod mty)) = ppr mod <+> text "->" </> ppr mty
+  pretty (ModEnv e) = pretty e
+  pretty (ModFun (FunSig _ mod mty)) = pretty mod <+> "->" </> pretty mty
 
 instance Pretty Env where
-  ppr (Env vtable ttable sigtable modtable _) =
+  pretty (Env vtable ttable sigtable modtable _) =
     nestedBlock "{" "}" $
       stack $
         punctuate line $
@@ -175,21 +182,21 @@
     where
       renderTypeBind (name, TypeAbbr l tps tp) =
         p l
-          <+> pprName name
-            <> mconcat (map ((text " " <>) . ppr) tps)
-            <> text " ="
-          <+> ppr tp
+          <+> prettyName name
+            <> mconcat (map ((" " <>) . pretty) tps)
+            <> " ="
+          <+> pretty tp
         where
-          p Lifted = text "type^"
-          p SizeLifted = text "type~"
-          p Unlifted = text "type"
+          p Lifted = "type^"
+          p SizeLifted = "type~"
+          p Unlifted = "type"
       renderValBind (name, BoundV tps t) =
-        text "val"
-          <+> pprName name
-            <> mconcat (map ((text " " <>) . ppr) tps)
-            <> text " ="
-          <+> ppr t
+        "val"
+          <+> prettyName name
+            <> mconcat (map ((" " <>) . pretty) tps)
+            <> " ="
+          <+> pretty t
       renderModType (name, _sig) =
-        text "module type" <+> pprName name
+        "module type" <+> prettyName name
       renderMod (name, mod) =
-        text "module" <+> pprName name <> text " =" <+> ppr mod
+        "module" <+> prettyName name <> " =" <+> pretty mod
diff --git a/src/Language/Futhark/Syntax.hs b/src/Language/Futhark/Syntax.hs
--- a/src/Language/Futhark/Syntax.hs
+++ b/src/Language/Futhark/Syntax.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE Strict #-}
 
 -- | The Futhark source language AST definition.  Many types, such as
@@ -15,7 +11,7 @@
 -- "Language.Futhark.Primitive".
 module Language.Futhark.Syntax
   ( module Language.Futhark.Core,
-    pretty,
+    prettyString,
     prettyText,
 
     -- * Types
@@ -47,7 +43,6 @@
     FloatValue (..),
     PrimValue (..),
     IsPrimValue (..),
-    Value (..),
 
     -- * Abstract syntax tree
     AttrInfo (..),
@@ -100,16 +95,16 @@
 
 import Control.Applicative
 import Control.Monad
-import Data.Array
 import Data.Bifoldable
 import Data.Bifunctor
 import Data.Bitraversable
 import Data.Foldable
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Monoid hiding (Sum)
 import Data.Ord
-import qualified Data.Set as S
+import Data.Set qualified as S
+import Data.Text qualified as T
 import Data.Traversable
 import Futhark.Util.Loc
 import Futhark.Util.Pretty
@@ -476,15 +471,6 @@
     Observe
   deriving (Eq, Ord, Show)
 
--- | Simple Futhark values.  Values are fully evaluated and their type
--- is always unambiguous.
-data Value
-  = PrimValue !PrimValue
-  | -- | It is assumed that the array is 0-indexed.  The type
-    -- is the full type.
-    ArrayValue !(Array Int Value) ValueType
-  deriving (Eq, Show)
-
 -- | An identifier consists of its name and the type of the value
 -- bound to the identifier.
 data IdentBase f vn = Ident
@@ -767,7 +753,7 @@
   | -- | Fail if the first expression does not return true,
     -- and return the value of the second expression if it
     -- does.
-    Assert (ExpBase f vn) (ExpBase f vn) (f String) SrcLoc
+    Assert (ExpBase f vn) (ExpBase f vn) (f T.Text) SrcLoc
   | -- | An n-ary value constructor.
     Constr Name [ExpBase f vn] (f PatType) SrcLoc
   | Update (ExpBase f vn) (SliceBase f vn) (ExpBase f vn) SrcLoc
@@ -1240,36 +1226,36 @@
 --- the Attributes module.
 
 instance Pretty PrimType where
-  ppr (Unsigned Int8) = text "u8"
-  ppr (Unsigned Int16) = text "u16"
-  ppr (Unsigned Int32) = text "u32"
-  ppr (Unsigned Int64) = text "u64"
-  ppr (Signed t) = ppr t
-  ppr (FloatType t) = ppr t
-  ppr Bool = text "bool"
+  pretty (Unsigned Int8) = "u8"
+  pretty (Unsigned Int16) = "u16"
+  pretty (Unsigned Int32) = "u32"
+  pretty (Unsigned Int64) = "u64"
+  pretty (Signed t) = pretty t
+  pretty (FloatType t) = pretty t
+  pretty Bool = "bool"
 
 instance Pretty BinOp where
-  ppr Backtick = text "``"
-  ppr Plus = text "+"
-  ppr Minus = text "-"
-  ppr Pow = text "**"
-  ppr Times = text "*"
-  ppr Divide = text "/"
-  ppr Mod = text "%"
-  ppr Quot = text "//"
-  ppr Rem = text "%%"
-  ppr ShiftR = text ">>"
-  ppr ShiftL = text "<<"
-  ppr Band = text "&"
-  ppr Xor = text "^"
-  ppr Bor = text "|"
-  ppr LogAnd = text "&&"
-  ppr LogOr = text "||"
-  ppr Equal = text "=="
-  ppr NotEqual = text "!="
-  ppr Less = text "<"
-  ppr Leq = text "<="
-  ppr Greater = text ">"
-  ppr Geq = text ">="
-  ppr PipeLeft = text "<|"
-  ppr PipeRight = text "|>"
+  pretty Backtick = "``"
+  pretty Plus = "+"
+  pretty Minus = "-"
+  pretty Pow = "**"
+  pretty Times = "*"
+  pretty Divide = "/"
+  pretty Mod = "%"
+  pretty Quot = "//"
+  pretty Rem = "%%"
+  pretty ShiftR = ">>"
+  pretty ShiftL = "<<"
+  pretty Band = "&"
+  pretty Xor = "^"
+  pretty Bor = "|"
+  pretty LogAnd = "&&"
+  pretty LogOr = "||"
+  pretty Equal = "=="
+  pretty NotEqual = "!="
+  pretty Less = "<"
+  pretty Leq = "<="
+  pretty Greater = ">"
+  pretty Geq = ">="
+  pretty PipeLeft = "<|"
+  pretty PipeRight = "|>"
diff --git a/src/Language/Futhark/Traversals.hs b/src/Language/Futhark/Traversals.hs
--- a/src/Language/Futhark/Traversals.hs
+++ b/src/Language/Futhark/Traversals.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TupleSections #-}
-
 -- |
 --
 -- Functions for generic traversals across Futhark syntax trees.  The
@@ -29,8 +26,8 @@
   )
 where
 
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Set as S
+import Data.List.NonEmpty qualified as NE
+import Data.Set qualified as S
 import Language.Futhark.Syntax
 
 -- | Express a monad mapping operation on a syntax node.  Each element
diff --git a/src/Language/Futhark/Tuple.hs b/src/Language/Futhark/Tuple.hs
--- a/src/Language/Futhark/Tuple.hs
+++ b/src/Language/Futhark/Tuple.hs
@@ -9,8 +9,8 @@
 
 import Data.Char (isDigit, ord)
 import Data.List (sortOn)
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Language.Futhark.Core (Name, nameFromString, nameToText)
 
 -- | Does this record map correspond to a tuple?
diff --git a/src/Language/Futhark/TypeChecker.hs b/src/Language/Futhark/TypeChecker.hs
--- a/src/Language/Futhark/TypeChecker.hs
+++ b/src/Language/Futhark/TypeChecker.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | The type checker checks whether the program is type-consistent
 -- and adds type annotations and various other elaborations.  The
 -- program does not need to have any particular properties for the
@@ -13,6 +10,8 @@
     checkModExp,
     Notes,
     TypeError (..),
+    prettyTypeError,
+    prettyTypeErrorNoLoc,
     Warnings,
     initialEnv,
     envWithImports,
@@ -23,10 +22,10 @@
 import Control.Monad.Writer hiding (Sum)
 import Data.Bifunctor (first, second)
 import Data.Either
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Ord
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.FreshNames hiding (newName)
 import Futhark.Util.Pretty hiding (space)
 import Language.Futhark
@@ -157,10 +156,10 @@
 dupDefinitionError space name loc1 loc2 =
   typeError loc1 mempty $
     "Duplicate definition of"
-      <+> ppr space
-      <+> pprName name <> "."
+      <+> pretty space
+      <+> prettyName name <> "."
       </> "Previously defined at"
-      <+> text (locStr loc2) <> "."
+      <+> pretty (locStr loc2) <> "."
 
 checkForDuplicateDecs :: [DecBase NoInfo Name] -> TypeM ()
 checkForDuplicateDecs =
@@ -228,7 +227,7 @@
           typeError loc mempty $
             "All function parameters must have non-anonymous sizes."
               </> "Hint: add size parameters to"
-              <+> pquote (pprName name') <> "."
+              <+> dquotes (prettyName name') <> "."
 
         pure (tparams', vtype', vtype_t)
 
@@ -304,7 +303,7 @@
       (lookupType loc qn >> warnAbout qn)
         `catchError` \_ -> pure ()
     warnAbout qn =
-      warn loc $ "Inclusion shadows type" <+> pquote (ppr qn) <+> "."
+      warn loc $ "Inclusion shadows type" <+> dquotes (pretty qn) <+> "."
 
 checkSigExp :: SigExpBase NoInfo Name -> TypeM (TySet, MTy, SigExpBase Info VName)
 checkSigExp (SigParens e loc) = do
@@ -561,7 +560,7 @@
       [] -> pure ()
       tp : _ ->
         typeError loc mempty $
-          "Size parameter" <+> pquote (ppr tp) <+> "unused."
+          "Size parameter" <+> dquotes (pretty tp) <+> "unused."
 
     case (l, l') of
       (_, Lifted)
@@ -579,7 +578,7 @@
             typeError loc mempty $
               "Non-lifted type abbreviations may not use existential sizes in their definition."
                 </> "Hint: use 'type~' or add size parameters to"
-                <+> pquote (pprName name) <> "."
+                <+> dquotes (prettyName name) <> "."
       _ -> pure ()
 
     bindSpaced [(Type, name)] $ do
@@ -657,12 +656,12 @@
       | p : _ <- filter nastyParameter params' ->
           warn loc $
             "Entry point parameter\n"
-              </> indent 2 (ppr p)
+              </> indent 2 (pretty p)
               </> "\nwill have an opaque type, so the entry point will likely not be callable."
       | nastyReturnType maybe_tdecl' rettype_t ->
           warn loc $
             "Entry point return type\n"
-              </> indent 2 (ppr rettype)
+              </> indent 2 (pretty rettype)
               </> "\nwill have an opaque type, so the result will likely not be usable."
     _ -> pure ()
 
@@ -735,7 +734,7 @@
   (name', env) <- lookupImport loc name
   when (isBuiltin name) $
     typeError loc mempty $
-      ppr name <+> "may not be explicitly imported."
+      pretty name <+> "may not be explicitly imported."
   pure (mempty, env, ImportDec name (Info name') loc)
 checkOneDec (ValDec vb) = do
   (env, vb') <- checkValBind vb
diff --git a/src/Language/Futhark/TypeChecker/Match.hs b/src/Language/Futhark/TypeChecker/Match.hs
--- a/src/Language/Futhark/TypeChecker/Match.hs
+++ b/src/Language/Futhark/TypeChecker/Match.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Checking for missing cases in a match expression.  Based on
 -- "Warnings for pattern matching" by Luc Maranget.  We only detect
 -- inexhaustiveness here - ideally, we would also like to check for
@@ -10,10 +8,10 @@
   )
 where
 
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Futhark.Util (maybeHead, nubOrd)
-import Futhark.Util.Pretty hiding (bool, group, space)
+import Futhark.Util.Pretty hiding (group, space)
 import Language.Futhark hiding (ExpBase (Constr))
 
 data Constr
@@ -34,21 +32,21 @@
 matchType (MatchWild t) = t
 matchType (MatchConstr _ _ t) = t
 
-pprMatch :: Int -> Match -> Doc
+pprMatch :: Int -> Match -> Doc a
 pprMatch _ MatchWild {} = "_"
-pprMatch _ (MatchConstr (ConstrLit l) _ _) = ppr l
+pprMatch _ (MatchConstr (ConstrLit l) _ _) = pretty l
 pprMatch p (MatchConstr (Constr c) ps _) =
   parensIf (not (null ps) && p >= 10) $
-    "#" <> ppr c <> mconcat (map ((" " <>) . pprMatch 10) ps)
+    "#" <> pretty c <> mconcat (map ((" " <>) . pprMatch 10) ps)
 pprMatch _ (MatchConstr ConstrTuple ps _) =
   parens $ commasep $ map (pprMatch (-1)) ps
 pprMatch _ (MatchConstr (ConstrRecord fs) ps _) =
   braces $ commasep $ zipWith ppField fs ps
   where
-    ppField name t = text (nameToString name) <> equals <> pprMatch (-1) t
+    ppField name t = pretty (nameToString name) <> equals <> pprMatch (-1) t
 
 instance Pretty Match where
-  ppr = pprMatch (-1)
+  pretty = pprMatch (-1)
 
 patternToMatch :: Pat -> Match
 patternToMatch (Id _ (Info t) _) = MatchWild $ toStruct t
diff --git a/src/Language/Futhark/TypeChecker/Modules.hs b/src/Language/Futhark/TypeChecker/Modules.hs
--- a/src/Language/Futhark/TypeChecker/Modules.hs
+++ b/src/Language/Futhark/TypeChecker/Modules.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Implementation of the Futhark module system (at least most of it;
 -- some is scattered elsewhere in the type checker).
 module Language.Futhark.TypeChecker.Modules
@@ -15,10 +12,10 @@
 import Control.Monad.Writer hiding (Sum)
 import Data.Either
 import Data.List (intersect)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
 import Data.Ord
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Util.Pretty
 import Language.Futhark
 import Language.Futhark.Semantic
@@ -216,10 +213,10 @@
             "Cannot refine a type having"
               <+> tpMsg ps <> " with a type having " <> tpMsg cur_ps <> "."
   | otherwise =
-      typeError loc mempty $ pquote (ppr tname) <+> "is not an abstract type in the module type."
+      typeError loc mempty $ dquotes (pretty tname) <+> "is not an abstract type in the module type."
   where
     tpMsg [] = "no type parameters"
-    tpMsg xs = "type parameters" <+> spread (map ppr xs)
+    tpMsg xs = "type parameters" <+> hsep (map pretty xs)
 
 paramsMatch :: [TypeParam] -> [TypeParam] -> Bool
 paramsMatch ps1 ps2 = length ps1 == length ps2 && all match (zip ps1 ps2)
@@ -287,7 +284,7 @@
         "Module defines"
           </> indent 2 (ppTypeAbbr abs name mod_t)
           </> "but module type requires"
-          <+> text what <> "."
+          <+> what <> "."
       where
         what = case name_l of
           Unlifted -> "a non-lifted type"
@@ -342,17 +339,17 @@
 missingType :: Pretty a => Loc -> a -> Either TypeError b
 missingType loc name =
   Left . TypeError loc mempty $
-    "Module does not define a type named" <+> ppr name <> "."
+    "Module does not define a type named" <+> pretty name <> "."
 
 missingVal :: Pretty a => Loc -> a -> Either TypeError b
 missingVal loc name =
   Left . TypeError loc mempty $
-    "Module does not define a value named" <+> ppr name <> "."
+    "Module does not define a value named" <+> pretty name <> "."
 
 missingMod :: Pretty a => Loc -> a -> Either TypeError b
 missingMod loc name =
   Left . TypeError loc mempty $
-    "Module does not define a module named" <+> ppr name <> "."
+    "Module does not define a module named" <+> pretty name <> "."
 
 mismatchedType ::
   Loc ->
@@ -369,19 +366,19 @@
       </> "but module type requires"
       </> indent 2 (ppTypeAbbr abs (QualName quals name) spec_t)
 
-ppTypeAbbr :: [VName] -> QualName VName -> (Liftedness, [TypeParam], StructRetType) -> Doc
+ppTypeAbbr :: [VName] -> QualName VName -> (Liftedness, [TypeParam], StructRetType) -> Doc a
 ppTypeAbbr abs name (l, ps, RetType [] (Scalar (TypeVar () _ tn args)))
   | qualLeaf tn `elem` abs,
     map typeParamToArg ps == args =
-      "type" <> ppr l
-        <+> ppr name
-        <+> spread (map ppr ps)
+      "type" <> pretty l
+        <+> pretty name
+        <+> hsep (map pretty ps)
 ppTypeAbbr _ name (l, ps, t) =
-  "type" <> ppr l
-    <+> ppr name
-    <+> spread (map ppr ps)
+  "type" <> pretty l
+    <+> pretty name
+    <+> hsep (map pretty ps)
     <+> equals
-    <+/> nest 2 (align (ppr t))
+    <+> nest 2 (align (pretty t))
 
 -- | Return new renamed/abstracted env, as well as a mapping from
 -- names in the signature to names in the new env.  This is used for
@@ -538,8 +535,8 @@
               "Type"
                 </> indent 2 (ppTypeAbbr [] (QualName quals name) (l, ps, t))
                 </> textwrap "cannot be made abstract because size parameter"
-                <+/> pquote (pprName d)
-                <+/> textwrap "is not used as an array size in the definition."
+                </> dquotes (prettyName d)
+                </> textwrap "is not used as an array size in the definition."
 
       let spec_t' = applySubst (`M.lookup` abs_subst_to_type) spec_t
           nonrigid = ps <> map (`TypeParamDim` mempty) (retDims t)
@@ -577,11 +574,11 @@
                 </> indent 2 (ppValBind (QualName quals spec_name) v)
                 </> fromMaybe mempty problem
 
-    matchValBinding :: Loc -> BoundV -> BoundV -> Maybe (Maybe Doc)
+    matchValBinding :: Loc -> BoundV -> BoundV -> Maybe (Maybe (Doc ()))
     matchValBinding loc (BoundV spec_tps orig_spec_t) (BoundV tps orig_t) = do
       case doUnification loc spec_tps tps (toStruct orig_spec_t) (toStruct orig_t) of
         Left (TypeError _ notes msg) ->
-          Just $ Just $ msg <> ppr notes
+          Just $ Just $ msg <> pretty notes
         -- Even if they unify, we still have to verify the uniqueness
         -- properties.
         Right t
@@ -590,10 +587,10 @@
 
     ppValBind v (BoundV tps t) =
       "val"
-        <+> ppr v
-        <+> spread (map ppr tps)
+        <+> pretty v
+        <+> hsep (map pretty tps)
         <+> colon
-        </> indent 2 (align (ppr t))
+        </> indent 2 (align (pretty t))
 
 -- | Apply a parametric module to an argument.
 applyFunctor ::
diff --git a/src/Language/Futhark/TypeChecker/Monad.hs b/src/Language/Futhark/TypeChecker/Monad.hs
--- a/src/Language/Futhark/TypeChecker/Monad.hs
+++ b/src/Language/Futhark/TypeChecker/Monad.hs
@@ -1,11 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Main monad in which the type checker runs, as well as ancillary
 -- data definitions.
 module Language.Futhark.TypeChecker.Monad
@@ -21,6 +13,8 @@
     lookupImport,
     localEnv,
     TypeError (..),
+    prettyTypeError,
+    prettyTypeErrorNoLoc,
     withIndexLink,
     unappliedFunctor,
     unknownVariable,
@@ -60,60 +54,68 @@
 import Control.Monad.State.Strict
 import Data.Either
 import Data.List (find, isPrefixOf)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import qualified Data.Version as Version
+import Data.Set qualified as S
+import Data.Version qualified as Version
 import Futhark.FreshNames hiding (newName)
-import qualified Futhark.FreshNames
-import Futhark.Util.Console
+import Futhark.FreshNames qualified
 import Futhark.Util.Pretty hiding (space)
 import Language.Futhark
 import Language.Futhark.Semantic
 import Language.Futhark.Warnings
-import qualified Paths_futhark
+import Paths_futhark qualified
 import Prelude hiding (mapM, mod)
 
-newtype Note = Note Doc
+newtype Note = Note (Doc ())
 
 -- | A collection of extra information regarding a type error.
 newtype Notes = Notes [Note]
   deriving (Semigroup, Monoid)
 
 instance Pretty Note where
-  ppr (Note msg) = "Note:" <+> align msg
+  pretty (Note msg) = unAnnotate $ "Note:" <+> align msg
 
 instance Pretty Notes where
-  ppr (Notes notes) = foldMap (((line <> line) <>) . ppr) notes
+  pretty (Notes notes) = unAnnotate $ foldMap (((line <> line) <>) . pretty) notes
 
 -- | A single note.
-aNote :: Pretty a => a -> Notes
-aNote = Notes . pure . Note . ppr
+aNote :: Doc () -> Notes
+aNote = Notes . pure . Note
 
 -- | Information about an error during type checking.
-data TypeError = TypeError Loc Notes Doc
+data TypeError = TypeError Loc Notes (Doc ())
 
-instance Pretty TypeError where
-  ppr (TypeError loc notes msg) =
-    text (inRed $ "Error at " <> locStr (srclocOf loc) <> ":")
-      </> msg <> ppr notes
+-- | Prettyprint type error.
+prettyTypeError :: TypeError -> Doc AnsiStyle
+prettyTypeError (TypeError loc notes msg) =
+  annotate
+    (bold <> color Red)
+    ("Error at " <> pretty (locText (srclocOf loc)) <> ":")
+    </> prettyTypeErrorNoLoc (TypeError loc notes msg)
 
-errorIndexUrl :: Doc
+-- | Prettyprint type error, without location information.  This can
+-- be used for cases where the location is printed in some other way.
+prettyTypeErrorNoLoc :: TypeError -> Doc AnsiStyle
+prettyTypeErrorNoLoc (TypeError _ notes msg) =
+  unAnnotate msg <> pretty notes <> hardline
+
+errorIndexUrl :: Doc a
 errorIndexUrl = version_url <> "error-index.html"
   where
     version = Paths_futhark.version
     base_url = "https://futhark.readthedocs.io/en/"
     version_url
       | last (Version.versionBranch version) == 0 = base_url <> "latest/"
-      | otherwise = base_url <> "v" <> text (Version.showVersion version) <> "/"
+      | otherwise = base_url <> "v" <> pretty (Version.showVersion version) <> "/"
 
 -- | Attach a reference to documentation explaining the error in more detail.
-withIndexLink :: Doc -> Doc -> Doc
+withIndexLink :: Doc a -> Doc a -> Doc a
 withIndexLink href msg =
   stack
     [ msg,
       "\nFor more information, see:",
-      indent 2 (ppr errorIndexUrl <> "#" <> href)
+      indent 2 (errorIndexUrl <> "#" <> href)
     ]
 
 -- | An unexpected functor appeared!
@@ -130,12 +132,12 @@
   m a
 unknownVariable space name loc =
   typeError loc mempty $
-    "Unknown" <+> ppr space <+> pquote (ppr name)
+    "Unknown" <+> pretty space <+> dquotes (pretty name)
 
 -- | An unknown type was referenced.
 unknownType :: MonadTypeChecker m => SrcLoc -> QualName Name -> m a
 unknownType loc name =
-  typeError loc mempty $ "Unknown type" <+> ppr name <> "."
+  typeError loc mempty $ "Unknown type" <+> pretty name <> "."
 
 -- | A name prefixed with an underscore was used.
 underscoreUse ::
@@ -146,7 +148,7 @@
 underscoreUse loc name =
   typeError loc mempty $
     "Use of"
-      <+> pquote (ppr name)
+      <+> dquotes (pretty name)
         <> ": variables prefixed with underscore may not be accessed."
 
 -- | A mapping from import strings to 'Env's.  This is used to resolve
@@ -246,9 +248,9 @@
     Nothing ->
       typeError loc mempty $
         "Unknown import"
-          <+> dquotes (text canonical_import)
+          <+> dquotes (pretty canonical_import)
           </> "Known:"
-          <+> commasep (map text (M.keys imports))
+          <+> commasep (map pretty (M.keys imports))
     Just scope -> pure (canonical_import, scope)
 
 -- | Evaluate a 'TypeM' computation within an extended (/not/
@@ -268,7 +270,7 @@
 -- internal interface is because we use distinct monads for checking
 -- expressions and declarations.
 class Monad m => MonadTypeChecker m where
-  warn :: Located loc => loc -> Doc -> m ()
+  warn :: Located loc => loc -> Doc () -> m ()
 
   newName :: VName -> m VName
   newID :: Name -> m VName
@@ -291,11 +293,11 @@
       _ ->
         typeError loc mempty $
           "Sizes must have type i64, but"
-            <+> pquote (ppr v)
+            <+> dquotes (pretty v)
             <+> "has type:"
-            </> ppr t
+            </> pretty t
 
-  typeError :: Located loc => loc -> Notes -> Doc -> m a
+  typeError :: Located loc => loc -> Notes -> Doc () -> m a
 
 -- | Elaborate the given name in the given namespace at the given
 -- location, producing the corresponding unique 'VName'.
@@ -368,7 +370,7 @@
             case getType t of
               Nothing ->
                 typeError loc mempty $
-                  "Attempt to use function" <+> pprName name <+> "as value."
+                  "Attempt to use function" <+> prettyName name <+> "as value."
               Just t' ->
                 pure
                   ( qn',
@@ -514,11 +516,11 @@
     available (Type, _) = True
     available (Term, v) = v `S.member` (type_names <> binop_names <> fun_names)
       where
-        type_names = S.fromList $ map (nameFromString . pretty) anyPrimType
+        type_names = S.fromList $ map (nameFromText . prettyText) anyPrimType
         binop_names =
           S.fromList $
             map
-              (nameFromString . pretty)
+              (nameFromText . prettyText)
               [minBound .. (maxBound :: BinOp)]
         fun_names = S.fromList $ map nameFromString ["shape"]
     available _ = False
diff --git a/src/Language/Futhark/TypeChecker/Terms.hs b/src/Language/Futhark/TypeChecker/Terms.hs
--- a/src/Language/Futhark/TypeChecker/Terms.hs
+++ b/src/Language/Futhark/TypeChecker/Terms.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Facilities for type-checking Futhark terms.  Checking a term
 -- requires a little more context to track uniqueness and such.
 --
@@ -23,11 +17,11 @@
 import Control.Monad.State
 import Data.Either
 import Data.List (find, foldl', partition)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map.Strict as M
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import Futhark.Util.Pretty hiding (bool, group, space)
+import Data.Set qualified as S
+import Futhark.Util.Pretty hiding (space)
 import Language.Futhark
 import Language.Futhark.Primitive (intByteSize)
 import Language.Futhark.Traversals
@@ -274,9 +268,9 @@
         Just sloc ->
           lift . typeError rloc mempty $
             "Field"
-              <+> pquote (ppr f)
+              <+> dquotes (pretty f)
               <+> "previously defined at"
-              <+> text (locStrRel rloc sloc) <> "."
+              <+> pretty (locStrRel rloc sloc) <> "."
         Nothing -> pure ()
 checkExp (ArrayLit all_es _ loc) =
   -- Construct the result type and unify all elements with it.  We
@@ -369,7 +363,7 @@
 checkExp (Project k e NoInfo loc) = do
   e' <- checkExp e
   t <- expType e'
-  kt <- mustHaveField (mkUsage loc $ "projection of field " ++ quote (pretty k)) k t
+  kt <- mustHaveField (mkUsage loc $ docText $ "projection of field " <> dquotes (pretty k)) k t
   pure $ Project k e' (Info kt) loc
 checkExp (AppExp (If e1 e2 e3 loc) _) =
   sequentially checkCond $ \e1' _ -> do
@@ -402,7 +396,7 @@
       pure $ QualParens (modname', modnameloc) e' loc
     ModFun {} ->
       typeError loc mempty . withIndexLink "module-is-parametric" $
-        "Module" <+> ppr modname <+> " is a parametric module."
+        "Module" <+> pretty modname <+> " is a parametric module."
   where
     qualifyEnv modname' env =
       env {envNameMap = M.map (qualify' modname') $ envNameMap env}
@@ -434,7 +428,7 @@
 
     checkField e k = do
       t <- expType e
-      let usage = mkUsage loc $ "projection of field " ++ quote (pretty k)
+      let usage = mkUsage loc $ docText $ "projection of field " <> dquotes (pretty k)
       kt <- mustHaveField usage k t
       pure $ Project k e (Info kt) loc
 checkExp (Negate arg loc) = do
@@ -451,7 +445,7 @@
     t <- expType e'
     case anyConsumption e_occs of
       Just c ->
-        let msg = "type computed with consumption at " ++ locStr (location c)
+        let msg = "type computed with consumption at " <> locText (location c)
          in zeroOrderType (mkUsage loc "consumption in right-hand side of 'let'-binding") msg t
       _ -> pure ()
 
@@ -568,7 +562,7 @@
     updateField _ _ _ =
       typeError loc mempty . withIndexLink "record-type-not-known" $
         "Full type of"
-          </> indent 2 (ppr src)
+          </> indent 2 (pretty src)
           </> textwrap " is not known at this point.  Add a type annotation to the original record to disambiguate."
 
 --
@@ -589,7 +583,7 @@
 checkExp (Assert e1 e2 NoInfo loc) = do
   e1' <- require "being asserted" [Bool] =<< checkExp e1
   e2' <- checkExp e2
-  pure $ Assert e1' e2' (Info (pretty e1)) loc
+  pure $ Assert e1' e2' (Info (prettyText e1)) loc
 checkExp (Lambda params body rettype_te NoInfo loc) = do
   (params', body', body_t, rettype', info) <-
     removeSeminullOccurrences . noUnique . incLevel . bindingParams [] params $ \_ params' -> do
@@ -668,7 +662,7 @@
           loc
     _ ->
       typeError loc mempty $
-        "Operator section with invalid operator of type" <+> ppr ftype
+        "Operator section with invalid operator of type" <+> pretty ftype
 checkExp (OpSectionRight op _ e _ NoInfo loc) = do
   (op', ftype) <- lookupVar loc op
   e_arg <- checkArg e
@@ -690,7 +684,7 @@
           loc
     _ ->
       typeError loc mempty $
-        "Operator section with invalid operator of type" <+> ppr ftype
+        "Operator section with invalid operator of type" <+> pretty ftype
 checkExp (ProjectSection fields NoInfo loc) = do
   a <- newTypeVar loc "a"
   let usage = mkUsage loc "projection at"
@@ -769,23 +763,23 @@
   deriving (Functor, Show)
 
 instance Pretty (Unmatched (PatBase Info VName)) where
-  ppr um = case um of
-    (UnmatchedNum p nums) -> ppr' p <+> "where p is not one of" <+> ppr nums
-    (UnmatchedBool p) -> ppr' p
-    (UnmatchedConstr p) -> ppr' p
-    (Unmatched p) -> ppr' p
+  pretty um = case um of
+    (UnmatchedNum p nums) -> pretty' p <+> "where p is not one of" <+> pretty nums
+    (UnmatchedBool p) -> pretty' p
+    (UnmatchedConstr p) -> pretty' p
+    (Unmatched p) -> pretty' p
     where
-      ppr' (PatAscription p t _) = ppr p <> ":" <+> ppr t
-      ppr' (PatParens p _) = parens $ ppr' p
-      ppr' (PatAttr _ p _) = parens $ ppr' p
-      ppr' (Id v _ _) = pprName v
-      ppr' (TuplePat pats _) = parens $ commasep $ map ppr' pats
-      ppr' (RecordPat fs _) = braces $ commasep $ map ppField fs
+      pretty' (PatAscription p t _) = pretty p <> ":" <+> pretty t
+      pretty' (PatParens p _) = parens $ pretty' p
+      pretty' (PatAttr _ p _) = parens $ pretty' p
+      pretty' (Id v _ _) = prettyName v
+      pretty' (TuplePat pats _) = parens $ commasep $ map pretty' pats
+      pretty' (RecordPat fs _) = braces $ commasep $ map ppField fs
         where
-          ppField (name, t) = text (nameToString name) <> equals <> ppr' t
-      ppr' Wildcard {} = "_"
-      ppr' (PatLit e _ _) = ppr e
-      ppr' (PatConstr n _ ps _) = "#" <> ppr n <+> sep (map ppr' ps)
+          ppField (name, t) = pretty (nameToString name) <> equals <> pretty' t
+      pretty' Wildcard {} = "_"
+      pretty' (PatLit e _ _) = pretty e
+      pretty' (PatConstr n _ ps _) = "#" <> pretty n <+> sep (map pretty' ps)
 
 checkIdent :: IdentBase NoInfo Name -> TermTypeM Ident
 checkIdent (Ident name _ loc) = do
@@ -888,7 +882,7 @@
       when (any (`S.member` problematic) (tp2_paramdims `S.difference` tp2_produced_dims)) $ do
         typeError loc mempty . withIndexLink "existential-param-ret" $
           "Existential size would appear in function parameter of return type:"
-            </> indent 2 (ppr (RetType ext tp2'))
+            </> indent 2 (pretty (RetType ext tp2'))
             </> textwrap "This is usually because a higher-order function is used with functional arguments that return existential sizes or locally named sizes, which are then used as parameters of other function arguments."
 
       occur [observation as loc]
@@ -897,7 +891,7 @@
 
       case anyConsumption dflow of
         Just c ->
-          let msg = "type of expression with consumption at " ++ locStr (location c)
+          let msg = "type of expression with consumption at " <> locText (location c)
            in zeroOrderType (mkUsage argloc "potential consumption in expression") msg tp1
         _ -> pure ()
 
@@ -944,7 +938,7 @@
   tfun' <- normPatType tfun
   checkApply loc fname tfun' arg
 checkApply loc (fname, prev_applied) ftype (argexp, _, _, _) = do
-  let fname' = maybe "expression" (pquote . ppr) fname
+  let fname' = maybe "expression" (dquotes . pretty) fname
 
   typeError loc mempty $
     if prev_applied == 0
@@ -952,16 +946,16 @@
         "Cannot apply"
           <+> fname'
           <+> "as function, as it has type:"
-          </> indent 2 (ppr ftype)
+          </> indent 2 (pretty ftype)
       else
         "Cannot apply"
           <+> fname'
-          <+> "to argument #" <> ppr (prev_applied + 1)
-          <+> pquote (shorten $ pretty $ flatten $ ppr argexp) <> ","
-          <+/> "as"
+          <+> "to argument #" <> pretty (prev_applied + 1)
+          <+> dquotes (shorten $ group $ pretty argexp) <> ","
+          </> "as"
           <+> fname'
           <+> "only takes"
-          <+> ppr prev_applied
+          <+> pretty prev_applied
           <+> arguments <> "."
   where
     arguments
@@ -1072,7 +1066,7 @@
         | otherwise = Nothing
 
       checkParamCausality known p =
-        checkCausality (ppr p) known (patternType p) (locOf p)
+        checkCausality (pretty p) known (patternType p) (locOf p)
 
       onExp ::
         S.Set VName ->
@@ -1080,7 +1074,7 @@
         StateT (S.Set VName) (Either TypeError) Exp
 
       onExp known (Var v (Info t) loc)
-        | Just bad <- checkCausality (pquote (ppr v)) known t loc =
+        | Just bad <- checkCausality (dquotes (pretty v)) known t loc =
             bad
       onExp known (ProjectSection _ (Info t) loc)
         | Just bad <- checkCausality "projection section" known t loc =
@@ -1151,19 +1145,19 @@
     causality what loc d dloc t =
       Left . TypeError loc mempty . withIndexLink "causality-check" $
         "Causality check: size"
-          <+/> pquote (pprName d)
-          <+/> "needed for type of"
+          </> dquotes (prettyName d)
+          </> "needed for type of"
           <+> what <> colon
-          </> indent 2 (ppr t)
+          </> indent 2 (pretty t)
           </> "But"
-          <+> pquote (pprName d)
+          <+> dquotes (prettyName d)
           <+> "is computed at"
-          <+/> text (locStrRel loc dloc) <> "."
+          </> pretty (locStrRel loc dloc) <> "."
           </> ""
           </> "Hint:"
           <+> align
             ( textwrap "Bind the expression producing"
-                <+> pquote (pprName d)
+                <+> dquotes (prettyName d)
                 <+> "with 'let' beforehand."
             )
 
@@ -1184,7 +1178,7 @@
         ps' ->
           typeError loc mempty . withIndexLink "unmatched-cases" $
             "Unmatched cases in match expression:"
-              </> indent 2 (stack (map ppr ps'))
+              </> indent 2 (stack (map pretty ps'))
     check e@(IntLit x ty loc) =
       e <$ case ty of
         Info (Scalar (Prim t)) -> errorBounds (inBoundsI x t) x t loc
@@ -1223,9 +1217,9 @@
       unless inBounds $
         typeError loc mempty . withIndexLink "literal-out-of-bounds" $
           "Literal "
-            <> ppr x
+            <> pretty x
             <> " out of bounds for inferred type "
-            <> ppr ty
+            <> pretty ty
             <> "."
 
 -- | Type-check a top-level (or module-level) function definition.
@@ -1273,7 +1267,7 @@
       fname' <- checkName Term fname loc
       when (nameToString fname `elem` doNotShadow) $
         typeError loc mempty . withIndexLink "may-not-be-redefined" $
-          "The" <+> pprName fname <+> "operator may not be redefined."
+          "The" <+> prettyName fname <+> "operator may not be redefined."
 
       pure (fname', tparams', params'', maybe_retdecl'', RetType dims rettype'', body'')
 
@@ -1301,7 +1295,7 @@
       | otherwise =
           typeError usage mempty . withIndexLink "ambiguous-type" $
             "Type is ambiguous (could be one of"
-              <+> commasep (map ppr ots) <> ")."
+              <+> commasep (map pretty ots) <> ")."
               </> "Add a type annotation to disambiguate the type."
     fixOverloaded (v, NoConstraint _ usage) = do
       -- See #1552.
@@ -1320,18 +1314,18 @@
           </> indent 2 (stack $ map field $ M.toList fs)
           </> "Add a type annotation to disambiguate the type."
       where
-        field (l, t) = ppr l <> colon <+> align (ppr t)
+        field (l, t) = pretty l <> colon <+> align (pretty t)
     fixOverloaded (_, HasConstrs cs usage) =
       typeError usage mempty . withIndexLink "ambiguous-type" $
         "Type is ambiguous (must be a sum type with constructors:"
-          <+> ppr (Sum cs) <> ")."
+          <+> pretty (Sum cs) <> ")."
           </> "Add a type annotation to disambiguate the type."
     fixOverloaded (v, Size Nothing (Usage Nothing loc)) =
       typeError loc mempty . withIndexLink "ambiguous-size" $
-        "Ambiguous size" <+> pquote (pprName v) <> "."
+        "Ambiguous size" <+> dquotes (prettyName v) <> "."
     fixOverloaded (v, Size Nothing (Usage (Just u) loc)) =
       typeError loc mempty . withIndexLink "ambiguous-size" $
-        "Ambiguous size" <+> pquote (pprName v) <+> "arising from" <+> text u <> "."
+        "Ambiguous size" <+> dquotes (prettyName v) <+> "arising from" <+> pretty u <> "."
     fixOverloaded _ = pure ()
 
 hiddenParamNames :: [Pat] -> Names
@@ -1472,10 +1466,10 @@
     v : _ ->
       typeError loc mempty . withIndexLink "alias-free-variable" $
         "Function result aliases the free variable "
-          <> pquote (pprName v)
+          <> dquotes (prettyName v)
           <> "."
           </> "Use"
-          <+> pquote "copy"
+          <+> dquotes "copy"
           <+> "to break the aliasing."
     _ ->
       pure ()
@@ -1543,15 +1537,15 @@
       | d : _ <- S.toList $ freeInPat p `S.intersection` forbidden =
           typeError p mempty . withIndexLink "inaccessible-size" $
             "Parameter"
-              <+> pquote (ppr p)
-              <+/> "refers to size"
-              <+> pquote (pprName d)
+              <+> dquotes (pretty p)
+              </> "refers to size"
+              <+> dquotes (prettyName d)
                 <> comma
-              <+/> textwrap "which will not be accessible to the caller"
+              </> textwrap "which will not be accessible to the caller"
                 <> comma
-              <+/> textwrap "possibly because it is nested in a tuple or record."
-              <+/> textwrap "Consider ascribing an explicit type that does not reference "
-                <> pquote (pprName d)
+              </> textwrap "possibly because it is nested in a tuple or record."
+              </> textwrap "Consider ascribing an explicit type that does not reference "
+                <> dquotes (prettyName d)
                 <> "."
       | otherwise = verifyParams forbidden' ps
       where
@@ -1639,11 +1633,11 @@
           notes <- dimNotes defloc $ NamedSize $ qualName k
           typeError defloc notes . withIndexLink "unknowable-param-def" $
             "Unknowable size"
-              <+> pquote (pprName k)
+              <+> dquotes (prettyName k)
               <+> "in parameter of"
-              <+> pquote (pprName defname)
+              <+> dquotes (prettyName defname)
                 <> ", which is inferred as:"
-              </> indent 2 (ppr t)
+              </> indent 2 (pretty t)
       | k `S.member` produced_sizes =
           pure $ Just $ Right k
     closeOver (_, _) =
diff --git a/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs b/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
--- a/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/DoLoop.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 -- | Type inference of @loop@.  This is complicated because of the
 -- uniqueness and size inference, so the implementation is separate
 -- from the main type checker.
@@ -17,11 +13,11 @@
 import Control.Monad.State
 import Data.Bifunctor
 import Data.Bitraversable
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Util (nubOrd)
-import Futhark.Util.Pretty hiding (bool, group, space)
+import Futhark.Util.Pretty hiding (group, space)
 import Language.Futhark
 import Language.Futhark.TypeChecker.Monad hiding (BoundV)
 import Language.Futhark.TypeChecker.Terms.Monad hiding (consumed)
@@ -118,15 +114,15 @@
               S.map aliasVar (aliases t) `S.intersection` bound_outside =
             lift . typeError loop_loc mempty $
               "Return value for loop parameter"
-                <+> pquote (pprName pat_v)
+                <+> dquotes (prettyName pat_v)
                 <+> "aliases"
-                <+> pquote (pprName v) <> "."
+                <+> dquotes (prettyName v) <> "."
         | otherwise = do
             (cons, obs) <- get
             unless (S.null $ aliases t `S.intersection` cons) $
               lift . typeError loop_loc mempty $
                 "Return value for loop parameter"
-                  <+> pquote (pprName pat_v)
+                  <+> dquotes (prettyName pat_v)
                   <+> "aliases other consumed loop parameter."
             when
               ( unique pat_v_t
@@ -134,7 +130,7 @@
               )
               $ lift . typeError loop_loc mempty
               $ "Return value for consuming loop parameter"
-                <+> pquote (pprName pat_v)
+                <+> dquotes (prettyName pat_v)
                 <+> "aliases previously returned value."
             if unique pat_v_t
               then put (cons <> aliases t, obs)
@@ -343,7 +339,7 @@
               | otherwise ->
                   typeError (srclocOf e) mempty $
                     "Iteratee of a for-in loop must be an array, but expression has type"
-                      <+> ppr t
+                      <+> pretty t
         While cond ->
           noUnique . bindingPat [] mergepat (Ascribed merge_t) $ \mergepat' ->
             onlySelfAliasing
diff --git a/src/Language/Futhark/TypeChecker/Terms/Monad.hs b/src/Language/Futhark/TypeChecker/Terms/Monad.hs
--- a/src/Language/Futhark/TypeChecker/Terms/Monad.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/Monad.hs
@@ -1,10 +1,3 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Facilities for type-checking terms.  Factored out of
 -- "Language.Futhark.TypeChecker.Terms" to prevent the module from
 -- being gigantic.
@@ -92,15 +85,16 @@
 import Data.Bitraversable
 import Data.Char (isAscii)
 import Data.List (find, isPrefixOf, sort)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import Futhark.Util.Pretty hiding (bool, group, space)
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Futhark.Util.Pretty hiding (space)
 import Language.Futhark
 import Language.Futhark.Semantic (includeToFilePath)
 import Language.Futhark.Traversals
 import Language.Futhark.TypeChecker.Monad hiding (BoundV)
-import qualified Language.Futhark.TypeChecker.Monad as TypeM
+import Language.Futhark.TypeChecker.Monad qualified as TypeM
 import Language.Futhark.TypeChecker.Types
 import Language.Futhark.TypeChecker.Unify hiding (Usage)
 import Prelude hiding (mod)
@@ -230,10 +224,10 @@
 
 --- Errors
 
-describeVar :: SrcLoc -> VName -> TermTypeM Doc
+describeVar :: SrcLoc -> VName -> TermTypeM (Doc a)
 describeVar loc v =
   gets $
-    maybe ("variable" <+> pquote (pprName v)) (nameReason loc)
+    maybe ("variable" <+> dquotes (prettyName v)) (nameReason loc)
       . M.lookup v
       . stateNames
 
@@ -243,31 +237,31 @@
   typeError rloc mempty . withIndexLink "use-after-consume" $
     "Using"
       <+> name' <> ", but this was consumed at"
-      <+> text (locStrRel rloc wloc) <> ".  (Possibly through aliasing.)"
+      <+> pretty (locStrRel rloc wloc) <> ".  (Possibly through aliasing.)"
 
 badLetWithValue :: (Pretty arr, Pretty src) => arr -> src -> SrcLoc -> TermTypeM a
 badLetWithValue arre vale loc =
   typeError loc mempty $
     "Source array for in-place update"
-      </> indent 2 (ppr arre)
+      </> indent 2 (pretty arre)
       </> "might alias update value"
-      </> indent 2 (ppr vale)
+      </> indent 2 (pretty vale)
       </> "Hint: use"
-      <+> pquote "copy"
+      <+> dquotes "copy"
       <+> "to remove aliases from the value."
 
 returnAliased :: Name -> SrcLoc -> TermTypeM ()
 returnAliased name loc =
   typeError loc mempty . withIndexLink "return-aliased" $
     "Unique-typed return value is aliased to"
-      <+> pquote (pprName name) <> ", which is not consumable."
+      <+> dquotes (prettyName name) <> ", which is not consumable."
 
 uniqueReturnAliased :: SrcLoc -> TermTypeM a
 uniqueReturnAliased loc =
   typeError loc mempty . withIndexLink "unique-return-aliased" $
     "A unique-typed component of the return value is aliased to some other component."
 
-notConsumable :: MonadTypeChecker m => SrcLoc -> Doc -> m b
+notConsumable :: MonadTypeChecker m => SrcLoc -> Doc () -> m b
 notConsumable loc v =
   typeError loc mempty . withIndexLink "not-consumable" $
     "Would consume" <+> v <> ", which is not consumable."
@@ -275,7 +269,7 @@
 unusedSize :: (MonadTypeChecker m) => SizeBinder VName -> m a
 unusedSize p =
   typeError p mempty . withIndexLink "unused-size" $
-    "Size" <+> ppr p <+> "unused in pattern."
+    "Size" <+> pretty p <+> "unused in pattern."
 
 --- Scope management
 
@@ -297,87 +291,87 @@
   | CheckingBranches StructType StructType
 
 instance Pretty Checking where
-  ppr (CheckingApply f e expected actual) =
+  pretty (CheckingApply f e expected actual) =
     header
       </> "Expected:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "Actual:  "
-      <+> align (ppr actual)
+      <+> align (pretty actual)
     where
       header =
         case f of
           Nothing ->
             "Cannot apply function to"
-              <+> pquote (shorten $ pretty $ flatten $ ppr e) <> " (invalid type)."
+              <+> dquotes (shorten $ group $ pretty e) <> " (invalid type)."
           Just fname ->
             "Cannot apply"
-              <+> pquote (ppr fname)
+              <+> dquotes (pretty fname)
               <+> "to"
-              <+> pquote (shorten $ pretty $ flatten $ ppr e) <> " (invalid type)."
-  ppr (CheckingReturn expected actual) =
+              <+> dquotes (shorten $ group $ pretty e) <> " (invalid type)."
+  pretty (CheckingReturn expected actual) =
     "Function body does not have expected type."
       </> "Expected:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "Actual:  "
-      <+> align (ppr actual)
-  ppr (CheckingAscription expected actual) =
+      <+> align (pretty actual)
+  pretty (CheckingAscription expected actual) =
     "Expression does not have expected type from explicit ascription."
       </> "Expected:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "Actual:  "
-      <+> align (ppr actual)
-  ppr (CheckingLetGeneralise fname) =
-    "Cannot generalise type of" <+> pquote (ppr fname) <> "."
-  ppr (CheckingParams fname) =
-    "Invalid use of parameters in" <+> pquote fname' <> "."
+      <+> align (pretty actual)
+  pretty (CheckingLetGeneralise fname) =
+    "Cannot generalise type of" <+> dquotes (pretty fname) <> "."
+  pretty (CheckingParams fname) =
+    "Invalid use of parameters in" <+> dquotes fname' <> "."
     where
-      fname' = maybe "anonymous function" ppr fname
-  ppr (CheckingPat pat NoneInferred) =
-    "Invalid pattern" <+> pquote (ppr pat) <> "."
-  ppr (CheckingPat pat (Ascribed t)) =
+      fname' = maybe "anonymous function" pretty fname
+  pretty (CheckingPat pat NoneInferred) =
+    "Invalid pattern" <+> dquotes (pretty pat) <> "."
+  pretty (CheckingPat pat (Ascribed t)) =
     "Pat"
-      <+> pquote (ppr pat)
+      <+> dquotes (pretty pat)
       <+> "cannot match value of type"
-      </> indent 2 (ppr t)
-  ppr (CheckingLoopBody expected actual) =
+      </> indent 2 (pretty t)
+  pretty (CheckingLoopBody expected actual) =
     "Loop body does not have expected type."
       </> "Expected:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "Actual:  "
-      <+> align (ppr actual)
-  ppr (CheckingLoopInitial expected actual) =
+      <+> align (pretty actual)
+  pretty (CheckingLoopInitial expected actual) =
     "Initial loop values do not have expected type."
       </> "Expected:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "Actual:  "
-      <+> align (ppr actual)
-  ppr (CheckingRecordUpdate fs expected actual) =
+      <+> align (pretty actual)
+  pretty (CheckingRecordUpdate fs expected actual) =
     "Type mismatch when updating record field"
-      <+> pquote fs' <> "."
+      <+> dquotes fs' <> "."
       </> "Existing:"
-      <+> align (ppr expected)
+      <+> align (pretty expected)
       </> "New:     "
-      <+> align (ppr actual)
+      <+> align (pretty actual)
     where
-      fs' = mconcat $ punctuate "." $ map ppr fs
-  ppr (CheckingRequired [expected] actual) =
+      fs' = mconcat $ punctuate "." $ map pretty fs
+  pretty (CheckingRequired [expected] actual) =
     "Expression must must have type"
-      <+> ppr expected <> "."
+      <+> pretty expected <> "."
       </> "Actual type:"
-      <+> align (ppr actual)
-  ppr (CheckingRequired expected actual) =
+      <+> align (pretty actual)
+  pretty (CheckingRequired expected actual) =
     "Type of expression must must be one of "
       <+> expected' <> "."
       </> "Actual type:"
-      <+> align (ppr actual)
+      <+> align (pretty actual)
     where
-      expected' = commasep (map ppr expected)
-  ppr (CheckingBranches t1 t2) =
+      expected' = commasep (map pretty expected)
+  pretty (CheckingBranches t1 t2) =
     "Branches differ in type."
       </> "Former:"
-      <+> ppr t1
+      <+> pretty t1
       </> "Latter:"
-      <+> ppr t2
+      <+> pretty t2
 
 -- | Type checking happens with access to this environment.  The
 -- 'TermScope' will be extended during type-checking as bindings come into
@@ -450,13 +444,13 @@
   = -- | Name is the result of a function application.
     NameAppRes (Maybe (QualName VName)) SrcLoc
 
-nameReason :: SrcLoc -> NameReason -> Doc
+nameReason :: SrcLoc -> NameReason -> Doc a
 nameReason loc (NameAppRes Nothing apploc) =
-  "result of application at" <+> text (locStrRel loc apploc)
+  "result of application at" <+> pretty (locStrRel loc apploc)
 nameReason loc (NameAppRes fname apploc) =
   "result of applying"
-    <+> pquote (ppr fname)
-    <+> parens ("at" <+> text (locStrRel loc apploc))
+    <+> dquotes (pretty fname)
+    <+> parens ("at" <+> pretty (locStrRel loc apploc))
 
 -- | The state is a set of constraints and a counter for generating
 -- type names.  This is distinct from the usual counter we use for
@@ -524,9 +518,9 @@
       Just checking' ->
         throwError $
           TypeError (locOf loc) notes $
-            ppr checking' <> line </> doc <> ppr bcs
+            pretty checking' <> line </> doc <> pretty bcs
       Nothing ->
-        throwError $ TypeError (locOf loc) notes $ doc <> ppr bcs
+        throwError $ TypeError (locOf loc) notes $ doc <> pretty bcs
 
   matchError loc notes bcs t1 t2 = do
     checking <- asks termChecking
@@ -535,19 +529,19 @@
         | hasNoBreadCrumbs bcs ->
             throwError $
               TypeError (locOf loc) notes $
-                ppr checking'
+                pretty checking'
         | otherwise ->
             throwError $
               TypeError (locOf loc) notes $
-                ppr checking' <> line </> doc <> ppr bcs
+                pretty checking' <> line </> doc <> pretty bcs
       Nothing ->
-        throwError $ TypeError (locOf loc) notes $ doc <> ppr bcs
+        throwError $ TypeError (locOf loc) notes $ doc <> pretty bcs
     where
       doc =
         "Types"
-          </> indent 2 (ppr t1)
+          </> indent 2 (pretty t1)
           </> "and"
-          </> indent 2 (ppr t2)
+          </> indent 2 (pretty t2)
           </> "do not match."
 
 -- | Instantiate a type scheme with fresh type variables for its type
@@ -580,12 +574,12 @@
   v <- newID $ mkTypeVarName name i
   case tparam of
     TypeParamType x _ _ -> do
-      constrain v . NoConstraint x . mkUsage loc $
-        "instantiated type parameter of " <> quote (pretty qn) <> "."
+      constrain v . NoConstraint x . mkUsage loc . docText $
+        "instantiated type parameter of " <> dquotes (pretty qn) <> "."
       pure (v, Subst [] $ RetType [] $ Scalar $ TypeVar mempty Nonunique (qualName v) [])
     TypeParamDim {} -> do
-      constrain v . Size Nothing . mkUsage loc $
-        "instantiated size parameter of " <> quote (pretty qn) <> "."
+      constrain v . Size Nothing . mkUsage loc . docText $
+        "instantiated size parameter of " <> dquotes (pretty qn) <> "."
       pure (v, SizeSubst $ NamedSize $ qualName v)
 
 checkQualNameWithEnv :: Namespace -> QualName Name -> SrcLoc -> TermTypeM (TermScope, QualName VName)
@@ -669,12 +663,12 @@
   lookupVar loc qn = do
     outer_env <- liftTypeM askEnv
     (scope, qn'@(QualName qs name)) <- checkQualNameWithEnv Term qn loc
-    let usage = mkUsage loc $ "use of " ++ quote (pretty qn)
+    let usage = mkUsage loc $ docText $ "use of " <> dquotes (pretty qn)
 
     t <- case M.lookup name $ scopeVtable scope of
       Nothing ->
         typeError loc mempty $
-          "Unknown variable" <+> pquote (ppr qn) <> "."
+          "Unknown variable" <+> dquotes (pretty qn) <> "."
       Just (WasConsumed wloc) -> useAfterConsume name loc wloc
       Just (BoundV _ tparams t)
         | "_" `isPrefixOf` baseString name -> underscoreUse loc qn
@@ -719,7 +713,7 @@
     checking <- asks termChecking
     case checking of
       Just checking' ->
-        throwError $ TypeError (locOf loc) notes (ppr checking' <> line </> s)
+        throwError $ TypeError (locOf loc) notes (pretty checking' <> line </> s)
       Nothing ->
         throwError $ TypeError (locOf loc) notes s
 
@@ -733,11 +727,11 @@
     Nothing -> do
       let rsrc = case e of
             SourceArg (FName fname) e' ->
-              RigidArg fname $ prettyOneLine e'
+              RigidArg fname $ prettyTextOneLine e'
             SourceBound e' ->
-              RigidBound $ prettyOneLine e'
+              RigidBound $ prettyTextOneLine e'
             SourceSlice d i j s ->
-              RigidSlice d $ prettyOneLine $ DimSlice i j s
+              RigidSlice d $ prettyTextOneLine $ DimSlice i j s
       d <- newDimVar loc (Rigid rsrc) "n"
       modify $ \s -> s {stateDimTable = M.insert e d $ stateDimTable s}
       pure
@@ -808,14 +802,14 @@
 
 --- Basic checking
 
-unifies :: String -> StructType -> Exp -> TermTypeM Exp
+unifies :: T.Text -> StructType -> Exp -> TermTypeM Exp
 unifies why t e = do
   unify (mkUsage (srclocOf e) why) t . toStruct =<< expType e
   pure e
 
 -- | @require ts e@ causes a 'TypeError' if @expType e@ is not one of
 -- the types in @ts@.  Otherwise, simply returns @e@.
-require :: String -> [PrimType] -> Exp -> TermTypeM Exp
+require :: T.Text -> [PrimType] -> Exp -> TermTypeM Exp
 require why ts e = do
   mustBeOneOf ts (mkUsage (srclocOf e) why) . toStruct =<< expType e
   pure e
diff --git a/src/Language/Futhark/TypeChecker/Terms/Pat.hs b/src/Language/Futhark/TypeChecker/Terms/Pat.hs
--- a/src/Language/Futhark/TypeChecker/Terms/Pat.hs
+++ b/src/Language/Futhark/TypeChecker/Terms/Pat.hs
@@ -1,7 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Type checking of patterns.
 module Language.Futhark.TypeChecker.Terms.Pat
   ( binding,
@@ -19,10 +15,10 @@
 import Data.Bitraversable
 import Data.Either
 import Data.List (find, isPrefixOf, sort)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import Futhark.Util.Pretty hiding (bool, group, space)
+import Data.Set qualified as S
+import Futhark.Util.Pretty hiding (group, space)
 import Language.Futhark
 import Language.Futhark.TypeChecker.Monad hiding (BoundV)
 import Language.Futhark.TypeChecker.Terms.Monad
@@ -65,12 +61,12 @@
     Just occ <- find consumes occs =
       typeError (srclocOf occ) mempty $
         "Consuming"
-          <+> pquote (pprName $ identName v)
-          <+> textwrap ("which is a non-consumable parameter bound at " <> locStr (locOf v) <> ".")
+          <+> dquotes (prettyName $ identName v)
+          <+> textwrap ("which is a non-consumable parameter bound at " <> locText (locOf v) <> ".")
   | not $ identName v `S.member` allOccurring occs,
-    not $ "_" `isPrefixOf` prettyName (identName v) =
+    not $ "_" `isPrefixOf` baseString (identName v) =
       warn (srclocOf v) $
-        "Unused variable" <+> pquote (pprName $ identName v) <+> "."
+        "Unused variable" <+> dquotes (prettyName $ identName v) <+> "."
   | otherwise =
       pure ()
   where
@@ -215,7 +211,7 @@
       | Just prevloc <- M.lookup (sizeName size) prev =
           typeError size mempty $
             "Size name also bound at "
-              <> text (locStrRel (srclocOf size) prevloc)
+              <> pretty (locStrRel (srclocOf size) prevloc)
               <> "."
       | otherwise =
           pure $ M.insert (sizeName size) (srclocOf size) prev
@@ -277,7 +273,7 @@
   PatAttr <$> checkAttr attr <*> checkPat' sizes p t <*> pure loc
 checkPat' _ (Id name _ loc) _
   | name' `elem` doNotShadow =
-      typeError loc mempty $ "The" <+> text name' <+> "operator may not be redefined."
+      typeError loc mempty $ "The" <+> pretty name' <+> "operator may not be redefined."
   where
     name' = nameToString name
 checkPat' _ (Id name NoInfo loc) (Ascribed t) = do
@@ -309,7 +305,7 @@
   | Just (f, fp) <- find (("_" `isPrefixOf`) . nameToString . fst) p_fs =
       typeError fp mempty $
         "Underscore-prefixed fields are not allowed."
-          </> "Did you mean" <> dquotes (text (drop 1 (nameToString f)) <> "=_") <> "?"
+          </> "Did you mean" <> dquotes (pretty (drop 1 (nameToString f)) <> "=_") <> "?"
 checkPat' sizes (RecordPat p_fs loc) (Ascribed (Scalar (Record t_fs)))
   | sort (map fst p_fs) == sort (M.keys t_fs) =
       RecordPat . M.toList <$> check <*> pure loc
@@ -322,7 +318,7 @@
 
   when (sort (M.keys fields') /= sort (map fst fields)) $
     typeError loc mempty $
-      "Duplicate fields in record pattern" <+> ppr p <> "."
+      "Duplicate fields in record pattern" <+> pretty p <> "."
 
   unify (mkUsage loc "matching a record pattern") (Scalar (Record fields')) $ toStruct t
   t' <- normTypeFully t
@@ -398,7 +394,7 @@
     size : _ ->
       typeError size mempty $
         "Cannot bind"
-          <+> ppr size
+          <+> pretty size
           <+> "as it is never used as the size of a concrete (non-function) value."
     [] ->
       bindNameMap (patNameMap p') $ m p'
diff --git a/src/Language/Futhark/TypeChecker/Types.hs b/src/Language/Futhark/TypeChecker/Types.hs
--- a/src/Language/Futhark/TypeChecker/Types.hs
+++ b/src/Language/Futhark/TypeChecker/Types.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TupleSections #-}
-
 -- | Type checker building blocks that do not involve unification.
 module Language.Futhark.TypeChecker.Types
   ( checkTypeExp,
@@ -32,11 +26,11 @@
 import Control.Monad.State
 import Data.Bifunctor
 import Data.List (find, foldl', sort, unzip4, (\\))
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
+import Data.Set qualified as S
 import Futhark.Util (nubOrd)
-import Futhark.Util.Pretty hiding ((<|>))
+import Futhark.Util.Pretty
 import Language.Futhark
 import Language.Futhark.Traversals
 import Language.Futhark.TypeChecker.Monad
@@ -216,7 +210,7 @@
     _ ->
       typeError loc mempty $
         "Type constructor"
-          <+> pquote (spread (ppr name : map ppr ps))
+          <+> dquotes (hsep (pretty name : map pretty ps))
           <+> "used without any arguments."
 --
 evalTypeExp (TETuple ts loc) = do
@@ -233,7 +227,7 @@
   let field_names = map fst fs
   unless (sort field_names == sort (nubOrd field_names)) $
     typeError loc mempty $
-      "Duplicate record fields in" <+> ppr t <> "."
+      "Duplicate record fields in" <+> pretty t <> "."
 
   checked <- traverse evalTypeExp $ M.fromList fs
   let fs' = fmap (\(x, _, _, _) -> x) checked
@@ -261,13 +255,13 @@
     (SizeLifted, _) ->
       typeError loc mempty $
         "Cannot create array with elements of size-lifted type"
-          <+> pquote (ppr t)
-          <+/> "(might cause irregular array)."
+          <+> dquotes (pretty t)
+          <+> "(might cause irregular array)."
     (Lifted, _) ->
       typeError loc mempty $
         "Cannot create array with elements of lifted type"
-          <+> pquote (ppr t)
-          <+/> "(might contain function)."
+          <+> dquotes (pretty t)
+          <+> "(might contain function)."
   where
     checkSizeExp SizeExpAny = do
       dv <- newTypeName "d"
@@ -282,7 +276,7 @@
   (t', svars, RetType dims st, l) <- evalTypeExp t
   unless (mayContainArray st) $
     warn loc $
-      "Declaring" <+> pquote (ppr st) <+> "as unique has no effect."
+      "Declaring" <+> dquotes (pretty st) <+> "as unique has no effect."
   pure (TEUnique t' loc, svars, RetType dims $ st `setUniqueness` Unique, l)
   where
     mayContainArray (Scalar Prim {}) = False
@@ -325,7 +319,7 @@
         Just d ->
           typeError loc mempty . withIndexLink "unused-existential" $
             "Existential size "
-              <> pquote (pprName d)
+              <> dquotes (prettyName d)
               <> " not used as array size."
         Nothing ->
           pure
@@ -343,7 +337,7 @@
   let constructors = map fst cs
   unless (sort constructors == sort (nubOrd constructors)) $
     typeError loc mempty $
-      "Duplicate constructors in" <+> ppr t
+      "Duplicate constructors in" <+> pretty t
 
   unless (length constructors < 256) $
     typeError loc mempty "Sum types must have less than 256 constructors."
@@ -370,11 +364,11 @@
     then
       typeError tloc mempty $
         "Type constructor"
-          <+> pquote (ppr tname)
+          <+> dquotes (pretty tname)
           <+> "requires"
-          <+> ppr (length ps)
+          <+> pretty (length ps)
           <+> "arguments, but provided"
-          <+> ppr (length targs) <> "."
+          <+> pretty (length targs) <> "."
     else do
       (targs', dims, substs) <- unzip3 <$> zipWithM checkArgApply ps targs
       pure
@@ -393,7 +387,7 @@
       pure (op', loc, args ++ [arg])
     rootAndArgs te' =
       typeError (srclocOf te') mempty $
-        "Type" <+> pquote (ppr te') <+> "is not a type constructor."
+        "Type" <+> dquotes (pretty te') <+> "is not a type constructor."
 
     checkArgApply (TypeParamDim pv _) (TypeArgExpDim (SizeExpNamed v dloc) loc) = do
       v' <- checkNamedSize loc v
@@ -425,9 +419,9 @@
     checkArgApply p a =
       typeError tloc mempty $
         "Type argument"
-          <+> ppr a
+          <+> pretty a
           <+> "not valid for a type parameter"
-          <+> ppr p <> "."
+          <+> pretty p <> "."
 
 -- | Check a type expression, producing:
 --
@@ -470,9 +464,9 @@
           lift $
             typeError loc mempty $
               "Name"
-                <+> pquote (ppr v)
+                <+> dquotes (pretty v)
                 <+> "also bound at"
-                <+> text (locStr prev_loc) <> "."
+                <+> pretty (locStr prev_loc) <> "."
         Nothing ->
           modify $ M.insert (ns, v) loc
 
@@ -489,10 +483,10 @@
   where
     bad v loc prev_loc =
       typeError loc mempty $
-        text "Name"
-          <+> pquote (ppr v)
+        "Name"
+          <+> dquotes (pretty v)
           <+> "also bound at"
-          <+> text (locStr prev_loc) <> "."
+          <+> pretty (locStr prev_loc) <> "."
 
     check seen (TEArrow (Just v) t1 t2 loc)
       | Just prev_loc <- M.lookup v seen =
@@ -542,10 +536,10 @@
         Just prev ->
           lift $
             typeError loc mempty $
-              text "Type parameter"
-                <+> pquote (ppr v)
+              "Type parameter"
+                <+> dquotes (pretty v)
                 <+> "previously defined at"
-                <+> text (locStr prev) <> "."
+                <+> pretty (locStr prev) <> "."
         Nothing -> do
           modify $ M.insert (ns, v) loc
           lift $ checkName ns v loc
@@ -570,10 +564,10 @@
   deriving (Show)
 
 instance Pretty t => Pretty (Subst t) where
-  ppr (Subst [] t) = ppr t
-  ppr (Subst tps t) = mconcat (map ppr tps) <> colon <+> ppr t
-  ppr PrimSubst = "#<primsubst>"
-  ppr (SizeSubst d) = ppr d
+  pretty (Subst [] t) = pretty t
+  pretty (Subst tps t) = mconcat (map pretty tps) <> colon <+> pretty t
+  pretty PrimSubst = "#<primsubst>"
+  pretty (SizeSubst d) = pretty d
 
 -- | Create a type substitution corresponding to a type binding.
 substFromAbbr :: TypeBinding -> Subst StructRetType
@@ -646,7 +640,7 @@
     mkSubst (TypeParamType _ pv _) (TypeArgType at _) =
       (pv, Subst [] $ RetType [] $ second mempty at)
     mkSubst p a =
-      error $ "applyType mkSubst: cannot substitute " ++ pretty a ++ " for " ++ pretty p
+      error $ "applyType mkSubst: cannot substitute " ++ prettyString a ++ " for " ++ prettyString p
 
 substTypesRet ::
   Monoid as =>
diff --git a/src/Language/Futhark/TypeChecker/Unify.hs b/src/Language/Futhark/TypeChecker/Unify.hs
--- a/src/Language/Futhark/TypeChecker/Unify.hs
+++ b/src/Language/Futhark/TypeChecker/Unify.hs
@@ -1,9 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE Trustworthy #-}
-
 -- | Implementation of unification and other core type system building
 -- blocks.
 module Language.Futhark.TypeChecker.Unify
@@ -41,10 +35,11 @@
 import Data.Bifunctor
 import Data.Char (isAscii)
 import Data.List (foldl', intersect)
-import qualified Data.Map.Strict as M
+import Data.Map.Strict qualified as M
 import Data.Maybe
-import qualified Data.Set as S
-import Futhark.Util.Pretty hiding (empty)
+import Data.Set qualified as S
+import Data.Text qualified as T
+import Futhark.Util.Pretty
 import Language.Futhark
 import Language.Futhark.TypeChecker.Monad hiding (BoundV)
 import Language.Futhark.TypeChecker.Types
@@ -56,21 +51,21 @@
   = MatchingTypes StructType StructType
   | MatchingFields [Name]
   | MatchingConstructor Name
-  | Matching Doc
+  | Matching (Doc ())
 
 instance Pretty BreadCrumb where
-  ppr (MatchingTypes t1 t2) =
+  pretty (MatchingTypes t1 t2) =
     "When matching type"
-      </> indent 2 (ppr t1)
+      </> indent 2 (pretty t1)
       </> "with"
-      </> indent 2 (ppr t2)
-  ppr (MatchingFields fields) =
+      </> indent 2 (pretty t2)
+  pretty (MatchingFields fields) =
     "When matching types of record field"
-      <+> pquote (mconcat $ punctuate "." $ map ppr fields) <> dot
-  ppr (MatchingConstructor c) =
-    "When matching types of constructor" <+> pquote (ppr c) <> dot
-  ppr (Matching s) =
-    s
+      <+> dquotes (mconcat $ punctuate "." $ map pretty fields) <> dot
+  pretty (MatchingConstructor c) =
+    "When matching types of constructor" <+> dquotes (pretty c) <> dot
+  pretty (Matching s) =
+    unAnnotate s
 
 -- | Unification failures can occur deep down inside complicated types
 -- (consider nested records).  We leave breadcrumbs behind us so we
@@ -93,15 +88,15 @@
   BreadCrumbs $ bc : bcs
 
 instance Pretty BreadCrumbs where
-  ppr (BreadCrumbs []) = mempty
-  ppr (BreadCrumbs bcs) = line <> stack (map ppr bcs)
+  pretty (BreadCrumbs []) = mempty
+  pretty (BreadCrumbs bcs) = line <> stack (map pretty bcs)
 
 -- | A usage that caused a type constraint.
-data Usage = Usage (Maybe String) SrcLoc
+data Usage = Usage (Maybe T.Text) SrcLoc
   deriving (Show)
 
 -- | Construct a 'Usage' from a location and a description.
-mkUsage :: SrcLoc -> String -> Usage
+mkUsage :: SrcLoc -> T.Text -> Usage
 mkUsage = flip (Usage . Just)
 
 -- | Construct a 'Usage' that has just a location, but no particular
@@ -110,8 +105,8 @@
 mkUsage' = Usage Nothing
 
 instance Pretty Usage where
-  ppr (Usage Nothing loc) = "use at " <> textwrap (locStr loc)
-  ppr (Usage (Just s) loc) = textwrap s <+/> "at" <+> textwrap (locStr loc)
+  pretty (Usage Nothing loc) = "use at " <> textwrap (locText loc)
+  pretty (Usage (Just s) loc) = textwrap s <+> "at" <+> textwrap (locText loc)
 
 instance Located Usage where
   locOf (Usage _ loc) = locOf loc
@@ -170,16 +165,16 @@
 -- | The source of a rigid size.
 data RigidSource
   = -- | A function argument that is not a constant or variable name.
-    RigidArg (Maybe (QualName VName)) String
+    RigidArg (Maybe (QualName VName)) T.Text
   | -- | An existential return size.
     RigidRet (Maybe (QualName VName))
   | RigidLoop
   | -- | Produced by a complicated slice expression.
-    RigidSlice (Maybe Size) String
+    RigidSlice (Maybe Size) T.Text
   | -- | Produced by a complicated range expression.
     RigidRange
   | -- | Produced by a range expression with this bound.
-    RigidBound String
+    RigidBound T.Text
   | -- | Mismatch in branches.
     RigidCond StructType StructType
   | -- | Invented during unification.
@@ -194,63 +189,63 @@
 data Rigidity = Rigid RigidSource | Nonrigid
   deriving (Eq, Ord, Show)
 
-prettySource :: SrcLoc -> SrcLoc -> RigidSource -> Doc
+prettySource :: SrcLoc -> SrcLoc -> RigidSource -> Doc ()
 prettySource ctx loc (RigidRet Nothing) =
   "is unknown size returned by function at"
-    <+> text (locStrRel ctx loc) <> "."
+    <+> pretty (locStrRel ctx loc) <> "."
 prettySource ctx loc (RigidRet (Just fname)) =
   "is unknown size returned by"
-    <+> pquote (ppr fname)
+    <+> dquotes (pretty fname)
     <+> "at"
-    <+> text (locStrRel ctx loc) <> "."
+    <+> pretty (locStrRel ctx loc) <> "."
 prettySource ctx loc (RigidArg fname arg) =
   "is value of argument"
-    </> indent 2 (shorten arg)
+    </> indent 2 (shorten (pretty arg))
     </> "passed to"
     <+> fname'
     <+> "at"
-    <+> text (locStrRel ctx loc) <> "."
+    <+> pretty (locStrRel ctx loc) <> "."
   where
-    fname' = maybe "function" (pquote . ppr) fname
+    fname' = maybe "function" (dquotes . pretty) fname
 prettySource ctx loc (RigidSlice d slice) =
   "is size produced by slice"
-    </> indent 2 (shorten slice)
+    </> indent 2 (shorten (pretty slice))
     </> d_desc <> "at"
-    <+> text (locStrRel ctx loc) <> "."
+    <+> pretty (locStrRel ctx loc) <> "."
   where
     d_desc = case d of
-      Just d' -> "of dimension of size " <> pquote (ppr d') <> " "
+      Just d' -> "of dimension of size " <> dquotes (pretty d') <> " "
       Nothing -> mempty
 prettySource ctx loc RigidLoop =
-  "is unknown size of value returned at" <+> text (locStrRel ctx loc) <> "."
+  "is unknown size of value returned at" <+> pretty (locStrRel ctx loc) <> "."
 prettySource ctx loc RigidRange =
-  "is unknown length of range at" <+> text (locStrRel ctx loc) <> "."
+  "is unknown length of range at" <+> pretty (locStrRel ctx loc) <> "."
 prettySource ctx loc (RigidBound bound) =
   "generated from expression"
-    </> indent 2 (shorten bound)
-    </> "used in range at " <> text (locStrRel ctx loc) <> "."
+    </> indent 2 (shorten (pretty bound))
+    </> "used in range at " <> pretty (locStrRel ctx loc) <> "."
 prettySource ctx loc (RigidOutOfScope boundloc v) =
   "is an unknown size arising from "
-    <> pquote (pprName v)
+    <> dquotes (prettyName v)
     <> " going out of scope at "
-    <> text (locStrRel ctx loc)
+    <> pretty (locStrRel ctx loc)
     <> "."
     </> "Originally bound at "
-      <> text (locStrRel ctx boundloc)
+      <> pretty (locStrRel ctx boundloc)
       <> "."
 prettySource ctx loc RigidCoerce =
   "is an unknown size arising from empty dimension in coercion at"
-    <+> text (locStrRel ctx loc) <> "."
+    <+> pretty (locStrRel ctx loc) <> "."
 prettySource _ _ RigidUnify =
   "is an artificial size invented during unification of functions with anonymous sizes."
 prettySource ctx loc (RigidCond t1 t2) =
   "is unknown due to conditional expression at "
-    <> text (locStrRel ctx loc)
+    <> pretty (locStrRel ctx loc)
     <> "."
     </> "One branch returns array of type: "
-      <> align (ppr t1)
+      <> align (pretty t1)
     </> "The other an array of type:       "
-      <> align (ppr t2)
+      <> align (pretty t2)
 
 -- | Retrieve notes describing the purpose or origin of the given
 -- t'Size'.  The location is used as the *current* location, for the
@@ -260,8 +255,8 @@
   c <- M.lookup (qualLeaf d) <$> getConstraints
   case c of
     Just (_, UnknowableSize loc rsrc) ->
-      pure . aNote . pretty $
-        pquote (ppr d) <+> prettySource (srclocOf ctx) loc rsrc
+      pure . aNote $
+        dquotes (pretty d) <+> prettySource (srclocOf ctx) loc rsrc
     _ -> pure mempty
 dimNotes _ _ = pure mempty
 
@@ -276,20 +271,20 @@
 typeVarNotes v = maybe mempty (aNote . note . snd) . M.lookup v <$> getConstraints
   where
     note (HasConstrs cs _) =
-      pprName v
+      prettyName v
         <+> "="
         <+> mconcat (map ppConstr (M.toList cs))
         <+> "..."
     note (Overloaded ts _) =
-      pprName v <+> "must be one of" <+> mconcat (punctuate ", " (map ppr ts))
+      prettyName v <+> "must be one of" <+> mconcat (punctuate ", " (map pretty ts))
     note (HasFields fs _) =
-      pprName v
+      prettyName v
         <+> "="
         <+> braces (mconcat (punctuate ", " (map ppField (M.toList fs))))
     note _ = mempty
 
-    ppConstr (c, _) = "#" <> ppr c <+> "..." <+> "|"
-    ppField (f, _) = pprName f <> ":" <+> "..."
+    ppConstr (c, _) = "#" <> pretty c <+> "..." <+> "|"
+    ppField (f, _) = prettyName f <> ":" <+> "..."
 
 -- | Monads that which to perform unification must implement this type
 -- class.
@@ -320,7 +315,7 @@
     loc ->
     Notes ->
     BreadCrumbs ->
-    Doc ->
+    Doc () ->
     m a
 
 -- | Replace all type variables with their substitution.
@@ -455,7 +450,7 @@
                       filter (`notElem` M.keys arg_fs) (M.keys fs)
                         ++ filter (`notElem` M.keys fs) (M.keys arg_fs)
                 unifyError usage mempty bcs $
-                  "Unshared fields:" <+> commasep (map ppr missing) <> "."
+                  "Unshared fields:" <+> commasep (map pretty missing) <> "."
         ( Scalar (TypeVar _ _ (QualName _ tn) targs),
           Scalar (TypeVar _ _ (QualName _ arg_tn) arg_targs)
           )
@@ -545,7 +540,7 @@
                       filter (`notElem` M.keys arg_cs) (M.keys cs)
                         ++ filter (`notElem` M.keys cs) (M.keys arg_cs)
                 unifyError usage mempty bcs $
-                  "Unshared constructors:" <+> commasep (map (("#" <>) . ppr) missing) <> "."
+                  "Unshared constructors:" <+> commasep (map (("#" <>) . pretty) missing) <> "."
         _
           | t1' == t2' -> pure ()
           | otherwise -> failure
@@ -563,9 +558,9 @@
   notes <- (<>) <$> dimNotes usage d1 <*> dimNotes usage d2
   unifyError usage notes bcs $
     "Dimensions"
-      <+> pquote (ppr d1)
+      <+> dquotes (pretty d1)
       <+> "and"
-      <+> pquote (ppr d2)
+      <+> dquotes (pretty d2)
       <+> "do not match."
 
 -- | Unifies two types.
@@ -592,9 +587,9 @@
       notes <- (<>) <$> dimNotes usage d1 <*> dimNotes usage d2
       unifyError usage notes bcs $
         "Dimensions"
-          <+> pquote (ppr d1)
+          <+> dquotes (pretty d1)
           <+> "and"
-          <+> pquote (ppr d2)
+          <+> dquotes (pretty d2)
           <+> "do not match."
 
     boundParam bound (NamedSize (QualName _ d)) = d `elem` bound
@@ -611,9 +606,9 @@
   when (vn `S.member` typeVars tp) $
     unifyError usage mempty bcs $
       "Occurs check: cannot instantiate"
-        <+> pprName vn
+        <+> prettyName vn
         <+> "with"
-        <+> ppr tp <> "."
+        <+> pretty tp <> "."
 
 scopeCheck ::
   MonadUnify m =>
@@ -643,12 +638,12 @@
       notes <- typeNotes usage tp
       unifyError usage notes bcs $
         "Cannot unify type"
-          </> indent 2 (ppr tp)
+          </> indent 2 (pretty tp)
           </> "with"
-          <+> pquote (pprName vn)
+          <+> dquotes (prettyName vn)
           <+> "(scope violation)."
           </> "This is because"
-          <+> pquote (pprName v)
+          <+> dquotes (prettyName v)
           <+> "is rigidly bound in a deeper scope."
 
 linkVarToType ::
@@ -680,7 +675,7 @@
           problems ->
             unifyError usage mempty bcs . withIndexLink "unify-param-existential" $
               "Parameter(s) "
-                <> commasep (map (pquote . pprName) problems)
+                <> commasep (map (dquotes . prettyName) problems)
                 <> " used as size(s) would go out of scope."
 
   case snd <$> M.lookup vn constraints of
@@ -689,9 +684,9 @@
             breadCrumb
               ( Matching $
                   "When verifying that"
-                    <+> pquote (pprName vn)
+                    <+> dquotes (prettyName vn)
                     <+> textwrap "is not instantiated with a function type, due to"
-                    <+> ppr unlift_usage
+                    <+> pretty unlift_usage
               )
               bcs
 
@@ -701,9 +696,9 @@
       when (any (`elem` bound) (freeInType tp)) $
         unifyError usage mempty bcs $
           "Type variable"
-            <+> pprName vn
+            <+> prettyName vn
             <+> "cannot be instantiated with type containing anonymous sizes:"
-            </> indent 2 (ppr tp)
+            </> indent 2 (pretty tp)
             </> textwrap "This is usually because the size of an array returned by a higher-order function argument cannot be determined statically.  This can also be due to the return size being a value parameter.  Add type annotation to clarify."
     Just (Equality _) -> do
       link
@@ -718,15 +713,15 @@
             _ ->
               unifyError usage mempty bcs $
                 "Cannot instantiate"
-                  <+> pquote (pprName vn)
+                  <+> dquotes (prettyName vn)
                   <+> "with type"
-                  </> indent 2 (ppr tp)
+                  </> indent 2 (pretty tp)
                   </> "as"
-                  <+> pquote (pprName vn)
+                  <+> dquotes (prettyName vn)
                   <+> "must be one of"
-                  <+> commasep (map ppr ts)
-                  <+/> "due to"
-                  <+/> ppr old_usage <> "."
+                  <+> commasep (map pretty ts)
+                  </> "due to"
+                  <+> pretty old_usage <> "."
     Just (HasFields required_fields old_usage) -> do
       link
       case tp of
@@ -736,11 +731,11 @@
               let bcs' =
                     breadCrumb
                       ( Matching $
-                          pprName vn
+                          prettyName vn
                             <+> "must be a record with at least the fields:"
-                            </> indent 2 (ppr (Record required_fields'))
+                            </> indent 2 (pretty (Record required_fields'))
                             </> "due to"
-                            <+> ppr old_usage <> "."
+                            <+> pretty old_usage <> "."
                       )
                       bcs
               mapM_ (uncurry $ unifyWith onDims usage bound bcs') $
@@ -755,15 +750,15 @@
         _ ->
           unifyError usage mempty bcs $
             "Cannot instantiate"
-              <+> pquote (pprName vn)
+              <+> dquotes (prettyName vn)
               <+> "with type"
-              </> indent 2 (ppr tp)
+              </> indent 2 (pretty tp)
               </> "as"
-              <+> pquote (pprName vn)
+              <+> dquotes (prettyName vn)
               <+> "must be a record with fields"
-              </> indent 2 (ppr (Record required_fields))
+              </> indent 2 (pretty (Record required_fields))
               </> "due to"
-              <+> ppr old_usage <> "."
+              <+> pretty old_usage <> "."
     -- See Note [Linking variables to sum types]
     Just (HasConstrs required_cs old_usage) ->
       case tp of
@@ -823,12 +818,12 @@
               notes <- dimNotes usage dim
               unifyError usage notes bcs $
                 "Cannot unify size variable"
-                  <+> pquote (ppr dim')
+                  <+> dquotes (pretty dim')
                   <+> "with"
-                  <+> pquote (pprName vn)
+                  <+> dquotes (prettyName vn)
                   <+> "(scope violation)."
                   </> "This is because"
-                  <+> pquote (ppr dim')
+                  <+> dquotes (pretty dim')
                   <+> "is rigidly bound in a deeper scope."
             _ -> modifyConstraints $ M.insert (qualLeaf dim') (lvl, c)
     _ -> pure ()
@@ -851,9 +846,9 @@
   where
     failure =
       unifyError usage mempty noBreadCrumbs $
-        text "Cannot unify type"
-          <+> pquote (ppr t)
-          <+> "with any of " <> commasep (map ppr ts) <> "."
+        "Cannot unify type"
+          <+> dquotes (pretty t)
+          <+> "with any of " <> commasep (map pretty ts) <> "."
 
 linkVarToTypes :: MonadUnify m => Usage -> VName -> [PrimType] -> m ()
 linkVarToTypes usage vn ts = do
@@ -864,30 +859,30 @@
         [] ->
           unifyError usage mempty noBreadCrumbs $
             "Type constrained to one of"
-              <+> commasep (map ppr ts)
+              <+> commasep (map pretty ts)
               <+> "but also one of"
-              <+> commasep (map ppr vn_ts)
+              <+> commasep (map pretty vn_ts)
               <+> "due to"
-              <+> ppr vn_usage <> "."
+              <+> pretty vn_usage <> "."
         ts' -> modifyConstraints $ M.insert vn (lvl, Overloaded ts' usage)
     Just (_, HasConstrs _ vn_usage) ->
       unifyError usage mempty noBreadCrumbs $
         "Type constrained to one of"
-          <+> commasep (map ppr ts)
+          <+> commasep (map pretty ts)
             <> ", but also inferred to be sum type due to"
-          <+> ppr vn_usage
+          <+> pretty vn_usage
             <> "."
     Just (_, HasFields _ vn_usage) ->
       unifyError usage mempty noBreadCrumbs $
         "Type constrained to one of"
-          <+> commasep (map ppr ts)
+          <+> commasep (map pretty ts)
             <> ", but also inferred to be record due to"
-          <+> ppr vn_usage
+          <+> pretty vn_usage
             <> "."
     Just (lvl, _) -> modifyConstraints $ M.insert vn (lvl, Overloaded ts usage)
     Nothing ->
       unifyError usage mempty noBreadCrumbs $
-        "Cannot constrain type to one of" <+> commasep (map ppr ts)
+        "Cannot constrain type to one of" <+> commasep (map pretty ts)
 
 -- | Assert that this type must support equality.
 equalityType ::
@@ -898,7 +893,7 @@
 equalityType usage t = do
   unless (orderZero t) $
     unifyError usage mempty noBreadCrumbs $
-      "Type " <+> pquote (ppr t) <+> "does not support equality (is higher-order)."
+      "Type " <+> dquotes (pretty t) <+> "does not support equality (is higher-order)."
   mapM_ mustBeEquality $ typeVars t
   where
     mustBeEquality vn = do
@@ -910,10 +905,10 @@
           | not $ orderZero vn_t ->
               unifyError usage mempty noBreadCrumbs $
                 "Type"
-                  <+> pquote (ppr t)
+                  <+> dquotes (pretty t)
                   <+> "does not support equality."
                   </> "Constrained to be higher-order due to"
-                  <+> ppr cusage
+                  <+> pretty cusage
                   <+> "."
           | otherwise -> pure ()
         Just (lvl, NoConstraint _ _) ->
@@ -926,7 +921,7 @@
           mapM_ (equalityType usage) $ concat $ M.elems cs
         _ ->
           unifyError usage mempty noBreadCrumbs $
-            "Type" <+> pprName vn <+> "does not support equality."
+            "Type" <+> prettyName vn <+> "does not support equality."
 
 zeroOrderTypeWith ::
   (MonadUnify m, Pretty (Shape dim), Monoid as) =>
@@ -937,7 +932,7 @@
 zeroOrderTypeWith usage bcs t = do
   unless (orderZero t) $
     unifyError usage mempty bcs $
-      "Type" </> indent 2 (ppr t) </> "found to be functional."
+      "Type" </> indent 2 (pretty t) </> "found to be functional."
   mapM_ mustBeZeroOrder . S.toList . typeVars $ t
   where
     mustBeZeroOrder vn = do
@@ -948,9 +943,9 @@
         Just (_, ParamType Lifted ploc) ->
           unifyError usage mempty bcs $
             "Type parameter"
-              <+> pquote (pprName vn)
+              <+> dquotes (prettyName vn)
               <+> "at"
-              <+> text (locStr ploc)
+              <+> pretty (locStr ploc)
               <+> "may be a function."
         _ -> pure ()
 
@@ -958,7 +953,7 @@
 zeroOrderType ::
   (MonadUnify m, Pretty (Shape dim), Monoid as) =>
   Usage ->
-  String ->
+  T.Text ->
   TypeBase dim as ->
   m ()
 zeroOrderType usage desc =
@@ -975,7 +970,7 @@
 arrayElemTypeWith usage bcs t = do
   unless (orderZero t) $
     unifyError usage mempty bcs $
-      "Type" </> indent 2 (ppr t) </> "found to be functional."
+      "Type" </> indent 2 (pretty t) </> "found to be functional."
   mapM_ mustBeZeroOrder . S.toList . typeVars $ t
   where
     mustBeZeroOrder vn = do
@@ -987,9 +982,9 @@
           | l `elem` [Lifted, SizeLifted] ->
               unifyError usage mempty bcs $
                 "Type parameter"
-                  <+> pquote (pprName vn)
+                  <+> dquotes (prettyName vn)
                   <+> "bound at"
-                  <+> text (locStr ploc)
+                  <+> pretty (locStr ploc)
                   <+> "is lifted and cannot be an array element."
         _ -> pure ()
 
@@ -997,7 +992,7 @@
 arrayElemType ::
   (MonadUnify m, Pretty (Shape dim), Monoid as) =>
   Usage ->
-  String ->
+  T.Text ->
   TypeBase dim as ->
   m ()
 arrayElemType usage desc =
@@ -1024,7 +1019,7 @@
           zipWithM_ (unifyWith onDims usage bound bcs') f1 f2
       | otherwise =
           unifyError usage mempty bcs $
-            "Cannot unify constructor" <+> pquote (pprName c) <> "."
+            "Cannot unify constructor" <+> dquotes (prettyName c) <> "."
 
 -- | In @mustHaveConstr usage c t fs@, the type @t@ must have a
 -- constructor named @c@ that takes arguments of types @ts@.
@@ -1049,17 +1044,17 @@
               | length fs == length fs' -> zipWithM_ (unify usage) fs fs'
               | otherwise ->
                   unifyError usage mempty noBreadCrumbs $
-                    "Different arity for constructor" <+> pquote (ppr c) <> "."
+                    "Different arity for constructor" <+> dquotes (pretty c) <> "."
     Scalar (Sum cs) ->
       case M.lookup c cs of
         Nothing ->
           unifyError usage mempty noBreadCrumbs $
-            "Constuctor" <+> pquote (ppr c) <+> "not present in type."
+            "Constuctor" <+> dquotes (pretty c) <+> "not present in type."
         Just fs'
           | length fs == length fs' -> zipWithM_ (unify usage) fs fs'
           | otherwise ->
               unifyError usage mempty noBreadCrumbs $
-                "Different arity for constructor" <+> pquote (ppr c) <+> "."
+                "Different arity for constructor" <+> dquotes (pretty c) <+> "."
     _ ->
       unify usage t $ Scalar $ Sum $ M.singleton c fs
 
@@ -1098,9 +1093,9 @@
       | otherwise ->
           unifyError usage mempty bcs $
             "Attempt to access field"
-              <+> pquote (ppr l)
+              <+> dquotes (pretty l)
               <+> " of value of type"
-              <+> ppr (toStructural t) <> "."
+              <+> pretty (toStructural t) <> "."
     _ -> do
       unify usage (toStruct t) $ Scalar $ Record $ M.singleton l l_type
       pure l_type'
@@ -1193,16 +1188,16 @@
   curLevel = pure 0
 
   unifyError loc notes bcs doc =
-    throwError $ TypeError (locOf loc) notes $ doc <> ppr bcs
+    throwError $ TypeError (locOf loc) notes $ doc <> pretty bcs
 
   matchError loc notes bcs t1 t2 =
-    throwError $ TypeError (locOf loc) notes $ doc <> ppr bcs
+    throwError $ TypeError (locOf loc) notes $ doc <> pretty bcs
     where
       doc =
         "Types"
-          </> indent 2 (ppr t1)
+          </> indent 2 (pretty t1)
           </> "and"
-          </> indent 2 (ppr t2)
+          </> indent 2 (pretty t2)
           </> "do not match."
 
 runUnifyM :: [TypeParam] -> [TypeParam] -> UnifyM a -> Either TypeError a
diff --git a/src/Language/Futhark/Warnings.hs b/src/Language/Futhark/Warnings.hs
--- a/src/Language/Futhark/Warnings.hs
+++ b/src/Language/Futhark/Warnings.hs
@@ -7,20 +7,20 @@
     singleWarning,
     singleWarning',
     listWarnings,
+    prettyWarnings,
   )
 where
 
 import Data.List (sortOn)
 import Data.Monoid
-import Futhark.Util.Console (inYellow)
 import Futhark.Util.Loc
 import Futhark.Util.Pretty
-import Language.Futhark.Core (locStr, prettyStacktrace)
+import Language.Futhark.Core (locText, prettyStacktrace)
 import Prelude
 
 -- | The warnings produced by the compiler.  The 'Show' instance
 -- produces a human-readable description.
-newtype Warnings = Warnings [(SrcLoc, [SrcLoc], Doc)]
+newtype Warnings = Warnings [(SrcLoc, [SrcLoc], Doc ())]
 
 instance Semigroup Warnings where
   Warnings ws1 <> Warnings ws2 = Warnings $ ws1 <> ws2
@@ -28,39 +28,38 @@
 instance Monoid Warnings where
   mempty = Warnings mempty
 
-instance Pretty Warnings where
-  ppr (Warnings []) = mempty
-  ppr (Warnings ws) =
-    stack $ punctuate line $ map onWarning $ sortOn (rep . wloc) ws
-    where
-      wloc (x, _, _) = locOf x
-      rep NoLoc = ("", 0)
-      rep (Loc p _) = (posFile p, posCoff p)
-      onWarning (loc, [], w) =
-        text (inYellow ("Warning at " ++ locStr loc ++ ":"))
-          </> indent 2 w
-      onWarning (loc, locs, w) =
-        text
-          ( inYellow
-              ( "Warning at\n"
-                  ++ prettyStacktrace 0 (map locStr (loc : locs))
-              )
-          )
-          </> indent 2 w
+prettyWarnings :: Warnings -> Doc AnsiStyle
+prettyWarnings (Warnings []) = mempty
+prettyWarnings (Warnings ws) =
+  stack $ map ((<> hardline) . onWarning) $ sortOn (rep . wloc) ws
+  where
+    wloc (x, _, _) = locOf x
+    rep NoLoc = ("", 0)
+    rep (Loc p _) = (posFile p, posCoff p)
+    onWarning (loc, [], w) =
+      annotate
+        (color Yellow)
+        ("Warning at" <+> pretty (locText loc) <> ":")
+        </> indent 2 (unAnnotate w)
+    onWarning (loc, locs, w) =
+      annotate
+        (color Yellow)
+        ("Warning at" </> pretty (prettyStacktrace 0 (map locText (loc : locs))))
+        </> indent 2 (unAnnotate w)
 
 -- | True if there are any warnings in the set.
 anyWarnings :: Warnings -> Bool
 anyWarnings (Warnings ws) = not $ null ws
 
 -- | A single warning at the given location.
-singleWarning :: SrcLoc -> Doc -> Warnings
+singleWarning :: SrcLoc -> Doc () -> Warnings
 singleWarning loc = singleWarning' loc []
 
 -- | A single warning at the given location, but also with a stack
 -- trace (sort of) to the location.
-singleWarning' :: SrcLoc -> [SrcLoc] -> Doc -> Warnings
+singleWarning' :: SrcLoc -> [SrcLoc] -> Doc () -> Warnings
 singleWarning' loc locs problem = Warnings [(loc, locs, problem)]
 
 -- | Exports Warnings into a list of (location, problem).
-listWarnings :: Warnings -> [(SrcLoc, Doc)]
+listWarnings :: Warnings -> [(SrcLoc, Doc ())]
 listWarnings (Warnings ws) = map (\(loc, _, doc) -> (loc, doc)) ws
diff --git a/src/main.hs b/src/main.hs
--- a/src/main.hs
+++ b/src/main.hs
@@ -1,7 +1,7 @@
 -- | The *actual* @futhark@ command line program, as seen by cabal.
 module Main (main) where
 
-import qualified Futhark.CLI.Main
+import Futhark.CLI.Main qualified
 
 -- | This is the main function.
 main :: IO ()
diff --git a/unittests/Futhark/AD/DerivativesTests.hs b/unittests/Futhark/AD/DerivativesTests.hs
--- a/unittests/Futhark/AD/DerivativesTests.hs
+++ b/unittests/Futhark/AD/DerivativesTests.hs
@@ -1,10 +1,10 @@
 module Futhark.AD.DerivativesTests (tests) where
 
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Futhark.AD.Derivatives
 import Futhark.Analysis.PrimExp
 import Futhark.IR.Syntax.Core (nameFromString)
-import Futhark.Util.Pretty (pretty)
+import Futhark.Util.Pretty (prettyString)
 import Test.Tasty
 import Test.Tasty.HUnit
 
@@ -40,12 +40,12 @@
       ]
 
     binOpTest bop =
-      testCase (pretty bop) $
+      testCase (prettyString bop) $
         let t = binOpType bop
             (dx, dy) = pdBinOp bop (blank t) (blank t)
          in (primExpType dx, primExpType dy) @?= (t, t)
 
     unOpTest bop =
-      testCase (pretty bop) $
+      testCase (prettyString bop) $
         let t = unOpType bop
          in primExpType (pdUnOp bop $ blank t) @?= t
diff --git a/unittests/Futhark/BenchTests.hs b/unittests/Futhark/BenchTests.hs
--- a/unittests/Futhark/BenchTests.hs
+++ b/unittests/Futhark/BenchTests.hs
@@ -2,8 +2,8 @@
 
 module Futhark.BenchTests (tests) where
 
-import qualified Data.Map as M
-import qualified Data.Text as T
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.Bench
 import Test.Tasty
 import Test.Tasty.QuickCheck
diff --git a/unittests/Futhark/IR/Mem/IxFun/Alg.hs b/unittests/Futhark/IR/Mem/IxFun/Alg.hs
--- a/unittests/Futhark/IR/Mem/IxFun/Alg.hs
+++ b/unittests/Futhark/IR/Mem/IxFun/Alg.hs
@@ -47,23 +47,23 @@
   deriving (Eq, Show)
 
 instance Pretty num => Pretty (IxFun num) where
-  ppr (Direct dims) =
-    text "Direct" <> parens (commasep $ map ppr dims)
-  ppr (Permute fun perm) = ppr fun <> ppr perm
-  ppr (Index fun is) = ppr fun <> ppr is
-  ppr (FlatIndex fun is) = ppr fun <> ppr is
-  ppr (Reshape fun oldshape) =
-    ppr fun
-      <> text "->reshape"
-      <> parens (ppr oldshape)
-  ppr (Coerce fun oldshape) =
-    ppr fun
-      <> text "->coerce"
-      <> parens (ppr oldshape)
-  ppr (OffsetIndex fun i) =
-    ppr fun <> text "->offset_index" <> parens (ppr i)
-  ppr (Rebase new_base fun) =
-    text "rebase(" <> ppr new_base <> text ", " <> ppr fun <> text ")"
+  pretty (Direct dims) =
+    "Direct" <> parens (commasep $ map pretty dims)
+  pretty (Permute fun perm) = pretty fun <> pretty perm
+  pretty (Index fun is) = pretty fun <> pretty is
+  pretty (FlatIndex fun is) = pretty fun <> pretty is
+  pretty (Reshape fun oldshape) =
+    pretty fun
+      <> "->reshape"
+      <> parens (pretty oldshape)
+  pretty (Coerce fun oldshape) =
+    pretty fun
+      <> "->coerce"
+      <> parens (pretty oldshape)
+  pretty (OffsetIndex fun i) =
+    pretty fun <> "->offset_index" <> parens (pretty i)
+  pretty (Rebase new_base fun) =
+    "rebase(" <> pretty new_base <> ", " <> pretty fun <> ")"
 
 iota :: Shape num -> IxFun num
 iota = Direct
diff --git a/unittests/Futhark/IR/Mem/IxFunTests.hs b/unittests/Futhark/IR/Mem/IxFunTests.hs
--- a/unittests/Futhark/IR/Mem/IxFunTests.hs
+++ b/unittests/Futhark/IR/Mem/IxFunTests.hs
@@ -5,19 +5,19 @@
   )
 where
 
-import qualified Data.List as L
-import qualified Futhark.IR.Mem.IxFun as IxFunLMAD
-import qualified Futhark.IR.Mem.IxFun.Alg as IxFunAlg
+import Data.List qualified as L
+import Futhark.IR.Mem.IxFun qualified as IxFunLMAD
+import Futhark.IR.Mem.IxFun.Alg qualified as IxFunAlg
 import Futhark.IR.Mem.IxFunWrapper
-import qualified Futhark.IR.Mem.IxFunWrapper as IxFunWrap
+import Futhark.IR.Mem.IxFunWrapper qualified as IxFunWrap
 import Futhark.IR.Syntax
 import Futhark.IR.Syntax.Core ()
-import qualified Futhark.Util.IntegralExp as IE
-import qualified Futhark.Util.Pretty as PR
+import Futhark.Util.IntegralExp qualified as IE
+import Futhark.Util.Pretty qualified as PR
 import Test.Tasty
 import Test.Tasty.HUnit
 import Prelude hiding (span)
-import qualified Prelude as P
+import Prelude qualified as P
 
 instance IE.IntegralExp Int where
   quot = P.quot
@@ -52,10 +52,10 @@
       resAlg = map (IxFunAlg.index ixfunAlg) points
       errorMessage =
         "lmad ixfun:  "
-          ++ PR.pretty ixfunLMAD
+          ++ PR.prettyString ixfunLMAD
           ++ "\n"
           ++ "alg ixfun:   "
-          ++ PR.pretty ixfunAlg
+          ++ PR.prettyString ixfunAlg
           ++ "\n"
           ++ "lmad shape:  "
           ++ show lmadShape
diff --git a/unittests/Futhark/IR/Mem/IxFunWrapper.hs b/unittests/Futhark/IR/Mem/IxFunWrapper.hs
--- a/unittests/Futhark/IR/Mem/IxFunWrapper.hs
+++ b/unittests/Futhark/IR/Mem/IxFunWrapper.hs
@@ -12,8 +12,8 @@
   )
 where
 
-import qualified Futhark.IR.Mem.IxFun as I
-import qualified Futhark.IR.Mem.IxFun.Alg as IA
+import Futhark.IR.Mem.IxFun qualified as I
+import Futhark.IR.Mem.IxFun.Alg qualified as IA
 import Futhark.IR.Syntax (FlatSlice, Slice)
 import Futhark.Util.IntegralExp
 
diff --git a/unittests/Futhark/IR/PropTests.hs b/unittests/Futhark/IR/PropTests.hs
--- a/unittests/Futhark/IR/PropTests.hs
+++ b/unittests/Futhark/IR/PropTests.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Futhark.IR.PropTests
@@ -6,8 +5,8 @@
   )
 where
 
-import qualified Futhark.IR.Prop.RearrangeTests
-import qualified Futhark.IR.Prop.ReshapeTests
+import Futhark.IR.Prop.RearrangeTests qualified
+import Futhark.IR.Prop.ReshapeTests qualified
 import Test.Tasty
 
 tests :: TestTree
diff --git a/unittests/Futhark/IR/Syntax/CoreTests.hs b/unittests/Futhark/IR/Syntax/CoreTests.hs
--- a/unittests/Futhark/IR/Syntax/CoreTests.hs
+++ b/unittests/Futhark/IR/Syntax/CoreTests.hs
@@ -1,10 +1,9 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Futhark.IR.Syntax.CoreTests (tests) where
 
 import Control.Applicative
-import Futhark.IR.Pretty (pretty)
+import Futhark.IR.Pretty (prettyString)
 import Futhark.IR.Syntax.Core
 import Language.Futhark.CoreTests ()
 import Language.Futhark.PrimitiveTests ()
@@ -41,9 +40,9 @@
     subShapeTest shape1 shape2 expected =
       testCase
         ( "subshapeOf "
-            ++ pretty shape1
+            ++ prettyString shape1
             ++ " "
-            ++ pretty shape2
+            ++ prettyString shape2
             ++ " == "
             ++ show expected
         )
diff --git a/unittests/Futhark/Optimise/MemoryBlockMerging/GreedyColoringTests.hs b/unittests/Futhark/Optimise/MemoryBlockMerging/GreedyColoringTests.hs
--- a/unittests/Futhark/Optimise/MemoryBlockMerging/GreedyColoringTests.hs
+++ b/unittests/Futhark/Optimise/MemoryBlockMerging/GreedyColoringTests.hs
@@ -5,9 +5,9 @@
 
 import Control.Arrow ((***))
 import Data.Function ((&))
-import qualified Data.Map as M
-import qualified Data.Set as S
-import qualified Futhark.Optimise.MemoryBlockMerging.GreedyColoring as GreedyColoring
+import Data.Map qualified as M
+import Data.Set qualified as S
+import Futhark.Optimise.MemoryBlockMerging.GreedyColoring qualified as GreedyColoring
 import Test.Tasty
 import Test.Tasty.HUnit
 
@@ -22,7 +22,9 @@
   testCase "psumTest"
     $ assertEqual
       "Color simple 1-2-3 using two colors"
-      ([(0, "local"), (1, "local")], [(1 :: Int, 0), (2, 1), (3, 0)])
+      ( [(0, "local"), (1, "local")] :: [(Int, String)],
+        [(1 :: Int, 0), (2, 1), (3, 0)]
+      )
     $ (M.toList *** M.toList)
     $ GreedyColoring.colorGraph
       (M.fromList [(1, "local"), (2, "local"), (3, "local")])
@@ -33,7 +35,9 @@
   testCase "allIntersect"
     $ assertEqual
       "Color a graph where all values intersect"
-      ([(0, "local"), (1, "local"), (2, "local")], [(1 :: Int, 2), (2, 1), (3, 0)])
+      ( [(0, "local"), (1, "local"), (2, "local")] :: [(Int, String)],
+        [(1 :: Int, 2), (2, 1), (3, 0)]
+      )
     $ (M.toList *** M.toList)
     $ GreedyColoring.colorGraph
       (M.fromList [(1, "local"), (2, "local"), (3, "local")])
@@ -57,7 +61,9 @@
     & M.toList *** M.toList
     & assertEqual
       "Color nodes with no intersections"
-      ([(0, "local")], [(1, 0), (2, 0), (3, 0)] :: [(Int, Int)])
+      ( [(0, "local")] :: [(Int, String)],
+        [(1, 0), (2, 0), (3, 0)] :: [(Int, Int)]
+      )
     & testCase "noIntersections"
 
 differentSpaces :: TestTree
@@ -68,5 +74,7 @@
     & M.toList *** M.toList
     & assertEqual
       "Color nodes with no intersections but in different spaces"
-      ([(0, "c"), (1, "b"), (2, "a")], [(1, 2), (2, 1), (3, 0)] :: [(Int, Int)])
+      ( [(0, "c"), (1, "b"), (2, "a")] :: [(Int, String)],
+        [(1, 2), (2, 1), (3, 0)] :: [(Int, Int)]
+      )
     & testCase "differentSpaces"
diff --git a/unittests/Futhark/Pkg/SolveTests.hs b/unittests/Futhark/Pkg/SolveTests.hs
--- a/unittests/Futhark/Pkg/SolveTests.hs
+++ b/unittests/Futhark/Pkg/SolveTests.hs
@@ -1,10 +1,8 @@
-{-# LANGUAGE OverloadedStrings #-}
-
 module Futhark.Pkg.SolveTests (tests) where
 
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.Monoid
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Futhark.Pkg.Solve
 import Futhark.Pkg.Types
 import Test.Tasty
diff --git a/unittests/Language/Futhark/CoreTests.hs b/unittests/Language/Futhark/CoreTests.hs
--- a/unittests/Language/Futhark/CoreTests.hs
+++ b/unittests/Language/Futhark/CoreTests.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Language.Futhark.CoreTests () where
diff --git a/unittests/Language/Futhark/PrimitiveTests.hs b/unittests/Language/Futhark/PrimitiveTests.hs
--- a/unittests/Language/Futhark/PrimitiveTests.hs
+++ b/unittests/Language/Futhark/PrimitiveTests.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Language.Futhark.PrimitiveTests
diff --git a/unittests/Language/Futhark/SyntaxTests.hs b/unittests/Language/Futhark/SyntaxTests.hs
--- a/unittests/Language/Futhark/SyntaxTests.hs
+++ b/unittests/Language/Futhark/SyntaxTests.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Language.Futhark.SyntaxTests (tests) where
@@ -8,9 +5,9 @@
 import Control.Applicative hiding (many, some)
 import Data.Char (isAlpha)
 import Data.Functor
-import qualified Data.Map as M
+import Data.Map qualified as M
 import Data.String
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Data.Void
 import Language.Futhark
 import Language.Futhark.Parser
@@ -19,7 +16,7 @@
 import Test.QuickCheck
 import Test.Tasty
 import Text.Megaparsec
-import qualified Text.Megaparsec.Char.Lexer as L
+import Text.Megaparsec.Char.Lexer qualified as L
 import Prelude
 
 tests :: TestTree
@@ -61,7 +58,9 @@
 
 instance IsString UncheckedTypeExp where
   fromString =
-    either (error . syntaxErrorMsg) id . parseType "IsString UncheckedTypeExp" . fromString
+    either (error . T.unpack . syntaxErrorMsg) id
+      . parseType "IsString UncheckedTypeExp"
+      . fromString
 
 type Parser = Parsec Void T.Text
 
diff --git a/unittests/Language/Futhark/TypeChecker/TypesTests.hs b/unittests/Language/Futhark/TypeChecker/TypesTests.hs
--- a/unittests/Language/Futhark/TypeChecker/TypesTests.hs
+++ b/unittests/Language/Futhark/TypeChecker/TypesTests.hs
@@ -1,13 +1,11 @@
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 module Language.Futhark.TypeChecker.TypesTests (tests) where
 
 import Data.Bifunctor (first)
 import Data.List (isInfixOf)
-import qualified Data.Map as M
+import Data.Map qualified as M
+import Data.Text qualified as T
 import Futhark.FreshNames
-import Futhark.Util.Pretty (prettyOneLine)
+import Futhark.Util.Pretty (docText, prettyTextOneLine)
 import Language.Futhark
 import Language.Futhark.Semantic
 import Language.Futhark.SyntaxTests ()
@@ -19,12 +17,14 @@
 
 evalTest :: TypeExp Name -> Either String ([VName], StructRetType) -> TestTree
 evalTest te expected =
-  testCase (pretty te) $
+  testCase (prettyString te) $
     case (fmap (extract . fst) (run (checkTypeExp te)), expected) of
       (Left got_e, Left expected_e) ->
-        (expected_e `isInfixOf` pretty got_e) @? pretty got_e
+        let got_e_s = T.unpack $ docText $ prettyTypeError got_e
+         in (expected_e `isInfixOf` got_e_s) @? got_e_s
       (Left got_e, Right _) ->
-        assertFailure $ "Failed: " <> pretty got_e
+        let got_e_s = T.unpack $ docText $ prettyTypeError got_e
+         in assertFailure $ "Failed: " <> got_e_s
       (Right actual_t, Right expected_t) ->
         actual_t @?= expected_t
       (Right actual_t, Left _) ->
@@ -33,7 +33,7 @@
     extract (_, svars, t, _) = (svars, t)
     run = snd . runTypeM env mempty (mkInitialImport "") blankNameSource
     -- We hack up an environment with some predefined type
-    -- abbreviations for testing.  This is all pretty sensitive to the
+    -- abbreviations for testing.  This is all prettyString sensitive to the
     -- specific unique names, so we have to be careful!
     env =
       initialEnv
@@ -151,10 +151,10 @@
 
 substTest :: M.Map VName (Subst StructRetType) -> StructRetType -> StructRetType -> TestTree
 substTest m t expected =
-  testCase (pretty_m <> ": " <> prettyOneLine t) $
+  testCase (pretty_m <> ": " <> T.unpack (prettyTextOneLine t)) $
     applySubst (`M.lookup` m) t @?= expected
   where
-    pretty_m = prettyOneLine $ map (first prettyName) $ M.toList m
+    pretty_m = T.unpack $ prettyText $ map (first toName) $ M.toList m
 
 -- Some of these tests may be a bit fragile, in that they depend on
 -- internal renumbering, which can be arbitrary.
diff --git a/unittests/Language/Futhark/TypeCheckerTests.hs b/unittests/Language/Futhark/TypeCheckerTests.hs
--- a/unittests/Language/Futhark/TypeCheckerTests.hs
+++ b/unittests/Language/Futhark/TypeCheckerTests.hs
@@ -1,6 +1,6 @@
 module Language.Futhark.TypeCheckerTests (tests) where
 
-import qualified Language.Futhark.TypeChecker.TypesTests
+import Language.Futhark.TypeChecker.TypesTests qualified
 import Test.Tasty
 
 tests :: TestTree
diff --git a/unittests/futhark_tests.hs b/unittests/futhark_tests.hs
--- a/unittests/futhark_tests.hs
+++ b/unittests/futhark_tests.hs
@@ -1,15 +1,15 @@
 module Main (main) where
 
-import qualified Futhark.AD.DerivativesTests
-import qualified Futhark.BenchTests
-import qualified Futhark.IR.Mem.IxFunTests
-import qualified Futhark.IR.PropTests
-import qualified Futhark.IR.Syntax.CoreTests
-import qualified Futhark.Optimise.MemoryBlockMerging.GreedyColoringTests
-import qualified Futhark.Pkg.SolveTests
-import qualified Language.Futhark.PrimitiveTests
-import qualified Language.Futhark.SyntaxTests
-import qualified Language.Futhark.TypeCheckerTests
+import Futhark.AD.DerivativesTests qualified
+import Futhark.BenchTests qualified
+import Futhark.IR.Mem.IxFunTests qualified
+import Futhark.IR.PropTests qualified
+import Futhark.IR.Syntax.CoreTests qualified
+import Futhark.Optimise.MemoryBlockMerging.GreedyColoringTests qualified
+import Futhark.Pkg.SolveTests qualified
+import Language.Futhark.PrimitiveTests qualified
+import Language.Futhark.SyntaxTests qualified
+import Language.Futhark.TypeCheckerTests qualified
 import Test.Tasty
 
 allTests :: TestTree
