Конечные преобразователи состояний в Haskell?

Мне было интересно, есть ли способ определить и работать с преобразователями конечных состояний в Haskell идиоматическим способом.

Вы можете рассматривать FST как генераторы (он генерирует выходные данные типа {x1,x2}) или как распознаватели (при наличии входных данных типа {x1,x2} он распознает это, если оно принадлежит рациональному отношению), или как трансляторы (имея входную ленту, он переводит ее в выходную ленту). Изменится ли представление в зависимости от подхода?

Можно ли также смоделировать FST таким образом, чтобы вы могли создать его, указав правила перезаписи? Например, создание DSL для моделирования правил перезаписи, а затем создание функции createFST :: [Rule] -> FST.

Самое близкое, что я смог найти, это machines библиотека Кметта, Бьярнасона и Кафа: https://hackage.haskell.org/package/machines

Но я не могу понять, как смоделировать FST с помощью Machine. Я полагаю, что правильный способ сделать это будет аналогичен тому, как они определяют машины Мура и Мили: определить FST как другую сущность, но предоставить экземпляр Automaton, чтобы иметь возможность использовать его как машину.

Я также нашел некоторые другие параметры, но они определяют их простым способом (например, в https://hackage.haskell.org/package/fst ). Меня это не очень убеждает, так как мне интересно, есть ли лучший способ сделать это идиоматически, используя сильные стороны системы типов Haskell (например, как машины Мура и Мили определены в библиотеке machines).


person gonzaw    schedule 17.01.2015    source источник
comment
Этот вопрос нуждается в редактировании, чтобы соответствовать теме StackOverflow. Явный запрос рекомендации по библиотеке или ресурсу не по теме, но описание конкретной проблемы и поиск решений (которые вполне могут включать библиотеки) допустимы. Спрашивать, как лучше сделать что-то, обычно слишком широко или основываться на мнении. Если вы спросите a, как что-то сделать, вы получите качественные ответы, но с менее самоуверенным обсуждением их достоинств.   -  person Cirdec    schedule 17.01.2015


Ответы (1)


Машина Mealy попеременно считывает a из потока входов a и выводит b в поток выходов. Сначала он читает, а затем выводит один раз после каждого чтения.

newtype Mealy a b = Mealy { runMealy :: a -> (b, Mealy a b) }

Машина Moore поочередно выводит b потоку выходов и считывает вход a из потока входов. Он начинается с вывода b, а затем считывается один раз после каждого вывода.

data Moore a b = Moore b (a -> Moore a b)

FST либо читает со своего входа, записывает на свой выход, либо останавливается. Он может читать столько раз подряд, сколько хочет, или писать столько раз подряд, сколько хочет.

data FST a b
    = Read  (a -> FST a b)
    | Write (b,   FST a b)
    | Stop

Эквивалентом FST от компьютеров является Process. Его определение немного расплывчато. Чтобы упростить обсуждение, мы пока забудем о Process и исследуем его изнутри.

Базовый функтор

Чтобы описать, что такое Process, мы сначала заметим закономерность во всех трех машинах. Каждый из них рекурсивно обращается к самому себе для того, «что делать дальше». Мы собираемся заменить «что делать дальше» на любой тип next. Машина Mealy, сопоставляя вход с выходом, также обеспечивает работу машины next.

newtype MealyF a b next = MealyF { runMealyF :: a -> (b, next) }

Машина Moore после вывода и запроса ввода вычисляет машину next для запуска.

data MooreF a b next = MooreF b (a -> next)

Мы можем написать FST таким же образом. Когда мы Read из ввода, мы выясним, что делать next в зависимости от ввода. Когда мы Write перейдем к выводу, мы также сообщим, что делать next после вывода. Когда мы Stop дальше делать нечего.

data FSTF a b next
    = Read  (a -> next)
    | Write (b,   next)
    | Stop

Этот шаблон устранения явной рекурсии неоднократно появляется в коде Haskell и обычно называется «базовым функтором». В пакете machine базовым функтором является Step. По сравнению с нашим кодом, Step переименовал переменную типа для вывода в o, что делать дальше с r, читать в Await и записывать в Yield.

data Step k o r
  = forall t. Await (t -> r) (k t) r
  | Yield o r
  | Stop

Awaiting немного сложнее, чем Read, потому что Machine может читать из нескольких источников. Для Processes, которые могут читать только из одного источника, k — это Is применяется к определенному типу, который является доказательством второго типа Is первого типа. Для чтения Process входы a, k будут Is a.

data Step (Is a) o r
  = forall t. Await (t -> r) (Is a t) r
  | Yield o r
  | Stop

Экзистенциальная количественная оценка forall t. — это деталь реализации для работы с Sources. Увидев, что a ~ t это становится.

data Step (Is a) o r
  = forall t ~ a. Await (t -> r) Refl r
  | Yield o r
  | Stop

Если мы объединим t с a и удалим конструктор Refl, который всегда один и тот же, это будет выглядеть как наш FSTF.

data Step (Is a) o r
  = Await (a -> r) r
  | Yield  o    r
  | Stop

Дополнительный r для того, что делать дальше в Await, — это то, что делать дальше, когда больше нет входных данных.

Машина трансформер `МашинаТ`

Преобразователь машины, MachineT, делает Step почти таким же, как наш FST. В нем говорится: «Машина, работающая над некоторой монадой m, — это то, что нужно сделать в этой монаде, чтобы получить следующую Step. next вещь, которую нужно сделать после каждого шага, — это еще одна MachineT».

newtype MachineT m k o = MachineT { runMachineT :: m (Step k o (MachineT m k o)) }

В целом, специализированный для наших типов, это выглядит как

newtype MachineT m (Is a) o = 
    MachineT m (
        Await (a -> MachineT m (Is a) o) (MachineT m (Is a) o)
      | Yield  o   (MachineT m (Is a) o)
      | Stop
    )

Machine это чистый MachineT.

type Machine k o = forall m. Monad m => MachineT m k o

Универсальная квантификация по всем Monads m — это еще один способ сказать, что вычисления не нуждаются ни в чем из базового Monad. Это можно увидеть, заменив Identity для m.

type Machine k o = 
    MachineT Identity (
        Await (a -> MachineT Identity k o) (MachineT Identity k o)
      | Yield  o   (MachineT Identity k o)
      | Stop
    )

Процессы

Process или ProcessT — это Machine или MachineT, которые считывают только один тип ввода a, Is a.

type Process    a b = Machine    (Is a) b
type ProcessT m a b = MachineT m (Is a) b

Process имеет следующую структуру после удаления всех промежуточных конструкторов, которые всегда одинаковы. Эта структура точно такая же, как наша FST, за исключением того, что в нее добавлено «что делать дальше» в случае, если ввода больше нет.

type Process a b =
    Await (a -> Process a b) (Process a b)
  | Yield  b   (Process a b)
  | Stop

Вариант ProcessT окружен m, так что он может действовать в монаде на каждом шаге.

Process модели датчиков состояния.

person Cirdec    schedule 17.01.2015
comment
Фантастический machines учебник, спасибо. Я давно хотел попытаться понять эту библиотеку. Интересно, есть ли способ как-то прикрепить это обсуждение к официальной документации. - person Daniel Wagner; 17.01.2015
comment
Потрясающий. С машинами Мура и Мили в библиотеке machines вы можете создать машину, развернув явное состояние с помощью unfoldMoore или unfoldMealy. Эти функции моделируют функции перехода, которые определяют машину Мура/Мили, поэтому их можно использовать для построения машины традиционным способом (определение состояний, затем определение функции перехода и вывода). Есть ли аналогичная функция, которую можно создать для FST (например, Process)? - person gonzaw; 17.01.2015
comment
@gonzaw Это был бы хороший дополнительный вопрос. Этот вопрос был о том, как моделировать конечные автоматы в Haskell, этот вопрос будет о том, как конкретно unfold и Process использовать машинную библиотеку. Вопрос будет лучше, если вы сможете указать конкретную проблему, например, как развернуть Process из data State = RepeatNext Word8 | SayAgain Word8 Word8 и step (RepeatNext n) = Await (x -> SayAgain n x) Stop; step (SayAgain 0 _) = Await (n -> RepeatNext n) Stop; step (SayAgain n x) = Yield x (SayAgain (n-1) x). - person Cirdec; 17.01.2015
comment
В порядке. Сначала я попробую поиграться с Process, чтобы посмотреть, смогу ли я понять это. Если нет, я задам дополнительный вопрос. Тогда я отмечу это как ответ. - person gonzaw; 17.01.2015