2015-04-16 6 views
4

У меня есть тип данных которого (один) Конструктор содержит экзистенциально количественный переменную типа:Могу ли я принудить экзистенциально квантифицированный аргумент в конструкторе типа?

data LogEvent = forall a . ToJSON a => 
      LogEvent { logTimestamp  :: Date 
        , logEventCategory :: Category 
        , logEventLevel :: LogLevel 
        , logThreadId  :: ThreadId 
        , logPayload  :: a 
        } 

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

Я понимаю, от this question и других показаний, которые экзистенциально количественных переменных типа являются уникальными по каждой конкретизации. Тем не менее, данный тип ToJSON a я могу что-то вроде следующего (псевдо-код):

let x :: Result Foo = fromJSON $ toJSON (logPayload event) 

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

Так как я могу переписать этот тип, чтобы извлечь logPayload, если я знаю, что его тип? I

ответ

9

Это похоже на existential typeclass (анти) модель. Эта экзистенциальная магия эквивалентна

data LogEvent = 
     LogEvent { logTimestamp  :: Date 
       , logEventCategory :: Category 
       , logEventLevel :: LogLevel 
       , logThreadId  :: ThreadId 
       , logPayload  :: Aeson.Value 
       } 

но это более четко говорит о вашей структуре. Вы не должны ожидать ничего от своей экзистенциальной структуры, чего вы не ожидаете от этого.

С другой стороны, если вы сделать знают logPayload «s типа, то вы должны кодировать эти знания на уровне типа, перемещая переменный тип из:

data LogEvent a = ... 

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

data ALogEvent = forall a. ToJSON a => ALogEvent (LogEvent a) 

для тех случаев, когда вы этого не знаете. На практике я очень редко видел необходимость в том, чтобы оба они существовали, но, возможно, у вас есть прецедент для этого.

Если вы знаете тип logPayload во время выполнения, но не можете отслеживать полезную нагрузку во время компиляции по какой-то причине, возможно, вы могли бы добавить Typeable a ограничения на ваш экзистенциальный, так что вы можете бросить, не прибегая к unsafeCoerce ... если вы допустили ошибку, вы не будете коррумпировать всю вашу программу причудливо.

+0

Здесь я использую экзистенциальную оболочку, потому что события передаются через 'Chan LogEvent' для асинхронного ведения журнала: отдельный поток читает событие и обрабатывает его. Я, конечно, изначально попытался создать тип LogEvent с параметром полезной нагрузки, но потом ничего не получаю, потому что потребитель на другом конце канала все еще не может наблюдать тип события. – insitu

+1

Если потребитель должен соблюдать тип события, я бы рекомендовал алгебраический тип. То есть, если он не обрабатывает все события равномерно, сохраняя специальный случай или два, в этом случае Typeable, вероятно, прав – luqui

+0

Да, но это означает привязку типа LogEvent к зарегистрированным событиям, чего я хотел избежать, но, очевидно, не сможет чтобы отложить больше :-) Это не библиотечный код, поэтому мне все равно, хотя это означает, что модуль журнала зависит от каждого зарегистрированного типа системы, что немного засасывает ... – insitu

1

Итак, как я могу переписать этот тип, чтобы разрешить извлечение logPayload, если я знаю его тип?

Если вы не хотите, чтобы изменить тип, вы можете заменить fromJSON & toJSON на unsafeCoerce - та же идея, тот же результат, если вы правы, но может привести к сбою программы, если вы не правы по поводу типа.

Если вы хотите, чтобы устройство проверки типов было правильно, вы должны будете указать тип a в LogEvent и не использовать экзистенциальный тип.

2

Вы можете рассмотреть возможность передачи Data.Typeable снимка; введите ограничение Typeable a в свой экзистенциальный тип, а затем, если вы можете правильно угадать скрытый тип, вы можете получить значение обратно под этим типом. См. this Gist для примера с игрушкой.

Обратите внимание, что этот метод приносит жертвы некоторого количества безопасности типов - если вы начинаете вводить другой тип внутри LogEvent, который вы не сделали, прежде чем можете сломать пользователей такого типа, которые предполагают, что они успешно обрабатывают каждый подкасс. В отличие от алгебраических типов, динамика и броски означают, что компилятор не может помочь вам доказать исчерпывающий характер.

+0

Следуя за комментарием luqui, я начал спускаться по этому маршруту, но затем ударил стену: Can not eta-уменьшить до экземпляра формы instance (...) => Typeable Command В объявлении экземпляра данных for 'Command' Типы, которые я хочу зарегистрировать, на самом деле являются экземплярами семейства типов, определенного в классе типов, и кажется, что это вызывает «DeriveDataTypeable». Или я могу неправильно интерпретировать ошибки компилятора – insitu

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

  • Нет связанных вопросов^_^