惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Latest news
Latest news
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
博客园 - 司徒正美
B
Blog RSS Feed
AWS News Blog
AWS News Blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
博客园 - Franky
P
Proofpoint News Feed
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
S
Schneier on Security
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
WordPress大学
WordPress大学
The Hacker News
The Hacker News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
罗磊的独立博客
T
The Blog of Author Tim Ferriss
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog
S
Security Affairs

Oskar Wickström

Coding on Paper Catching Typos on My Website with Browser Testing The Bombadil Terminal Experiment There and Back Again: From Quickstrom to Bombadil Computer Says No: Error Reporting for LTL A Year with the Daylight Computer Finding Bugs in a Coding Agent with Lightweight DST Machine: Learning; Human: Unlearning; How I Built “The Monospace Web” A Flexible Minimalist Neovim for 2024 Statically Typed Functional Programming with Python 3.12 Specifying State Machines with Temporal Logic Clearing Weeds and Planting Trees Introducing Quickstrom: High-confidence browser testing The TodoMVC Showdown: Testing with WebCheck Time Travelling and Fixing Bugs with Property-Based Testing Property-Based Testing in a Screencast Editor, Case Study 3: Integration Testing Property-Based Testing in a Screencast Editor, Case Study 2: Video Scene Classification Property-Based Testing in a Screencast Editor, Case Study 1: Timeline Flattening Property-Based Testing in a Screencast Editor: Introduction Why I’m No Longer Taking Donations Writing a Screencast Video Editor in Haskell Declarative GTK+ Programming with Haskell Finite-State Machines, Part 2: Explicit Typed State Transitions Modeling with Haskell Data Types Automating the Build of your Technical Presentation Tagless Final Encoding of a Test Language Hyper: Elegant Weapons for a More Civilized Page Taking a Step Back from Oden Custom Formatting in HTML and LaTeX Code Listings using Pandoc Paramount Color Scheme for Vim
Motor: Finite-State Machines in Haskell
Oskar Wickström · 2017-10-27 · via Oskar Wickström

While writing my talk “Finite-state machines? Your compiler wants in!”, I have worked on porting the Idris ST library to Haskell. I call it Motor.

Motor is an experimental Haskell library for building finite-state machines with type-safe transitions and effects. I have just published it on Hackage, written a bunch of documentation with Haddock, and put the source code on GitHub.

This blog post is very similar to the Hackage documentation, and aims to pique your interest. The library and documentation will probably evolve and outdate this description, though.

State Machines using Row Types

The central finite-state machine abstraction in Motor is the MonadFSM type class. MonadFSM is an indexed monad type class, meaning that it has not one, but three type parameters:

  1. A Row of input resource states
  2. A Row of output resource states
  3. A return type (just as in Monad)

The MonadFSM parameter kinds might look a bit scary, but they state the same:

class IxMonad m =>
  MonadFSM (m :: (Row *) -> (Row *) -> * -> *) where
  ...

The rows describe how the FSM computation will affect the state of its resources when evaluated. A row is essentially a type-level map, from resource names to state types, and the FSM computation's rows describe the resource states before and after the computation.

An FSM computation newConn that adds a resource named "connection" with state Idle could have the following type:

newConn :: MonadFSM m =>
  m r ("connection" ::= Idle :| r) ()

A computation spawnTwoPlayers that adds two resources could have this type:

spawnTwoPlayers :: MonadFSM m =>
  m r ("hero2" ::= Standing :| "hero1" ::= Standing :| r) ()

Motor uses the extensible records in Data.OpenRecords, provided by the CTRex library, for row kinds. Have a look at it's documentation to learn more about the type-level operators available for rows.

Building on Indexed Monads

As mentioned above, MonadFSM is an indexed monad. It uses the definition from Control.Monad.Indexed, in the indexed package. This means that you can use ibind and friends to compose FSM computations.

-- 'c1' and 'c2' are FSM computations
c1 >>>= \_ -> c2

You can combine this with the RebindableSyntax language extension to get do-syntax for FSM programs:

test :: MonadFSM m => m Empty Empty ()
test = do
  c1
  c2
  r <- c3
  c4 r
  where
    (>>) a = (>>>=) a . const
    (>>=) = (>>>=)

See 24 Days of GHC Extensions: Rebindable Syntax for some more information on how to use RebindableSyntax.

State Actions

To make it easier to read and write FSM computation types, there is some syntax sugar available.

State actions allow you to describe state changes of named resources with a single list, as opposed two writing two rows. They also take care of matching the CTRex row combinators with the expectations of Motor, which can be tricky to do by hand.

There are three state actions:

  • Add adds a new resource.
  • To transitions the state of a resource.
  • Delete deletes an existing resource.

A mapping between a resource name is written using the :-> type operator, with a Symbol on the left, and a state action type on the right. Here are some examples:

"container" :-> Add Empty

"list" :-> To Empty NonEmpty

"game" :-> Delete GameEnded

So, the list of mappings from resource names to state actions describe what happens to each resource. Together with an initial row of resources r, and a return value a, we can declare the type of an FSM computation using the Actions type:

MonadFSM m => Actions m '[ n1 :-> a1, n2 :-> a2, ... ] r a

A computation that adds two resources could have the following type:

addingTwoThings ::
  MonadFSM m =>
  Actions m '[ "container" :-> Add Empty
              , "game" :-> Add Started
              ] r ()

Infix Operators

As an alternative to the Add, To, and Delete types, Motor offers infix operator aliases. These start with ! to indicate that they can be effectful.

The !--> operator is an infix alias for To:

useStateMachines ::
  MonadFSM m =>
  Actions m '[ "program" :-> NotCool !--> Cool ] r ()

The !+ and !- are infix aliases for mappings from resource names to Add and Delete state actions, respectively:

startNewGame ::
    MonadFSM m =>
    Actions m '[ "game" !+ Started ] r ()
endGameWhenWon ::
    MonadFSM m =>
    Actions m '[ "game" !- Won ] r ()

Row Polymorphism

Because of how CTRex works, FSM computations that have a free variable as their input row of resources, i.e. that are polymorphic in the sense of other resource states, must list all their actions in reverse order.

doFourThings ::
     Game m
  => Actions m '[ "hero2" !- Standing
                , "hero1" !- Standing
                , "hero2" !+ Standing
                , "hero1" !+ Standing
                ] r ()
doFourThings = do
  spawn hero1
  spawn hero2
  perish hero1
  perish hero

  where
    (>>) a = (>>>=) a . const
    (>>=) = (>>>=)

This is obviously quite clumsy. If anyone has ideas on how to fix or work around it, please get in touch. Had the r been replaced by Empty in the type signature above, it could have had type NoActions m Empty () instead.

Running the State Machine

The runFSM function in Motor.FSM runs an FSM computation in some base monad:

runFSM :: Monad m => FSM m Empty Empty a -> m a

FSM has instances for IxMonadTrans and a bunch of other type classes. More might be added as they are needed.

Examples

There is only one small Door example in the repository, along with some test programs. I haven’t had much time to write examples, but hopefully I will soon. The door example does feature most of the relevant concepts, though.