2014-09-29 3 views
3

Я использую библиотеку syntactic для создания АСТ. Для того, чтобы оценить AST до значения (Haskell), все мои узлы должны быть экземпляром синтаксической класса EvalEnv:Как отменить мой шаблон

class EvalEnv sym env where 
    compileSym :: proxy env -> sym sig -> DenotationM (Reader env) sig 

синтаксический также предоставляет «по умолчанию» реализация:

compileSymDefault :: (Eval sym, Signature sig) 
    => proxy env -> sym sig -> DenotationM (Reader env) sig 

но ограничение на sig недостижим в случаях EvalEnv, делая следующее (скажем, дублирующих друг друга) экземпляра невозможно:

instance EvalEnv sym env where 
    compileSym = compileSymDefault 

Все мои пользовательские AST узлы GADTs, как правило, с несколькими конструкторами, где a параметр всегда удовлетворяет ограничение для compileSymDefault:

data ADDITIVE a where 
    Add :: (Num a) => ADDITIVE (a :-> a :-> Full a) 
    Sub :: (Num a) => ADDITIVE (a :-> a :-> Full a) 

В результате, я обнаружил, что все моих экземпляров для EvalEnv взгляда как:

instance EvalEnv ADDITIVE env where 
    compileSym p Add = compileSymDefault p Add 
    compileSym p Sub = compileSymDefault p Sub 

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

Есть ли способ избежать необходимости перечислять каждый конструктор для каждого типа узла, который я делаю?

+0

Несвязанный, я не уверен, что вы должны передавать прокси-сервер для 'compileSym'. Это необязательно и усложняет это определение. – Carl

+0

@Carl Если было не ясно, 'compileSymDefault' предоставляется синтаксической библиотекой. Там может быть какая-то причина. – crockeea

+0

Где находится класс 'Additive'? Я не могу найти его ни в одном из «синтаксических» модулей, и ни один из них не может быть Hayoo. http://hayoo.fh-wedel.de/?query=syntactic+Additive – Cirdec

ответ

2

Если я правильно понимаю проблему, шаблонная панель возникает из-за необходимости использовать сопоставление шаблонов с каждым конструктором, чтобы обеспечить необходимый контекст в области видимости. Помимо имени конструктора все ветви case идентичны.

В приведенном ниже коде используется функция ранга-2 removeBoilerplate, которая может использоваться для приведения контекста в область видимости. Две примерные функции сначала определяются с использованием шаблона кода, а затем преобразуются для использования вспомогательной функции removeBoilerplate.

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

Я не знаком с синтаксисом, чтобы быть на 100% уверенным, что это сработает, но похоже, что у него хорошие шансы. Вам, вероятно, потребуется немного изменить тип функции removeBoilerplate.

{-# LANGUAGE GADTs , ExplicitForAll , ScopedTypeVariables , 
      FlexibleContexts , RankNTypes #-} 

class Class a where 

-- Random function requiring the class 
requiresClass1 :: Class a => a -> String 
requiresClass1 _ = "One!" 

-- Another one 
requiresClass2 :: Class a => a -> String 
requiresClass2 _ = "Two!" 

-- Our GADT, in which each constructor puts Class in scope 
data GADT a where 
    Cons1 :: Class (GADT a) => GADT a 
    Cons2 :: Class (GADT a) => GADT a 
    Cons3 :: Class (GADT a) => GADT a 

-- Boring boilerplate 
boilerplateExample1 :: GADT a -> String 
boilerplateExample1 [email protected] = requiresClass1 x 
boilerplateExample1 [email protected] = requiresClass1 x 
boilerplateExample1 [email protected] = requiresClass1 x 

-- More boilerplate 
boilerplateExample2 :: GADT a -> String 
boilerplateExample2 [email protected] = requiresClass2 x 
boilerplateExample2 [email protected] = requiresClass2 x 
boilerplateExample2 [email protected] = requiresClass2 x 

-- Scrapping Boilerplate: let's list the constructors only here, once for all 
removeBoilerplate :: GADT a -> (forall b. Class b => b -> c) -> c 
removeBoilerplate [email protected] f = f x 
removeBoilerplate [email protected] f = f x 
removeBoilerplate [email protected] f = f x 

-- No more boilerplate! 
niceBoilerplateExample1 :: GADT a -> String 
niceBoilerplateExample1 x = removeBoilerplate x requiresClass1 

niceBoilerplateExample2 :: GADT a -> String 
niceBoilerplateExample2 x = removeBoilerplate x requiresClass2 
2

Вы не можете отказаться от своего шаблона, но вы можете немного уменьшить его. Ни код scrap your boilerplate, ни новый код GHC Generics не могут вызывать экземпляры для GADT, как у вас. Можно было создать EvalEnv экземпляров с template haskell, но я не буду обсуждать это.

Мы можем уменьшить количество плитки, которую мы пишем очень немного. Идея, с которой мы столкнулись, состоит в том, что forall a есть Signature a экземпляр для любых ADDITIVE a. Давайте сделаем класс вещей, для которых это верно.

class Signature1 f where 
    signatureDict :: f a -> Dict (Signature a) 

Dict является GADT, который фиксирует ограничение. Для его определения требуется {-# LANGUAGE ConstraintKinds #-}. Кроме того, вы можете импортировать его с Data.Constraint в пакет constraints.

data Dict c where 
    Dict :: c => Dict c 

Чтобы использовать ограничение захваченное Dict конструктор, мы должны шаблон матч против него. Затем мы можем написать compileSym с точки зрения signatureDict и compileSymDefault.

compileSymSignature1 :: (Eval sym, Signature1 sym) => 
    proxy env -> sym sig -> DenotationM (Reader env) sig 
compileSymSignature1 p s = 
    case signatureDict s of 
     Dict -> compileSymDefault p s 

Теперь мы можем выписать ADDITIVE и его экземпляры, захватив идею, что есть всегда Signature a экземпляр для любого ADDITIVE a.

data ADDITIVE a where 
    Add :: (Num a) => ADDITIVE (a :-> a :-> Full a) 
    Sub :: (Num a) => ADDITIVE (a :-> a :-> Full a) 

instance Eval ADDITIVE where 
    evalSym Add = (+) 
    evalSym Sub = (-) 

instance Signature1 ADDITIVE where 
    signatureDict Add = Dict 
    signatureDict Sub = Dict 

instance EvalEnv ADDITIVE env where 
    compileSym = compileSymSignature1 

Выписывая экземпляр Signature1 не имеет много преимуществ по сравнению выписывая экземпляр EvalEnv.Единственные преимущества, которые мы получили, заключаются в том, что мы взяли идею, которая может быть полезна в другом месте, а экземпляр Signature1 немного проще писать.

+0

Я рассматривал Generics как возможность, но я возьму ваше слово, что это невозможно. – crockeea

+0

Все, что вам нужно сделать, чтобы увидеть, что 'GHC.Generics' не может получить экземпляр Generic1 для' ADDITIVE', добавляет строку 'geting instance Generic1 ADDITIVE'. GHC предоставит вам сообщение об ошибке, даже если у вас нет '{- # LANGUAGE StandaloneDeriving # -}', '{- # LANGUAGE DeriveGeneriC# -}' и 'import GHC.Generics'. – Cirdec

Смежные вопросы