Helpful library updates
This commit is contained in:
@@ -1,749 +0,0 @@
|
||||
Below is the implementation handoff for replacing the current recursive/rebuilding IO small-step interpreter with an explicit machine stack, primarily to support `Reader` via `ask` and `local`, while setting up the right shape for eventual async.
|
||||
|
||||
## Goal
|
||||
|
||||
Refactor `IODriver` from this model:
|
||||
|
||||
```haskell
|
||||
stepIO :: IOPermissions -> T -> IO Step
|
||||
|
||||
data Step
|
||||
= Done T
|
||||
| Continue T
|
||||
```
|
||||
|
||||
to an explicit abstract machine:
|
||||
|
||||
```haskell
|
||||
Machine = Runtime + current action + continuation frames
|
||||
```
|
||||
|
||||
This is required because `local` is dynamically scoped. It needs to modify the Reader environment for a sub-computation, then restore the previous environment exactly when that sub-computation completes. The current “rebuild `Bind left' k`” approach has nowhere to store that restoration behavior.
|
||||
|
||||
This change should support:
|
||||
|
||||
```tricu
|
||||
ask
|
||||
local
|
||||
```
|
||||
|
||||
now, and keep the structure compatible with future async suspension/resumption.
|
||||
|
||||
Do not implement async in this pass.
|
||||
|
||||
---
|
||||
|
||||
## New action tags
|
||||
|
||||
Extend the tricu IO action language with Reader tags:
|
||||
|
||||
```tricu
|
||||
ask = _ : pair 6 t
|
||||
local = f action : pair 7 (pair f action)
|
||||
```
|
||||
|
||||
Host-side:
|
||||
|
||||
```haskell
|
||||
data Action
|
||||
= APure T
|
||||
| ABind T T
|
||||
| APutStr T
|
||||
| AGetLine
|
||||
| AReadFile T
|
||||
| AWriteFile T T
|
||||
| AAsk
|
||||
| ALocal T T
|
||||
deriving (Show)
|
||||
```
|
||||
|
||||
Recommended tag allocation:
|
||||
|
||||
```text
|
||||
0 = pure
|
||||
1 = bind
|
||||
2 = putStr
|
||||
3 = getLine
|
||||
4 = readFile
|
||||
5 = writeFile
|
||||
6 = ask
|
||||
7 = local
|
||||
```
|
||||
|
||||
State tags can come later:
|
||||
|
||||
```text
|
||||
8 = get
|
||||
9 = put
|
||||
```
|
||||
|
||||
Do not add `bindR`, `bindS`, or `bindRS` yet. Reader is being added as an effect inside the existing IO action language, so the existing IO `bind` remains the only sequencing operator.
|
||||
|
||||
---
|
||||
|
||||
## New runtime model
|
||||
|
||||
Add a runtime context:
|
||||
|
||||
```haskell
|
||||
data Runtime = Runtime
|
||||
{ rtPerms :: IOPermissions
|
||||
, rtEnv :: T
|
||||
}
|
||||
deriving (Show)
|
||||
```
|
||||
|
||||
Later this can become:
|
||||
|
||||
```haskell
|
||||
data Runtime = Runtime
|
||||
{ rtPerms :: IOPermissions
|
||||
, rtEnv :: T
|
||||
, rtState :: T
|
||||
}
|
||||
```
|
||||
|
||||
but for this pass, keep it minimal unless State is implemented at the same time.
|
||||
|
||||
Add continuation frames:
|
||||
|
||||
```haskell
|
||||
data Frame
|
||||
= BindFrame T
|
||||
| LocalFrame T
|
||||
deriving (Show)
|
||||
```
|
||||
|
||||
Frame meanings:
|
||||
|
||||
```text
|
||||
BindFrame k:
|
||||
When the current action produces value x, continue with apply k x.
|
||||
|
||||
LocalFrame oldEnv:
|
||||
When the current action produces value x, restore oldEnv, then continue with x.
|
||||
```
|
||||
|
||||
Add the machine state:
|
||||
|
||||
```haskell
|
||||
data Machine = Machine
|
||||
{ machineRuntime :: Runtime
|
||||
, machineCurrent :: T
|
||||
, machineFrames :: [Frame]
|
||||
}
|
||||
deriving (Show)
|
||||
```
|
||||
|
||||
Frames should be treated as a stack, with the head as the top:
|
||||
|
||||
```haskell
|
||||
push frame machine = machine { machineFrames = frame : machineFrames machine }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New step result
|
||||
|
||||
Replace the current `Step` with machine-oriented stepping:
|
||||
|
||||
```haskell
|
||||
data Step
|
||||
= Halt Runtime T
|
||||
| Continue Machine
|
||||
deriving (Show)
|
||||
```
|
||||
|
||||
`Halt runtime value` means the entire IO program is done.
|
||||
|
||||
`Continue machine` means the machine can take another step.
|
||||
|
||||
---
|
||||
|
||||
## Core stepping semantics
|
||||
|
||||
The central function should become:
|
||||
|
||||
```haskell
|
||||
stepMachine :: Machine -> IO Step
|
||||
```
|
||||
|
||||
It should decode `machineCurrent`.
|
||||
|
||||
### `pure`
|
||||
|
||||
When the current action is `APure value`, do not immediately halt. First inspect the frame stack.
|
||||
|
||||
Pseudo-code:
|
||||
|
||||
```haskell
|
||||
finishValue :: Machine -> T -> IO Step
|
||||
finishValue machine value =
|
||||
case machineFrames machine of
|
||||
[] ->
|
||||
pure (Halt (machineRuntime machine) value)
|
||||
|
||||
BindFrame k : rest ->
|
||||
pure (Continue machine
|
||||
{ machineCurrent = apply k value
|
||||
, machineFrames = rest
|
||||
})
|
||||
|
||||
LocalFrame oldEnv : rest ->
|
||||
let runtime' = (machineRuntime machine) { rtEnv = oldEnv }
|
||||
in pure (Continue machine
|
||||
{ machineRuntime = runtime'
|
||||
, machineCurrent = pureAction value
|
||||
, machineFrames = rest
|
||||
})
|
||||
```
|
||||
|
||||
You will need a helper:
|
||||
|
||||
```haskell
|
||||
pureAction :: T -> T
|
||||
pureAction x = Fork (ofNumber 0) x
|
||||
```
|
||||
|
||||
This is important: restoring a `LocalFrame` should not discard the value. It restores the environment and re-enters the machine as `pure value`, allowing the next frame to receive the value.
|
||||
|
||||
### `bind`
|
||||
|
||||
For:
|
||||
|
||||
```haskell
|
||||
ABind left k
|
||||
```
|
||||
|
||||
do not recursively step `left`, and do not rebuild `Bind left' k`.
|
||||
|
||||
Instead:
|
||||
|
||||
```haskell
|
||||
Continue machine
|
||||
{ machineCurrent = left
|
||||
, machineFrames = BindFrame k : machineFrames machine
|
||||
}
|
||||
```
|
||||
|
||||
This is the major refactor. Continuations move out of the tree and into the frame stack.
|
||||
|
||||
### `ask`
|
||||
|
||||
For:
|
||||
|
||||
```haskell
|
||||
AAsk
|
||||
```
|
||||
|
||||
produce the current Reader environment:
|
||||
|
||||
```haskell
|
||||
finishValue machine (rtEnv (machineRuntime machine))
|
||||
```
|
||||
|
||||
or equivalently:
|
||||
|
||||
```haskell
|
||||
Continue machine { machineCurrent = pureAction currentEnv }
|
||||
```
|
||||
|
||||
Prefer `finishValue` because it avoids an extra step.
|
||||
|
||||
### `local`
|
||||
|
||||
For:
|
||||
|
||||
```haskell
|
||||
ALocal f action
|
||||
```
|
||||
|
||||
do:
|
||||
|
||||
```haskell
|
||||
let runtime = machineRuntime machine
|
||||
oldEnv = rtEnv runtime
|
||||
newEnv = apply f oldEnv
|
||||
runtime' = runtime { rtEnv = newEnv }
|
||||
|
||||
Continue machine
|
||||
{ machineRuntime = runtime'
|
||||
, machineCurrent = action
|
||||
, machineFrames = LocalFrame oldEnv : machineFrames machine
|
||||
}
|
||||
```
|
||||
|
||||
This is the central correctness point.
|
||||
|
||||
`local` enters a scoped environment by pushing a restoration frame. When the scoped action finishes, `LocalFrame oldEnv` restores the previous environment and passes the produced value onward.
|
||||
|
||||
Nested `local` works naturally because frames stack:
|
||||
|
||||
```tricu
|
||||
local f (
|
||||
local g ask
|
||||
)
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```text
|
||||
push LocalFrame env0
|
||||
set env = f env0
|
||||
|
||||
push LocalFrame env1
|
||||
set env = g env1
|
||||
|
||||
ask returns env2
|
||||
|
||||
pop LocalFrame env1
|
||||
restore env1
|
||||
|
||||
pop LocalFrame env0
|
||||
restore env0
|
||||
```
|
||||
|
||||
### Normal IO actions
|
||||
|
||||
For host IO actions, perform the side effect and then call `finishValue`.
|
||||
|
||||
Examples:
|
||||
|
||||
```haskell
|
||||
APutStr str ->
|
||||
case decodeString str "PutStr" of
|
||||
Right s -> do
|
||||
putStr s
|
||||
finishValue machine Leaf
|
||||
Left _ ->
|
||||
finishValue machine (errResult 6)
|
||||
```
|
||||
|
||||
```haskell
|
||||
AReadFile path ->
|
||||
case decodeString path "ReadFile" of
|
||||
Right p -> do
|
||||
result <- ...
|
||||
finishValue machine result
|
||||
Left _ ->
|
||||
finishValue machine (errResult 6)
|
||||
```
|
||||
|
||||
Important: IO actions should no longer return `Done value` directly. They should return a value to the frame stack via `finishValue`.
|
||||
|
||||
---
|
||||
|
||||
## Decode changes
|
||||
|
||||
Extend `decodeAction`:
|
||||
|
||||
```haskell
|
||||
decodeAction :: T -> Either String Action
|
||||
decodeAction tree =
|
||||
case tree of
|
||||
Fork tag payload ->
|
||||
case toNumber tag of
|
||||
Right 0 -> Right (APure payload)
|
||||
|
||||
Right 1 -> case payload of
|
||||
Fork left k -> Right (ABind left k)
|
||||
_ -> Left "Invalid Bind: expected pair action continuation"
|
||||
|
||||
Right 2 -> Right (APutStr payload)
|
||||
|
||||
Right 3 -> Right AGetLine
|
||||
|
||||
Right 4 -> Right (AReadFile payload)
|
||||
|
||||
Right 5 -> case payload of
|
||||
Fork path contents -> Right (AWriteFile path contents)
|
||||
_ -> Left "Invalid WriteFile: expected pair path contents"
|
||||
|
||||
Right 6 -> Right AAsk
|
||||
|
||||
Right 7 -> case payload of
|
||||
Fork f action -> Right (ALocal f action)
|
||||
_ -> Left "Invalid Local: expected pair function action"
|
||||
|
||||
Right n -> Left $ "Unknown IO action tag: " ++ show n
|
||||
|
||||
Left err -> Left $ "Invalid action tag: " ++ err
|
||||
|
||||
_ ->
|
||||
Left $ "Invalid action tree: expected pair tag payload, got " ++ show tree
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Runner API
|
||||
|
||||
Add a new Reader-aware runner:
|
||||
|
||||
```haskell
|
||||
runIOWithEnv :: IOPermissions -> T -> T -> IO T
|
||||
runIOWithEnv perms env action = loop initialMachine
|
||||
where
|
||||
initialMachine = Machine
|
||||
{ machineRuntime = Runtime
|
||||
{ rtPerms = perms
|
||||
, rtEnv = env
|
||||
}
|
||||
, machineCurrent = action
|
||||
, machineFrames = []
|
||||
}
|
||||
|
||||
loop machine = do
|
||||
step <- stepMachine machine
|
||||
case step of
|
||||
Halt _ value -> pure value
|
||||
Continue machine' -> loop machine'
|
||||
```
|
||||
|
||||
Keep the existing API as a compatibility wrapper:
|
||||
|
||||
```haskell
|
||||
runIO :: IOPermissions -> T -> IO T
|
||||
runIO perms action =
|
||||
runIOWithEnv perms Leaf action
|
||||
```
|
||||
|
||||
If State is added in the same branch, prefer:
|
||||
|
||||
```haskell
|
||||
runIOWith :: IOPermissions -> T -> T -> T -> IO (T, T)
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
```text
|
||||
permissions
|
||||
initial reader env
|
||||
initial state
|
||||
action
|
||||
```
|
||||
|
||||
returns:
|
||||
|
||||
```text
|
||||
final result
|
||||
final state
|
||||
```
|
||||
|
||||
But if this handoff is only for Reader, use `runIOWithEnv`.
|
||||
|
||||
---
|
||||
|
||||
## Permission helpers
|
||||
|
||||
The current permission helper functions can mostly stay as-is, but they should read permissions from runtime:
|
||||
|
||||
```haskell
|
||||
let perms = rtPerms (machineRuntime machine)
|
||||
```
|
||||
|
||||
The current helpers are nested inside `stepIO`. After the refactor, either:
|
||||
|
||||
1. keep them in a `where` block under `stepMachine`, or
|
||||
2. lift them to top-level helper functions.
|
||||
|
||||
Prefer lifting pure/reusable helpers to top-level if this file is getting large:
|
||||
|
||||
```haskell
|
||||
decodeString :: T -> String -> Either String String
|
||||
canonicalizeSafe :: FilePath -> IO (Either String FilePath)
|
||||
pathAllowed :: FilePath -> [FilePath] -> IO Bool
|
||||
tryReadFile :: FilePath -> IO T
|
||||
tryWriteFile :: FilePath -> String -> IO T
|
||||
okResult :: T -> T
|
||||
errResult :: Integer -> T
|
||||
ioErrorCode :: IOException -> Integer
|
||||
```
|
||||
|
||||
This will make `stepMachine` much easier to read.
|
||||
|
||||
---
|
||||
|
||||
## `io.tri` changes
|
||||
|
||||
Add the Reader constructors:
|
||||
|
||||
```tricu
|
||||
ask = _ : pair 6 t
|
||||
local = f action : pair 7 (pair f action)
|
||||
```
|
||||
|
||||
No new bind is required.
|
||||
|
||||
Example usage:
|
||||
|
||||
```tricu
|
||||
program =
|
||||
bind ask (env :
|
||||
putStrLn env)
|
||||
```
|
||||
|
||||
Example `local` usage:
|
||||
|
||||
```tricu
|
||||
program =
|
||||
bind ask (outer :
|
||||
bind (local (env : append env "-inner")
|
||||
(bind ask (inner :
|
||||
pure inner)))
|
||||
(result :
|
||||
bind ask (after :
|
||||
pure result)))
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
|
||||
```text
|
||||
outer ask sees original env
|
||||
inner ask sees transformed env
|
||||
after ask sees original env again
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tests to add
|
||||
|
||||
Add tests around behavior, not implementation details.
|
||||
|
||||
### 1. `ask` returns initial environment
|
||||
|
||||
Program:
|
||||
|
||||
```tricu
|
||||
io (bind ask (env : pure env))
|
||||
```
|
||||
|
||||
Run with env:
|
||||
|
||||
```text
|
||||
"dev"
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
"dev"
|
||||
```
|
||||
|
||||
### 2. `local` transforms environment
|
||||
|
||||
Program:
|
||||
|
||||
```tricu
|
||||
io (
|
||||
local (env : append env "-local")
|
||||
(bind ask (env : pure env))
|
||||
)
|
||||
```
|
||||
|
||||
Initial env:
|
||||
|
||||
```text
|
||||
"root"
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
"root-local"
|
||||
```
|
||||
|
||||
### 3. `local` restores environment afterward
|
||||
|
||||
Program structure:
|
||||
|
||||
```tricu
|
||||
bind ask (before :
|
||||
bind (local transform scopedAsk) (inside :
|
||||
bind ask (after :
|
||||
pure (pair before (pair inside after)))))
|
||||
```
|
||||
|
||||
Initial env:
|
||||
|
||||
```text
|
||||
"root"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
pair "root" (pair "root-local" "root")
|
||||
```
|
||||
|
||||
### 4. nested `local` composes correctly
|
||||
|
||||
Program:
|
||||
|
||||
```tricu
|
||||
local f (
|
||||
local g ask
|
||||
)
|
||||
```
|
||||
|
||||
Initial env:
|
||||
|
||||
```text
|
||||
"root"
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```tricu
|
||||
f = x : append x "-f"
|
||||
g = x : append x "-g"
|
||||
```
|
||||
|
||||
Expected inner ask:
|
||||
|
||||
```text
|
||||
"root-f-g"
|
||||
```
|
||||
|
||||
Also verify after both locals, environment is restored by doing a final `ask`.
|
||||
|
||||
### 5. `local` result passes through bind correctly
|
||||
|
||||
Program:
|
||||
|
||||
```tricu
|
||||
bind
|
||||
(local transform (pure "value"))
|
||||
(x : pure x)
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
"value"
|
||||
```
|
||||
|
||||
This catches a common bug where `LocalFrame` restores env but loses the value.
|
||||
|
||||
### 6. IO still works through bind
|
||||
|
||||
Existing IO tests should continue passing unchanged through `runIO`.
|
||||
|
||||
### 7. IO inside local
|
||||
|
||||
Program:
|
||||
|
||||
```tricu
|
||||
local transform (
|
||||
bind ask (env :
|
||||
bind (putStrLn env) (_ :
|
||||
pure env))
|
||||
)
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
prints transformed env
|
||||
returns transformed env
|
||||
```
|
||||
|
||||
Then optionally ask after local to verify restoration.
|
||||
|
||||
---
|
||||
|
||||
## Invariants to preserve
|
||||
|
||||
The implementation should maintain these invariants:
|
||||
|
||||
```text
|
||||
1. The current action is always the next instruction to evaluate.
|
||||
|
||||
2. The frame stack contains all pending continuations and cleanup scopes.
|
||||
|
||||
3. Bind does not recursively step its left side.
|
||||
It pushes BindFrame and switches current to the left action.
|
||||
|
||||
4. local does not run its action to completion.
|
||||
It pushes LocalFrame and switches current to the scoped action.
|
||||
|
||||
5. Only LocalFrame restores Reader environment.
|
||||
|
||||
6. State, when added later, should not be restored by LocalFrame.
|
||||
|
||||
7. Existing runIO behavior remains source-compatible.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common failure modes
|
||||
|
||||
The likely bugs are:
|
||||
|
||||
```text
|
||||
Bug: local leaks environment.
|
||||
Cause: setting rtEnv but never restoring it.
|
||||
Fix: push LocalFrame oldEnv and restore in finishValue.
|
||||
|
||||
Bug: local restores environment but loses result.
|
||||
Cause: popping LocalFrame and halting directly.
|
||||
Fix: after restoration, continue with pureAction value.
|
||||
|
||||
Bug: bind continuations run under the wrong env.
|
||||
Cause: LocalFrame and BindFrame pop order is wrong.
|
||||
Fix: use stack head as top. Push LocalFrame when entering local; push BindFrame when entering bind. Pop exactly one frame when a value is produced.
|
||||
|
||||
Bug: existing IO bind tests fail.
|
||||
Cause: IO actions halt instead of passing result to finishValue.
|
||||
Fix: every completed primitive action should call finishValue.
|
||||
|
||||
Bug: nested binds still rebuild trees.
|
||||
Cause: old ABind logic left in place.
|
||||
Fix: ABind should only push BindFrame and switch current to left.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Async relevance, but not implementation
|
||||
|
||||
This machine representation is intentionally compatible with async.
|
||||
|
||||
A future scheduler can store:
|
||||
|
||||
```haskell
|
||||
Machine runtime current frames
|
||||
```
|
||||
|
||||
when a task blocks, then resume it later by restoring the same `Machine`.
|
||||
|
||||
Do not implement any of these now:
|
||||
|
||||
```haskell
|
||||
AFork
|
||||
AAwait
|
||||
ASleep
|
||||
TaskId
|
||||
Scheduler
|
||||
Runnable queue
|
||||
Blocked table
|
||||
```
|
||||
|
||||
But avoid designs that would make future suspension impossible, especially recursive “run sub-computation to completion” implementations of `local`. The point of the frame machine is that every effect remains small-step and resumable.
|
||||
|
||||
---
|
||||
|
||||
## Recommended implementation order
|
||||
|
||||
1. Add `Runtime`, `Frame`, and `Machine`.
|
||||
2. Add `pureAction`.
|
||||
3. Replace `Step` with `Halt Runtime T | Continue Machine`.
|
||||
4. Implement `finishValue`.
|
||||
5. Rewrite `ABind` to push `BindFrame`.
|
||||
6. Rewrite existing primitive IO actions to call `finishValue`.
|
||||
7. Add `AAsk` and `ALocal`.
|
||||
8. Add `runIOWithEnv`.
|
||||
9. Rewrite `runIO` as a wrapper.
|
||||
10. Add `ask` and `local` to `io.tri`.
|
||||
11. Add Reader behavior tests.
|
||||
12. Run all existing IO tests and confirm no regressions.
|
||||
|
||||
The key handoff instruction is: implement `local` as a continuation frame, not as a recursive nested run. This keeps the interpreter genuinely small-step and gives the eventual async runtime the exact representation it will need for suspension and resumption.
|
||||
262
notes/stdlib-todo.md
Normal file
262
notes/stdlib-todo.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Standard Library TODO / Expansion Plan
|
||||
|
||||
> Foundational expansions that improve safety, composability, and ergonomics across the entire tricu ecosystem.
|
||||
|
||||
---
|
||||
|
||||
## 1. Extract a `maybe.tri` / Option layer
|
||||
|
||||
**Motivation:** `head`, `tail`, `last`, `nth`, `bytesHead`, and `bytesTail` currently return `t` on failure, which is ambiguous because `t` is also a valid tree value. A proper option layer makes all of these APIs safer and self-describing.
|
||||
|
||||
**Additions:**
|
||||
|
||||
```tricu
|
||||
nothing = t
|
||||
just = x : t x
|
||||
|
||||
matchMaybe = nothingCase justCase maybe :
|
||||
triage
|
||||
nothingCase
|
||||
justCase
|
||||
(_ _ : nothingCase)
|
||||
maybe
|
||||
|
||||
maybe = default f m : matchMaybe default f m
|
||||
maybeMap = f m : matchMaybe nothing (x : just (f x)) m
|
||||
maybeBind = m f : matchMaybe nothing f m
|
||||
maybeOr = default m : matchMaybe default id m
|
||||
maybe? = matchMaybe false (_ : true)
|
||||
```
|
||||
|
||||
**Then update existing list/bytes primitives:**
|
||||
|
||||
```tricu
|
||||
headMaybe
|
||||
lastMaybe
|
||||
nthMaybe
|
||||
bytesHead
|
||||
bytesTail
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Move `Result` combinators from `binary.tri` into `base.tri`
|
||||
|
||||
**Motivation:** `Result` is defined in `base.tri`, but `mapResult` and `bindResult` currently live in `binary.tri`. This makes them unavailable to IO, socket, and file helpers unless every consumer imports binary parsing.
|
||||
|
||||
**Move / add to `base.tri`:**
|
||||
|
||||
```tricu
|
||||
mapResult = f result :
|
||||
matchResult
|
||||
(code rest : err code rest)
|
||||
(value rest : ok (f value) rest)
|
||||
result
|
||||
|
||||
bindResult = result f :
|
||||
matchResult
|
||||
(code rest : err code rest)
|
||||
(value rest : f value rest)
|
||||
result
|
||||
|
||||
resultOr = default result :
|
||||
matchResult
|
||||
(_ _ : default)
|
||||
(value _ : value)
|
||||
result
|
||||
|
||||
resultMapErr = f result :
|
||||
matchResult
|
||||
(code rest : err (f code) rest)
|
||||
(value rest : ok value rest)
|
||||
result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Add basic numeric comparison and arithmetic
|
||||
|
||||
**Motivation:** Without comparison and arithmetic, list slicing, parser limits, counters, socket loops, and CLI utilities are awkward or impossible.
|
||||
|
||||
**Priority order:**
|
||||
|
||||
1. `isZero?`
|
||||
2. `add`
|
||||
3. `sub`
|
||||
4. `lt?`
|
||||
5. `lte?`
|
||||
6. `mul`
|
||||
|
||||
Even simple Peano / binary-tree versions unlock a huge amount of functionality.
|
||||
|
||||
---
|
||||
|
||||
## 4. Expand `list.tri` with safer and more complete primitives
|
||||
|
||||
**Motivation:** `reverse` currently uses `append` recursively, which is quadratic. Many common operations (`take`, `drop`, `concatMap`) are missing entirely.
|
||||
|
||||
**High-impact additions:**
|
||||
|
||||
```tricu
|
||||
take
|
||||
drop
|
||||
splitAt
|
||||
concatMap
|
||||
find
|
||||
partition
|
||||
zipWith
|
||||
```
|
||||
|
||||
**Performance fix:**
|
||||
|
||||
```tricu
|
||||
reverse_ = y (self xs acc :
|
||||
matchList
|
||||
acc
|
||||
(h r : self r (pair h acc))
|
||||
xs)
|
||||
|
||||
reverse = xs : reverse_ xs t
|
||||
```
|
||||
|
||||
This is low-effort, high-impact because `reverse` is already used by `binary.tri` and `conversions.tri`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Add string aliases / helpers
|
||||
|
||||
**Motivation:** IO and CLI programs almost always need line/string manipulation. Socket protocols are also easier with canonical string utilities.
|
||||
|
||||
**Highest value:**
|
||||
|
||||
```tricu
|
||||
startsWith?
|
||||
contains?
|
||||
lines
|
||||
unlines
|
||||
```
|
||||
|
||||
Also consider:
|
||||
|
||||
```tricu
|
||||
strLength
|
||||
strAppend
|
||||
strEq?
|
||||
strEmpty?
|
||||
words
|
||||
unwords
|
||||
endsWith?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Expand `binary.tri` into a small parser-combinator layer
|
||||
|
||||
**Motivation:** `binary.tri` already has `readU8`, `readBytes`, `expectBytes`, etc. A thin combinator layer makes binary parsing much more ergonomic and reusable for protocols/file formats.
|
||||
|
||||
**Suggested additions:**
|
||||
|
||||
```tricu
|
||||
pureParser = value bs : ok value bs
|
||||
failParser = code bs : err code bs
|
||||
|
||||
mapParser = mapResult
|
||||
bindParser = bindResult
|
||||
thenParser = p q : bindResult p (_ : q)
|
||||
orParser = p q bs :
|
||||
matchResult
|
||||
(_ _ : q bs)
|
||||
(value rest : ok value rest)
|
||||
(p bs)
|
||||
```
|
||||
|
||||
Then common parsers:
|
||||
|
||||
```tricu
|
||||
readWhile
|
||||
readUntil
|
||||
readRemaining
|
||||
peekU8
|
||||
eof?
|
||||
expectAscii
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Add endian / int conversion helpers
|
||||
|
||||
**Motivation:** `readU16BEBytes` and `readU32BEBytes` return raw byte lists. Either rename them or add actual numeric decoding.
|
||||
|
||||
**Suggested additions:**
|
||||
|
||||
```tricu
|
||||
u16BE
|
||||
u16LE
|
||||
u32BE
|
||||
u32LE
|
||||
|
||||
readU16BE
|
||||
readU16LE
|
||||
readU32BE
|
||||
readU32LE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Add resource-safe IO helpers
|
||||
|
||||
**Motivation:** Sockets, files, and process-like resources need predictable cleanup.
|
||||
|
||||
```tricu
|
||||
finally = action cleanup :
|
||||
bind action (result :
|
||||
bind cleanup (_ :
|
||||
pure result))
|
||||
|
||||
bracket = acquire release use :
|
||||
bind acquire (resource :
|
||||
bind (use resource) (result :
|
||||
bind (release resource) (_ :
|
||||
pure result)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Add socket server loops
|
||||
|
||||
**Motivation:** Almost every socket example repeats the same `forever` + `withAccepted_` scaffolding.
|
||||
|
||||
```tricu
|
||||
serveForever = server handler :
|
||||
forever
|
||||
(withAccepted_ server
|
||||
(err : pure t)
|
||||
(client peer :
|
||||
fork (handler client peer)))
|
||||
```
|
||||
|
||||
Also consider:
|
||||
|
||||
```tricu
|
||||
connectTo = addr port :
|
||||
onOk_ socket (client :
|
||||
onOk_ (connect client addr port) (_ :
|
||||
pure (ok client)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Add a curated `prelude.tri`
|
||||
|
||||
**Motivation:** As libraries grow, users need a stable starting point.
|
||||
|
||||
```tricu
|
||||
!import "base.tri" !Local
|
||||
!import "maybe.tri" !Local
|
||||
!import "list.tri" !Local
|
||||
!import "bytes.tri" !Local
|
||||
!import "conversions.tri" !Local
|
||||
```
|
||||
|
||||
This gives a standard baseline without importing IO / socket / binary by default.
|
||||
|
||||
Reference in New Issue
Block a user