LPScript
Functional Smalltalk with actors, for screens that run in a browser
A message is a class
A message is a value class, and a handler is the actor method named after it. Which methods are handlers is written, not inferred. There is no receive loop, so 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.50s in source code is a Decimal, a scaled integer over BigInt, not a binary floating-point number. There is no / on Decimal: dividing takes the scale of the result and a rounding 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.
Object subclass: #Inc.
Object subclass: #Get.
Actor subclass: #Counter fields: (count).
Counter class >> init: n = Counter fields: { count: n }.
Counter >> Inc = self copyWith: { count: count + 1 }.
Counter >> Get = self reply: count.
c := Actor spawn: (Counter init: 0).
c ! (Inc fields: {}).
(c ask: (Get fields: {})) forced displayNl. "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 handler for Nope, c ! (Nope fields: {}) is a compile error: actor Counter has no handler for Nope. The type of an ask: result is the type of the handler's reply.
A handler that does not return the next state. A handler returns self, self copyWith:, self become:, or self reply:. Anything else is refused at the definition: Inc answers Int, and a handler answers the next state.
A selector that is not in the HTML. '#lines tr' in Html clone: '#lines tr' is checked against the HTML file named by the file's page: header.
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.
A child actor's reference held by anyone but its parent. When a child is restarted, only its parent is told the new reference. Any other actor holding it would send to an actor that no longer exists, so holding it is a compile error.
Money, dates, and JSON
1234.50s is a Decimal. There is no /: dividing takes the scale of the result and a rounding direction. 1234.50 without the s is a Float.
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