Учитывая, что моя работа сосредоточена на связанных вопросах области видимости и безопасности типов, кодируемых на уровне типов, я наткнулся на этот старый вопрос, пока гуглил, и подумал, что попробую.
Я думаю, этот пост дает ответ, довольно близкий к исходной спецификации. Все это получается на удивление коротким, если у вас есть правильная настройка.
Сначала я начну с примера программы, чтобы дать вам представление о том, как выглядит конечный результат:
program :: Program
program = Program
$ Declare (Var :: Name "foo") (Of :: Type Int)
:> Assign (The (Var :: Name "foo")) (EInt 1)
:> Declare (Var :: Name "bar") (Of :: Type Bool)
:> increment (The (Var :: Name "foo"))
:> Assign (The (Var :: Name "bar")) (ENot $ EBool True)
:> Done
Определение объема
Чтобы гарантировать, что мы можем присваивать значения только переменным, которые были объявлены ранее, нам необходимо понятие области действия.
GHC.TypeLits
предоставляет нам строки уровня типа (называемые Symbol
), поэтому мы можем очень хорошо использовать строки в качестве имен переменных, если захотим. И поскольку мы хотим обеспечить безопасность типов, каждое объявление переменной поставляется с аннотацией типа, которую мы будем хранить вместе с именем переменной. Таким образом, наш тип прицелов: [(Symbol, *)]
.
Мы можем использовать семейство типов, чтобы проверить, находится ли данный Symbol
в области видимости, и вернуть связанный с ним тип, если это так:
type family HasSymbol (g :: [(Symbol,*)]) (s :: Symbol) :: Maybe * where
HasSymbol '[] s = 'Nothing
HasSymbol ('(s, a) ': g) s = 'Just a
HasSymbol ('(t, a) ': g) s = HasSymbol g s
Из этого определения мы можем определить понятие переменной: переменная типа a
в области видимости g
является символом s
, так что HasSymbol g s
возвращает 'Just a
. Это то, что представляет собой тип данных ScopedSymbol
, используя количественную оценку существования для хранения s
.
data ScopedSymbol (g :: [(Symbol,*)]) (a :: *) = forall s.
(HasSymbol g s ~ 'Just a) => The (Name s)
data Name (s :: Symbol) = Var
Здесь я целенаправленно злоупотребляю нотациями повсюду: The
- конструктор для типа ScopedSymbol
, а Name
- _ 18_ с более приятным именем и конструктором. Это позволяет писать такие тонкости, как:
example :: ScopedSymbol ('("foo", Int) ': '("bar", Bool) ': '[]) Bool
example = The (Var :: Name "bar")
Заявления
Теперь, когда у нас есть понятие области видимости и хорошо типизированных переменных в этой области, мы можем начать рассматривать эффекты, которые должны иметь Statement
s. Учитывая, что новые переменные могут быть объявлены в Statement
, нам нужно найти способ распространить эту информацию в области видимости. Ключевым моментом в ретроспективе является наличие двух индексов: входной и выходной области.
Чтобы Declare
новая переменная вместе с ее типом расширила текущую область действия парой имени переменной и соответствующего типа.
Assign
ments, с другой стороны, не изменяют объем. Они просто связывают ScopedSymbol
с выражением соответствующего типа.
data Statement (g :: [(Symbol, *)]) (h :: [(Symbol,*)]) where
Declare :: Name s -> Type a -> Statement g ('(s, a) ': g)
Assign :: ScopedSymbol g a -> Exp g a -> Statement g g
data Type (a :: *) = Of
Мы снова ввели тип прокси, чтобы иметь более удобный синтаксис на уровне пользователя.
example' :: Statement '[] ('("foo", Int) ': '[])
example' = Declare (Var :: Name "foo") (Of :: Type Int)
example'' :: Statement ('("foo", Int) ': '[]) ('("foo", Int) ': '[])
example'' = Assign (The (Var :: Name "foo")) (EInt 1)
Statement
s можно объединить в цепочку с сохранением области видимости путем определения следующего GADT последовательностей, выровненных по типу:
infixr 5 :>
data Statements (g :: [(Symbol, *)]) (h :: [(Symbol,*)]) where
Done :: Statements g g
(:>) :: Statement g h -> Statements h i -> Statements g i
Выражения
Выражения в основном не изменились по сравнению с исходным определением, за исключением того, что теперь они имеют ограниченную область видимости, а новый конструктор EVar
позволяет нам разыменовать ранее объявленную переменную (используя ScopedSymbol
), давая нам выражение соответствующего типа.
data Exp (g :: [(Symbol,*)]) (t :: *) where
EVar :: ScopedSymbol g a -> Exp g a
EBool :: Bool -> Exp g Bool
EInt :: Int -> Exp g Int
EAdd :: Exp g Int -> Exp g Int -> Exp g Int
ENot :: Exp g Bool -> Exp g Bool
Программ
Program
- это просто последовательность операторов, начинающаяся в пустой области видимости. Мы снова используем экзистенциальную количественную оценку, чтобы скрыть объем, который мы получили.
data Program = forall h. Program (Statements '[] h)
Очевидно, что можно писать подпрограммы на Haskell и использовать их в своих программах. В этом примере у меня очень простой increment
, который можно определить так:
increment :: ScopedSymbol g Int -> Statement g g
increment v = Assign v (EAdd (EVar v) (EInt 1))
Я загрузил весь фрагмент кода вместе с правильными LANGUAGE
прагмами и примерами, перечисленными здесь в отдельном gist < / а>. Однако я не включил туда никаких комментариев.
person
gallais
schedule
26.07.2016
TVar
работает в этом примере: он статически гарантирует, что данная ссылка на переменную находится как в области видимости, так и правильно типизирована. - person Cactus   schedule 26.11.2015String
s: gergo.erdi.hu/blog/; в частности, см. здесь примеры: gergo.erdi.hu/blog/ - person Cactus   schedule 30.11.2015