Как вы формулируете N-арные типы произведений и сумм в этой типизированной Вселенной лямбда-исчисления?

вот код, где у меня проблема:

{-# LANGUAGE GADTs, LANGUAGE DataKinds #-} 

-- * Universe of Terms * -- 

type Id = String 

data Term a where 
   Var :: Id -> Term a
   Lam :: Id -> Type -> Term b -> Term (a :-> b)
   App :: Term (a :-> b) -> Term a -> Term b 
   Let :: Id -> Term a -> Term b -> Term b 

   Tup :: Term a -> Term b -> Term (a :*: b)   -- * existing tuple
   Lft :: Term a -> Term (a :+: b)   -- * existing sum 
   Rgt :: Term b -> Term (a :+: b)

   Tru :: Term Boolean
   Fls :: Term Boolean
   Bot :: Term Unit

-- * Universe of Types * --

data Type = Type :-> Type | Type :*: Type | Type :+: Type | Boolean | Unit

поэтому я хочу продлить Tup быть определенным над произвольно многими аргументами, то же самое с sum. Но формулировка, включающая списки, ограничила бы окончательный термин одним типом a:

Sum :: [Term a] -> Term a 

я мог бы просто избавиться от a и сделать что-то вроде:

Sum :: [Term] -> Term

но тогда я теряю те самые вещи, которые я пытаюсь выразить.

Итак, как я могу выразить некоторый полиморфный термин без потеря выразительности?

1 ответов


делать это для "списка" сложно, используя систему типов Haskell, но это можно сделать. В качестве отправной точки достаточно легко, если вы ограничиваете себя двоичными продуктами и суммами (и лично я бы просто придерживался этого):

{-# LANGUAGE GADTs, DataKinds, TypeOperators, KindSignatures, TypeFamilies #-} 

import Prelude hiding (sum) -- for later

-- * Universe of Terms * -- 

type Id = String 

data Term :: Type -> * where 
   Var :: Id -> Term a
   Lam :: Id -> Type -> Term b -> Term (a :-> b)
   App :: Term (a :-> b) -> Term a -> Term b 

   Let :: Id -> Term a -> Term b -> Term b 
   Tup :: Term a -> Term b -> Term (a :*: b) -- for binary products
   Lft :: Term a -> Term (a :+: b) -- new for sums
   Rgt :: Term b -> Term (a :+: b) -- new for sums
   Tru :: Term Boolean
   Fls :: Term Boolean
   Uni :: Term Unit -- renamed

-- * Universe of Types * --

data Type = Type :-> Type | Type :*: Type | Type :+: Type | Boolean | Unit | Void
  -- added :+: and Void for sums

чтобы построить тип суммы произвольной длины, нам нужна среда терминов. Это гетерогенный список, индексированный по типам терминов в нем:

data Env :: [Type] -> * where
   Nil   :: Env '[]
   (:::) :: Term t -> Env ts -> Env (t ': ts)

infixr :::

затем мы используем семейство типов, чтобы Свернуть список типов в двоичный файл вид продукции. Кроме того, мы могли бы добавить что-то вроде Product [Type] до Type Вселенной.

type family TypeProd (ts :: [Type]) :: Type
type instance TypeProd '[]       = Unit
type instance TypeProd (t ': ts) = t :*: TypeProd ts

на prod функции сворачивает такую среду для приложений Tup. Опять ты также можно добавить Prod как конструктор этого типа Term тип данных.

prod :: Env ts -> Term (TypeProd ts)
prod Nil        = Uni
prod (x ::: xs) = x `Tup` prod xs

суммы произвольной длины принимают только один элемент для инъекции, но нужен тег для указания в какой тип суммы его ввести:

data Tag :: [Type] -> Type -> * where
   First :: Tag (t ': ts) t
   Next  :: Tag ts s -> Tag (t ': ts) s

опять же, у нас есть введите family и функцию для создания такого зверя:

type family TypeSum (ts :: [Type]) :: Type
type instance TypeSum '[]       = Void
type instance TypeSum (t ': ts) = t :+: TypeSum ts

sum :: Tag ts t -> Term t -> Term (TypeSum ts)
sum First    x = Lft x
sum (Next t) x = Rgt (sum t x)

конечно, возможно множество вариаций или обобщений, но это должно дать вам идея.