Уже была новость про RC. -XSafe, -XTrustworthy, -XUnsafe, -XPolyKinds, compiler plugins.
Ещё из новых возможностей - в GHCi теперь кроме функций можно давать любые определения (data, type, newtype, class, instance, deriving, foreign). Функции по-прежнему требуют подставлять let (как в do-блоке, может, в будущем уберут такое поведение).
-XConstraintKinds (заметка в блоге автора). Если -XPolyKinds не очень много меняет - просто позволяет писать чуть более строго типизированный type-level код, то -XConstraintKinds - несколько более мощное расширение. Оно добавляет новый kind - Constraint. Если все типы объединяются в kind *, то все классы типов объединяются в kind Constraint, более того, теперь можно проводить forall квантификацию не только по типам (:: *), но и по классам типов (:: Constraint). Например, теперь можно делать динамическую диспетчеризацию (позднее связывание) и Dynamic-like типы более удобно.
Пример.
module Shape where
-- * Объекты (типы данных).
data Circle = Circle Double
deriving ( Show, Eq )
data Square = Square Double
deriving ( Show, Eq )
-- * Интерфейс (набор методов).
class ShapeInterface a where
perimeter :: a -> Double
area :: a -> Double
-- * Реализация интерфейса (определения методов).
instance ShapeInterface Circle where
perimeter (Circle r) = 2 * pi * r
area (Circle r) = pi * r * r
instance ShapeInterface Square where
perimeter (Square s) = 4 * s
area (Square s) = s * s
это статическая диспетчеризация (раннее связывание, специфицирующая метод функция подставляется во время компиляции). Раньше для динамической диспетчеризации нужно было писать:
{-# LANGUAGE ExistentialQuantification #-}
module ShapeDynamic where
import Shape
-- * Динамический тип -- обвёртка с помощью existential quantification.
data Shape = forall a. ShapeInterface a => Shape a
-- * Реализация интерфейса для динамического типа.
instance ShapeInterface Shape where
perimeter (Shape x) = perimeter x
area (Shape x) = area x
-- * Умные конструкторы для динамических объектов.
circle :: Double -> Shape
circle = Shape . Circle
square :: Double -> Shape
square = Shape . Square
-- * Использование.
shapes :: [Shape]
shapes = [circle 2, square 3]
example :: [Double]
example = map area shapes
-- shapes
-- > No instance for (Show Shape)
-- example
-- > [12.566370614359172,9.0]
который уже реализует динамическую диспетчеризацию (позднее связывание, выбор специфицирующей метод функции будет произведён во время выполнения).
Если после вдруг окажется, что кроме ShapeInterface нужны другие интерфейсы, то нужно возвращаться и править обвёртку. Либо писать обвёртку на каждый интерфейс (а это адъ). Например, чтобы добавить интерфейс Show:
data Shape = forall a. (Show a, ShapeInterface a) => Shape a
instance Show Shape where
show (Shape x) = show x
-- shapes
-- > [Circle 2.0,Square 3.0]
Теперь, с помощью -XConstraintKinds, можно написать так:
{-# LANGUAGE Rank2Types, KindSignatures, GADTs, ConstraintKinds,
FlexibleInstances, UndecidableInstances #-}
module ShapeDynamicNew where
import GHC.Prim ( Constraint )
import Shape
-- * Общее место.
-- | Dynamic строит тип (*) из одно-параметрического класса типов
-- (* -> Constraint). Класс типов передаётся как аргумент конструктору типов.
-- Конструктор данных принимает любые объекты разделяющие интерфейс данного
-- класса типов и возвращает динамический объект построенного типа Dynamic cls.
data Dynamic :: (* -> Constraint) -> * where
Dynamic :: forall cls a. cls a => a -> Dynamic cls
-- * Складываем необходимые интерфейсы.
class (Show t, ShapeInterface t) => ShowShapeInterface t
instance (Show t, ShapeInterface t) => ShowShapeInterface t
-- * Динамический тип -- все те объекты которые разделяют данный интерфейс(ы).
type Shape = Dynamic ShowShapeInterface
-- * Реализация интерфейса для динамического типа.
instance ShapeInterface Shape where
perimeter (Dynamic x) = perimeter x
area (Dynamic x) = area x
instance Show Shape where
show (Dynamic x) = show x
-- Умные конструкторы для динамических объектов.
circle :: Double -> Shape
circle = Dynamic . Circle
square :: Double -> Shape
square = Dynamic . Square
-- * Использование.
shapes :: [Shape]
shapes = [circle 2.0, square 3.0]
example :: [Double]
example = map area shapes
-- shapes
-- > [Circle 2.0,Square 3.0]
-- example
-- > [12.566370614359172,9.0]