LPScript
Functional Smalltalk with actors, for screens that run in a browser
A message is a method call
A message sent to an actor becomes a call to the method of that name. The method returns the next state. There is no receive loop. Because of this, the runtime does not need to suspend and resume code (fibers or generators), and code that has to wait uses JavaScript's await.
No re-render conditions to write
If a handler returns the same state object, nothing is redrawn. If it returns a different one, the actor's region is redrawn. The runtime makes this decision. You do not write re-render conditions in your program.
Decimals are exact
1234.50 in source code is a Decimal, not a binary floating-point number. Division that does not divide exactly is an error. Rounding takes a number of digits and a direction. This is so that business systems do not get money wrong.
The designer's HTML is used as-is
Rows and dialogs are cloned from sample elements the designer put in the HTML. There is no HTML in the program. A selector that does not exist in the HTML is a compile error.
What it is for
Screens that run in a browser: lists, searches, detail panes, editing and saving, passing values between pages, HTTP and WebSocket communication, and animations that redraw every frame.
There is one mechanism. State lives in an actor. Button clicks, HTTP responses, and animation frames all arrive as messages. Whether to redraw is decided by whether the state changed. A ten-thousand-row grid, a breakout game, and a canvas shooter are all built with this mechanism. Nothing was added to the language for games.
The defaults are set for business data. Money is a Decimal, a date is a calendar date, and JSON numbers keep their digits.
Actor subclass: #Counter fields: (count).
Counter class >> init: n = Counter fields: { count: n }.
Counter >> inc = self with: { count: count + 1 }.
Counter >> get = self reply: count.
c := Actor spawn: (Counter init: 0).
c tell inc.
(c ask get) forced printNl. "1"
LPScript shares its design with Lambda Prelude, a language for long-running server-side services written in OCaml 5. LPScript is not a port of it. The two share three design decisions: values are immutable, behavior is shared through protocols instead of inheritance, and Maybe is used instead of nil. They share no runtime code.
What the compiler detects
A message with no handler. If Counter has no increment handler, c tell increment is a compile error. The type of an ask result is the type of the handler's reply. Sending to a reference typed only as ActorRef[a] is also a compile error, because there is no handler to check the message against.
A selector that is not in the HTML. '#lines tr' in Html clone: '#lines tr' is checked against the HTML file named by the module's page: declaration.
A become: that drops a message. When an actor replaces itself with another class, the new class must accept every message the reference's type requires. Otherwise it is a compile error.
A block passed to another actor. A block refers to variables in the scope where it was created, so it cannot be copied. Values passed between actors must be copyable, so this is a compile error.
Money, dates, and JSON
1234.50 is a Decimal. Division that is not exact is an error. Rounding takes a number of digits and a direction.
Date is a calendar date with no time and no time zone. One month after the 31st is the last day of the next month.
JSON numbers are kept as the text they arrived as, so 1234567890123456789.12 survives a round trip to the server unchanged.
Runtime environment
The browser, as ordinary ES modules. A screen is one .lps file plus the components it imports. Each screen is built separately. Type-checking a 280-line order screen reported 0.000 seconds (below the timer's resolution).
A screen is a program. The only thing two screens share is a string that one writes and the next reads exactly once. Live actors and shared object graphs are not passed between screens. As a result, the unit one person owns and the unit the compiler checks are the same. Screens can be assigned one per person, and changing one screen does not require rebuilding the others.
Heavy work runs on another thread with Worker spawn:on:. An actor reference does not record which thread the actor runs on, so the calling code does not change.
License and Contact
LPScript is proprietary software, all rights reserved. Copying, modification, or redistribution of any part of LPScript requires the prior written permission of Lambda LLC. It is not open source.
Binaries (the lpsc compiler, the language server, the runtime, and the editor extension) are provided free of charge, strictly as-is, with no warranty and no liability of any kind. You may evaluate and use the binaries at your own risk; we do not guarantee fitness for any purpose, do not provide support unless separately contracted, and accept no responsibility for damages arising from their use.
Source code is disclosed only to partner companies that have signed an NDA — the NDA and the accompanying agreement constitute the "prior written permission" required by the license. Source access is bundled with a paid engagement (joint development, customization, integration); it is not sold as a standalone product.
If you are considering LPScript for building business screens or for production use, please get in touch. We usually work together in one of three ways.
- Joint development — building the screens with your team: screens for an existing backend, or replacing the screens of an existing system.
- Customization — shared components shaped for your screens, the way they meet your designer's HTML, and a build process that fits your environment.
- Source disclosure under NDA — full source access for in-house extension, audit, and long-term maintenance.
Contact: Lambda LLC