iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
👌

What's New in GHC 9.8

に公開

GHC 9.8.1 was released on October 10, 2023.

In this article, we will check the new features of GHC 9.8. Past similar articles are:

This article is not an exhaustive introduction. Please also refer to the official release notes:

Features in GHC 9.8

ExtendedLiterals Extension

You can now write literals for sized primitive types (like Int8#):

ghci> :set -XExtendedLiterals
ghci> :m + GHC.Exts
ghci> :t 42#Int8
42#Int8 :: Int8#

TypeAbstractions Extension: Invisible Binders in Type Declarations

In current Haskell, you can use kind variables in declarations of types with polymorphic kinds without declaring them.

class Foo (a :: k)
type family Bar (a :: k)

For reference, these kinds look like this:

type Foo :: forall k. k -> Constraint
type Bar :: forall k. k -> Type

With the TypeAbstractions extension, you can bind the kind variables of forall k. within the type definition. The syntax reuses the one for type application (kind application):

type Foo :: forall k. k -> Constraint
class Foo @k (a :: k)

type Bar :: forall k. k -> Type
type family Bar @k (a :: k)

Although not mentioned in the GHC Proposal, the actual implementation seems to require the use of standalone kind signatures. For details, please refer to the discussion during implementation:

Unsatisfiable Class

As a way for libraries to provide custom type errors, there was the TypeError type family. However, TypeError had usability issues due to being implemented as a type family. Please read the Proposal for more details.

The following type class and function are added:

module GHC.TypeError where

type Unsatisfiable :: ErrorMessage -> Constraint
class Unsatisfiable msg

unsatisfiable :: Unsatisfiable msg => a
-- To be precise: forall {rep} msg (a :: TYPE rep). Unsatisfiable msg => a

Improvements in Parallel Builds: -jsem Option

Build times for large Haskell projects with a large number of dependencies might be shortened.

Previously, parallelization when building Haskell projects included:

  • Package-level parallelization (cabal's -j option)
  • Module-level parallelization (GHC's -j option)

Package-level parallelization is beneficial when building many dependent packages, and module-level parallelization is beneficial when building a package containing many modules.

So, how can we fully utilize CPU cores when building a package that depends on many packages and itself contains many modules? If you only pass -j to cabal, the main package's build won't be parallelized. If you only pass -j to GHC, the many dependent packages won't be built in parallel. If you specify the -j option for both cabal and GHC, the degree of parallelism becomes excessive during the build of dependent packages.

The -jsem option now implemented in GHC allows multiple GHC processes to cooperate and adjust the degree of parallelism effectively. This also requires support from the build tool side; cabal is expected to support it in version 3.12. With compatible GHC and cabal,

cabal build -j --semaphore

you will be able to utilize CPU cores optimally (the --semaphore option might become the default in the future).

Strengthening of Rewrite Rules

It will be possible to perform a kind of higher-order matching in rewrite rules. For example, the following rewrite rule can be used:

foo :: (Int -> Int -> Int) -> Int
foo f = f 42 44 + 1
{-# NOINLINE foo #-}
{-# RULES
"foo" forall f. foo (\x y -> f y x) = 777
 #-}

main = do
  print $ foo (\x y -> x + 2 * y)
  print $ foo (\x y -> 2 * x + y)

Fused Multiply-Add

Many modern CPUs have instructions for calculating Fused Multiply-Add (FMA). For more details on FMA, please refer to an article I wrote previously:

Previously, there was no way to make GHC output FMA instructions other than using FFI, but now primitive functions corresponding to FMA instructions have been added. The following eight have been added:

module GHC.Exts where

fmaddFloat# :: Float# -> Float# -> Float# -> Float# -- x * y + z
fmsubFloat# :: Float# -> Float# -> Float# -> Float# -- x * y - z
fnmaddFloat# :: Float# -> Float# -> Float# -> Float# -- - x * y + z
fnmsubFloat# :: Float# -> Float# -> Float# -> Float# -- - x * y - z

fmaddDouble# :: Double# -> Double# -> Double# -> Double# -- x * y + z
fmsubDouble# :: Double# -> Double# -> Double# -> Double# -- x * y - z
fnmaddDouble# :: Double# -> Double# -> Double# -> Double# -- - x * y + z
fnmsubDouble# :: Double# -> Double# -> Double# -> Double# -- - x * y - z

Note that on architectures where FMA is not necessarily supported, the fma function from libc is called. Therefore, on platforms where libc's FMA is buggy (specifically Windows on x86), the correct answer may not be calculated.

In my package fp-ieee, I have been providing FMA for some time, but I am considering using GHC's FMA primitives starting from GHC 9.8 in environments where FMA is not buggy.

Warnings for head / tail

Warnings will now be issued when using head and tail, which are notorious as partial functions.

ghci> head "foo"

<interactive>:1:1: warning: [GHC-63394] [-Wx-partial]
    In the use of ‘head’
    (imported from Prelude, but defined in Just GHC.List):
    "This is a partial function, it throws an error on empty lists. Use pattern matching or Data.List.uncons instead. Consider refactoring to use Data.List.NonEmpty."
'f'
ghci> tail "foo"

<interactive>:2:1: warning: [GHC-63394] [-Wx-partial]
    In the use of ‘tail’
    (imported from Prelude, but defined in Just GHC.List):
    "This is a partial function, it throws an error on empty lists. Replace it with drop 1, or use pattern matching or Data.List.uncons instead. Consider refactoring to use Data.List.NonEmpty."
"oo"

This warning can be suppressed with the -Wno-x-partial option.

On the other hand, it seems that warnings are not issued for init and last, which are also partial functions.

While this might be a welcome change for pure function fundamentalists, personally I wonder, "Is it really necessary to go that far?"

Other Library Feature Additions

Data.List.!? :: [a] -> Int -> Maybe a
Data.List.unsnoc :: [a] -> Maybe ([a], a)
Data.Tuple.getSolo :: Solo a -> a
Data.Functor.unzip :: Functor f => f (a, b) -> (f a, f b)

Data.List.!? is a safe version of !!. It returns Nothing if the element is out of range.

Data.List.unsnoc calculates both init and last at the same time.

Data.Tuple.getSolo extracts the element from a one-element tuple (Solo type).

Data.Functor.unzip is a generalization of Data.List.unzip.

Others

  • -Wterm-variable-capture: In the upcoming RequiredTypeArguments extension, implicit quantification will not occur if there is a term-level variable with the same name as a type variable. -Wterm-variable-capture issues a warning when implicit quantification of a type variable occurs and a term-level variable with the same name is present.
  • 128-bit wide SIMD primitives are now available in the LLVM backend for AArch64 (this was my contribution).

Discussion