Компилятор SystemT и работа с бесконечными типами в Haskell - PullRequest
0 голосов
/ 17 ноября 2018

Я слежу за этим сообщением в блоге: http://semantic -domain.blogspot.com / 2012/12 / total-функциональное программирование в частичном. Html

Показывает небольшую программу компиляции OCaml для System T (простой общий функциональный язык) .

Весь конвейер принимает синтаксис OCaml (посредством метапрограммирования Camlp4), преобразует их в OCaml AST, который переводится в SystemT Lambda Calculus (см .: module Term), а затем, наконец, SystemT Combinator Calculus (см .: module Goedel). На последнем этапе также используется метапрограммирование OCaml Ast.expr type.

Я пытаюсь перевести его на Haskell без использования Template Haskell.

Для формы SystemT Combinator я написал ее как

{-# LANGUAGE GADTs #-}

data TNat = Zero | Succ TNat

data THom a b where
  Id :: THom a a
  Unit :: THom a ()
  ZeroH :: THom () TNat
  SuccH :: THom TNat TNat
  Compose :: THom a b -> THom b c -> THom a c
  Pair :: THom a b -> THom a c -> THom a (b, c)
  Fst :: THom (a, b) a
  Snd :: THom (a, b) b
  Curry :: THom (a, b) c -> THom a (b -> c)
  Eval :: THom ((a -> b), a) b -- (A = B) * A -> B
  Iter :: THom a b -> THom (a, b) b -> THom (a, TNat) b

Обратите внимание, что Compose - это форвардная композиция, которая отличается от (.).

Во время перевода лямбда-исчисления SystemT в SystemT Combinator Calculus функция Elaborate.synth пытается преобразовать переменные лямбда-исчисления SystemT в серии составных проекционных выражений (связанных с индексами де Брюджина). Это потому, что исчисление комбинатора не имеет переменных / имен переменных. Это делается с помощью Elaborate.lookup, который использует функцию Quote.find.

Проблема в том, что при моем кодировании исчисления комбинатора в качестве GADT THom a b. Перевод функции Quote.find:

  let rec find x  = function
    | [] -> raise Not_found
    | (x', t) :: ctx' when x = x' -> <:expr< Goedel.snd >> 
    | (x', t) :: ctx' -> <:expr< Goedel.compose Goedel.fst $find x ctx'$ >>

В Хаскелл:

find :: TVar -> Context -> _
find tvar [] = error "Not Found"
find tvar ((tvar', ttype):ctx)
  | tvar == tvar' = Snd
  | otherwise     = Compose Fst (find tvar ctx)

В результате возникает ошибка бесконечного типа.

• Occurs check: cannot construct the infinite type: a ~ (a, c)
  Expected type: THom (a, c) c
    Actual type: THom ((a, c), c) c

Проблема заключается в том, что использование Compose и Fst и Snd из THom a b GADT может привести к бесконечным изменениям сигнатуры типа.

В статье нет этой проблемы, потому что она, кажется, использует Ast.expr OCaml для обертывания базовых выражений.

Я не уверен, как разрешить в Haskell. Должен ли я использовать экзистенциально количественный тип, такой как

data TExpr = forall a b. TExpr (THom a b)

Или какой-то тип уровня Fix для адаптации проблемы бесконечного типа. Однако я не уверен, как это меняет функции find или lookup.

1 Ответ

0 голосов
/ 18 ноября 2018

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

Подход в оригинальном посте

Перевод оригинального поста требует шаблона Haskell и пристрастия; find вернул бы Q.Exp, представляющий некоторую Hom a b, избегая этой проблемы, как и в оригинальном сообщении. Как и в оригинальном посте, ошибка типа в исходном коде будет обнаружена при проверке типов при выводе всех функций Template Haskell. Таким образом, ошибки типа по-прежнему обнаруживаются до времени выполнения, но вам все равно нужно будет написать tests , чтобы найти случаи, когда ваши макросы выводят некорректные выражения. Можно дать более сильные гарантии ценой значительного увеличения сложности.

Зависимая типизация / GADT на входе и выходе

Если вы хотите отклониться от поста, одной из альтернатив является повсеместное использование «зависимой типизации» и создание зависимой типизации input . Я свободно использую этот термин, чтобы включать в себя как фактически зависимо типизированные языки, так и реальный зависимый Haskell (когда он приземляется), а также способы фальсифицировать зависимый типизацию в Haskell сегодня (через GADT, синглтоны и тому подобное). Тем не менее, вы теряете возможность писать свой собственный типограф и выбирать, какую систему типов использовать; как правило, в конечном итоге вы встраиваете простейшее лямбда-исчисление и можете имитировать полиморфизм с помощью полиморфных функций Haskell, которые могут генерировать термины для заданного типа. Это проще в зависимо-типизированных языках, но возможно вообще в Haskell.

Но, честно говоря, на этом пути проще использовать абстрактный синтаксис высшего порядка и функции Haskell, например:

data Exp a where
  Abs :: (Exp a -> Exp b) -> Exp (a -> b)
  App :: Exp (a -> b) -> Exp a -> Exp b
  Var :: String -> Exp a —- only use for free variables
exampleId :: Exp (a -> a)
exampleId = Abs (\x -> x)

Если вы можете использовать этот подход (подробности здесь опущены), вы получите высокую степень уверенности от GADT с ограниченной сложностью. Однако этот подход слишком негибкий для многих сценариев, например, потому что контексты ввода видны только компилятору Haskell, а не в ваших типах или терминах.

От нетипизированных до типизированных терминов

Третий вариант - вводить зависимо и , чтобы ваша программа по-прежнему превращала слабо типизированный ввод в строго типизированный. В этом случае ваше средство проверки типов в целом преобразует термины некоторого типа Expr в термины GADT TExp gamma t, Hom a b или тому подобное. Поскольку вы статически не знаете, что такое gamma и t (или a и b), вам действительно понадобится какое-то экзистенциальное.

Но простой экзистенциальный объект слишком слаб: чтобы создать более крупное, хорошо типизированное выражение, вам нужно проверить, что созданные типы допускают это. Например, для построения TExpr, содержащего выражение Compose из двух меньших TExpr, вам необходимо проверить (во время выполнения), что их типы совпадают. А с простым экзистенциальным вы не можете. Поэтому вам необходимо иметь представление типов также на уровне значений.

Более того, экзистенциалы надоедают иметь дело (как обычно), поскольку вы никогда не сможете выставить переменные скрытого типа в возвращаемом типе или спроецировать их (в отличие от зависимых записей / сигма-типов). Я, честно говоря, не совсем уверен, что это в конечном итоге можно заставить работать. Вот возможный частичный набросок на Хаскеле, вплоть до одного случая find.

data Type t where
  VNat :: Type Nat
  VString :: Type String
  VArrow :: Type a -> Type b -> Type (a -> b)
  VPair :: Type a -> Type b -> Type (a, b) 
  VUnit :: Type ()
data SomeType = forall t. SomeType (Type t)
data SomeHom = forall a b. SomeHom (Type a) (Type b) (THom a b)

type Context = [(TVar, SomeType)] 
getType :: Context -> SomeType
getType [] = SomeType VUnit 
getType ((_, SomeType ttyp) :: gamma) =  
   case getType gamma of
       SomeType ctxT -> SomeType (VPair ttyp
find :: TVar -> Context -> SomeHom 
find tvar ((tvar’, ttyp) :: gamma)
   | tvar == tvar’ =
       case (ttyp, getType gamma) of
         (SomeType t, SomeType ctxT) ->
          SomeHom (VPair t ctxT) t Fst
...