Преобразуватели на крайни състояния в Haskell?

Чудех се дали има начин да дефинирам и да работя с датчици с ограничено състояние в Haskell по идиоматичен начин.

Можете да подходите към FST като към генератори (генерира изход от тип {x1,x2}) или като разпознаватели (предвид вход от тип {x1,x2} той разпознава ако принадлежи към рационалното отношение), или като транслатори (при дадена входна лента, той я превежда в изходна лента). Ще се промени ли представянето в зависимост от подхода?

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

Най-близкото, което можах да намеря, е библиотеката machines на Kmett, Bjarnason и Cough: https://hackage.haskell.org/package/machines

Но изглежда не мога да осъзная как да моделирам FST с Machine. Предполагам, че правилният начин да го направите би бил подобен на това как дефинират машините на Мур и Мили: дефинирайте FST като различен обект, но осигурете екземпляр на Automaton, за да можете да го използвате като машина.

Намерих и някои други опции, но те го дефинират по ясен начин (като в https://hackage.haskell.org/package/fst ). Това не ме убеждава много, тъй като се чудя дали има по-добър начин да го направя идиоматично, използвайки силните страни на системата от типове на Haskell (като как машините на Moore и Mealy са дефинирани в библиотеката 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 и обикновено се нарича "базов функтор". В пакета за машини основният функтор е 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`

Машинният трансформатор, 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
страхотно С машините на Moore и Mealy в библиотеката machines можете да създадете машина, като разгънете изрично състояние с помощта на unfoldMoore или unfoldMealy. Тези функции моделират преходните функции, които дефинират машина на Мур/Мийли, така че все още можете да ги използвате, за да конструирате машина по традиционния начин (дефиниране на състоянията, след това дефиниране на прехода и изходната функция). Има ли подобна функция, която може да бъде създадена за FSTs (т.е. Process)? - person gonzaw; 17.01.2015
comment
@gonzaw Това би било добър последващ въпрос. Този въпрос беше за това как да се моделират крайни автомати в Haskell, този въпрос щеше да бъде за това как конкретно да се unfold a 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