2016-11-19 1 views
2

Я пишу модульный и расширяемый текстовый редактор в Haskell, и я хотел бы реализовать плагин таким образом: Автор плагина предоставляет одну функцию, которая выглядит примерно так:Как я могу обрабатывать пользовательские плагины в своих типах?

handleEvent :: (PluginState, EditorState) -> Event -> (PluginState, EditorState) 

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

Как я могу написать что-то смутно, как это:

type Plugin = (PluginState, EditorState) -> Event -> (PluginState, EditorState) 
data MyEditor = MyEditor EditorState [Plugin] [PluginState] 

Когда PluginState не конкретный тип?

TLDR; Как я могу хранить карту значений с неконкретными типами доступным способом, не выпекая в каждом состоянии плагина плагина в мое глобальное состояние? Я согласен с повторной компиляцией редактора при добавлении нового плагина.

Спасибо! Я действительно застрял на этом:/

Если вам нужно какое-либо разъяснение, пожалуйста, просто спросите!

+0

Отправной точкой для этого является поиск того, как GHC обрабатывает плагины и как Yi обрабатывает конфигурацию. Я думаю, что оба относятся к частичному перекомпиляционному подходу ... – Alec

+1

Звучит как случай для https://hackage.haskell.org/package/vault – Gurkenglas

ответ

1

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

Может быть, вы могли бы использовать existential type, чтобы скрыть состояние плагина, что-то вроде

{-# LANGUAGE ExistentialQuantification #-} 

data Plugin = forall ps. Plugin { 
     currentState :: ps 
    , transition :: ps -> EditorState -> Event -> (ps, EditorState) 
    } 

handleEvent :: Plugin -> EditorState -> Event -> (Plugin,EditorState) 
handleEvent (Plugin ps t) es e = 
    let (ps',es') = t ps es e 
    in (Plugin ps' t,es') 

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

charPlugin :: Plugin 
charPlugin = Plugin 'a' (\ps es e -> (succ ps,es)) 

intPlugin :: Plugin 
intPlugin = Plugin (1::Int) (\ps es e -> (succ ps,es)) 

(я черпал вдохновение из Fold типа из foldl пакета , который использует экзистенциалы в подобном путь)

Теперь вы можете иметь список плагинов:.

plugins :: [Plugin] 
plugins = [charPlugin,intPlugin] 

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

data Plugin = forall ps. Show ps => Plugin { 
     currentState :: ps 
    , transition :: ps -> EditorState -> Event -> (ps, EditorState) 
    } 

Я подозреваю, что пример Monoid может быть определен для типа Plugin.

Кроме того, мы могли бы думать о явно параметризуя Plugin от типа событий он принимает, как

data Plugin e = ... 

В этом случае плагин может быть сделан экземпляром Contravariant и, возможно, Divisible, а также.

И если мы идем дикие и параметризация на состояние редактора

data Plugin es e = ... 

тогда, возможно, мы могли бы найти способ «зум» данный плагин для работы в более общем состоянии, чем та, для которой он был определены.

+0

С этим немного сложно работать (я должен распаковать функцию перехода, применить он, а затем переупакуйте состояние, и функция перехода вернется в новый плагин, который будет возвращен), но в стороне от этого он работает как шарм! Благодаря! –

+0

@ Chris Penner Упаковка-распаковка может обрабатываться только одной функцией, например 'handleEvent'. Кроме того, тип «Plugin» должен иметь строгие поля, чтобы избежать утечек пространства. – danidiaz

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