on master help sign in
49 files changed+1114−317
since splitsnapshots
unifiedsplit
.gitignore+1 LICENSE+7 README.md+11 dev/elm-live.sh → dev/sink-elm-live.sh+1−1 dev/vanilla-elm-live.sh+7 docs/sink.md+101 elm.json+11−2 example/script.js+16−1 example/src/Sink.elm+199 example/src/Main.elm → example/src/Vanilla.elm+21−16 example/journal/app/journal.hoon → example/urbit/app/journal.hoon+26−19 example/journal/desk.bill → example/urbit/desk.bill example/journal/desk.docket-0 → example/urbit/desk.docket-0 example/journal/gen/journal/add.hoon → example/urbit/gen/journal/add.hoon example/journal/lib/agentio.hoon → example/urbit/lib/agentio.hoon example/journal/lib/dbug.hoon → example/urbit/lib/dbug.hoon example/journal/lib/default-agent.hoon → example/urbit/lib/default-agent.hoon example/journal/lib/docket.hoon → example/urbit/lib/docket.hoon example/journal/lib/journal.hoon → example/urbit/lib/journal.hoon example/urbit/lib/noun-diff.hoon+227 example/urbit/lib/sink.hoon+26 example/journal/lib/skeleton.hoon → example/urbit/lib/skeleton.hoon example/journal/mar/bill.hoon → example/urbit/mar/bill.hoon example/journal/mar/docket-0.hoon → example/urbit/mar/docket-0.hoon example/journal/mar/hoon.hoon → example/urbit/mar/hoon.hoon example/journal/mar/jam.hoon → example/urbit/mar/jam.hoon example/journal/mar/journal/action.hoon → example/urbit/mar/journal/action.hoon example/journal/mar/journal/update.hoon → example/urbit/mar/journal/update.hoon example/journal/mar/json.hoon → example/urbit/mar/json.hoon example/journal/mar/kelvin.hoon → example/urbit/mar/kelvin.hoon example/journal/mar/mime.hoon → example/urbit/mar/mime.hoon example/journal/mar/noun.hoon → example/urbit/mar/noun.hoon example/journal/mar/txt-diff.hoon → example/urbit/mar/txt-diff.hoon example/journal/mar/txt.hoon → example/urbit/mar/txt.hoon example/journal/sur/docket.hoon → example/urbit/sur/docket.hoon example/journal/sur/journal.hoon → example/urbit/sur/journal.hoon example/journal/sys.kelvin → example/urbit/sys.kelvin src/BitWriter.elm+5−5 src/Ur.elm+2−2 src/Ur/Constructor.elm+2−2 src/Ur/Deconstructor.elm+211−218 src/Ur/NounDiff.elm+115 src/Ur/Phonemic.elm+1−1 src/Ur/Requests.elm+1−1 src/Ur/Run.elm+80−23 src/Ur/Sub.elm+21−6 src/Ur/Sub/Internal.elm+3−1 src/Ur/Uw.elm+8−8 tests/Test/Urbit.elm+11−11
.gitignore+1
@@ -1,2 +1,3 @@
11elm-stuff
22elm.js
3+zod
LICENSEnew file+7
@@ -0,0 +1,7 @@
1+Copyright 2023 Ilia Kostiuchenko
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.md+11
@@ -4,8 +4,19 @@
44
55## Usage
66
7+### Demo
8+
9+[![DEMO](https://img.youtube.com/vi/tynCzifvIzE/0.jpg)](https://www.youtube.com/watch?v=tynCzifvIzE)
10+
11+
12+## Getting started
13+
14+The easiest way of using this library is by using the [airlock template](https://github.com/ilyakooo0/airlock-template).
15+
716### FFI
817
18+This is for setting up this library from scratch.
19+
920Urbit subscriptions require [server-side event (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
1021Unfortunately, Elm does not provide a native implementation.
1122This means that we will need to use [JS FFI (aka ports)](https://guide.elm-lang.org/interop/ports.html).
dev/elm-live.sh → dev/sink-elm-live.shrenamed+1−1
@@ -4,4 +4,4 @@ set -e
44
55cd "$(dirname "$0")/../example"
66
7−nix run nixpkgs#elmPackages.elm-live -- src/Main.elm --start-page=index.html -- --output=elm.js --debug "$@"
7+nix run nixpkgs#elmPackages.elm-live -- src/Sink.elm --start-page=index.html -- --output=elm.js --debug "$@"
dev/vanilla-elm-live.shnew file+7
@@ -0,0 +1,7 @@
1+#!/bin/sh
2+
3+set -e
4+
5+cd "$(dirname "$0")/../example"
6+
7+nix run nixpkgs#elmPackages.elm-live -- src/Vanilla.elm --start-page=index.html -- --output=elm.js --debug "$@"
docs/sink.mdnew file+101
@@ -0,0 +1,101 @@
1+# %sink
2+
3+%sink is a state replication system between your %gall agent and your Elm airlock.
4+
5+%sink allows you to avoid having to manually write subscriptions which send semantic diffs between your %gall agent and the frontend.
6+
7+## How it works
8+
9+%sink consists of two parts:
10+1. The %sink library you import into your %gall agent and call appropriate functions at the right points in your agent lifecycle. More on this in a bit.
11+2. The `Ur.Sub.sink` urbit subscription which reconciles the state and hands you the latest version of %gall agent state that you can then `Deconstruct` into Elm data structures.
12+
13+The whole system works by saving the previous version your %gall agent state and diffing the raw nouns that compose your old state and your new state.
14+On first initialization of the %sink system the whole state you supplied is sent to the frontend. Any subsequent changes are sent as a diff between your old state and your new state.
15+
16+## How to use it
17+
18+### The %gall agent part
19+
20+#### Copying library files
21+
22+In order to use %sink you have to copy the following two files into your agent desk into the `lib` directory:
23+1. [example/urbit/lib/noun-diff.hoon](../example/urbit/lib/noun-diff.hoon) -- contains the algorithm for diffing raw nouns.
24+2. [example/urbit/lib/sink.hoon](../example/urbit/lib/sink.hoon) -- contains the logic of syncing the state between your agent and your frontend.
25+
26+#### Adding sync points into your agent
27+
28+First you need to import the %sink library into your agent:
29+
30+```hoon
31+/+ *sink
32+```
33+
34+Next you need to initialize the sink. Put the following declaration somewhere before your agent door:
35+
36+```hoon
37+::
38+:: A deferred expression of the state you want to sync to the frontend.
39+=* entries (tap:j-orm journal.stat)
40+::
41+:: Replace /sync with whatever path you want to use for syncing your state.
42+=/ snik (sink ~[/sync])
43+::
44+:: Next you initialize your sink with your initial agent state.
45+=/ sink (snik entries)
46+```
47+
48+You can have multiple sinks in the same application to sync different parts of your state on different paths.
49+
50+Don't forget to reinitialize your `sink` when your restore your agent state in the `++on-load` arm:
51+
52+```hoon
53+++ on-load
54+ |= old-vase=vase
55+ ^- (quip card _this)
56+ =/ state !<(versioned-state old-vase)
57+ ::
58+ :: the `sink (snik entries)` is the important bit.
59+ `this(state state, sink (snik entries))
60+```
61+
62+Lastly, you need to send sink updates whenever you change your state. Most likely this will be in your `++on-poke` arm:
63+
64+```hoon
65+::
66+:: This line generates a `card` that you need to pass to arvo and updates
67+:: the `sink` to reference the latest state of your agent.
68+=^ card sink (sync:sink entries)
69+```
70+
71+You can look at the [journal.hoon](../example/urbit/app/journal.hoon) for a full example.
72+
73+#### Adding sink the the frontend
74+
75+For your frontend to recieve %sink updates you need to pass the result of calling `Ur.Sub.sink` to the `urbitSubscriptions` field of `Ur.Run.application` or similar function from `Ur.Run`:
76+
77+```elm
78+main : Ur.Run.Program Model Msg
79+main =
80+ Ur.Run.application
81+ {
82+ -- ...
83+ urbitSubscriptions =
84+ -- ...
85+ Ur.Sub.sink
86+ { ship = ship
87+ , app = "journal"
88+ , path = [ "sync" ]
89+ , deconstructor =
90+ D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b )))
91+ |> D.map GotListings
92+ }
93+ -- ...
94+ }
95+```
96+
97+In the `deconstructor` field you specify a `Deconstructor` the deconstructs _the whole_ state that is being synced form the %gall agent.
98+
99+You do not have to deal with diffs. It is handled automatically
100+
101+You can look at [example/src/Sink.elm](../example/src/Sink.elm) for a full example.
elm.json+11−2
@@ -3,9 +3,18 @@
33 "name": "ilyakooo0/airlock",
44 "summary": "Urbit airlock",
55 "license": "MIT",
6− "version": "1.0.0",
6+ "version": "1.1.1",
77 "exposed-modules": [
8− "Ur", "Ur.Cmd", "Ur.Sub", "Ur.Constructor", "Ur.Deconstructor", "Ur.Phonemic", "Ur.Da", "Ur.Uw", "Ur.Run", "Ur.Types"
8+ "Ur",
9+ "Ur.Cmd",
10+ "Ur.Sub",
11+ "Ur.Constructor",
12+ "Ur.Deconstructor",
13+ "Ur.Phonemic",
14+ "Ur.Da",
15+ "Ur.Uw",
16+ "Ur.Run",
17+ "Ur.Types"
918 ],
1019 "elm-version": "0.19.0 <= v < 0.20.0",
1120 "dependencies": {
example/script.js+16−1
@@ -2,7 +2,22 @@ import { fetchEventSource } from "/fetch-event-source.js"
22
33const uid = `${Math.floor(Date.now() / 1000)}-${Math.random()}`;
44
5−let app = Elm.Main.init({ node: document.getElementById("elm"), flags: { uid } });
5+function searchForInit(obj) {
6+ if (obj.init) {
7+ return obj.init;
8+ } else {
9+ for (let key in obj) {
10+ if (obj.hasOwnProperty(key)) {
11+ const result = searchForInit(obj[key]);
12+ if (result) {
13+ return result;
14+ }
15+ }
16+ }
17+ }
18+}
19+
20+let app = searchForInit(Elm)({ node: document.getElementById("elm"), flags: { uid } });
621
722app.ports.createEventSource.subscribe((url) => {
823 fetchEventSource(url, {
example/src/Sink.elmnew file+199
@@ -0,0 +1,199 @@
1+port module Sink exposing (main)
2+
3+import BigInt exposing (BigInt)
4+import Browser exposing (Document)
5+import Element exposing (..)
6+import Element.Border as Border
7+import Heroicons.Outline
8+import Json.Decode as JD
9+import Task
10+import Time
11+import Ur
12+import Ur.Cmd
13+import Ur.Constructor as C
14+import Ur.Deconstructor as D
15+import Ur.Run
16+import Ur.Sub
17+import Widget
18+import Widget.Icon as Icon
19+import Widget.Material as Material
20+
21+
22+url : String
23+url =
24+ "http://localhost:8080"
25+
26+
27+main : Ur.Run.Program Model Msg
28+main =
29+ Ur.Run.application
30+ { init =
31+ \_ _ ->
32+ ( { error = ""
33+ , entries = Nothing
34+ , newEntry = ""
35+ , shipName = Nothing
36+ }
37+ , Cmd.batch
38+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
39+ |> Cmd.map (result (Debug.toString >> Error) (always Noop))
40+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
41+ ]
42+ |> Ur.Cmd.cmd
43+ )
44+ , update = update
45+ , view = view
46+ , subscriptions = always Sub.none
47+ , createEventSource = createEventSource
48+ , urbitSubscriptions =
49+ \{ shipName } ->
50+ case shipName of
51+ Just ship ->
52+ Ur.Sub.batch
53+ [ Ur.Sub.sink
54+ { ship = ship
55+ , app = "journal"
56+ , path = [ "sync" ]
57+ , deconstructor =
58+ D.list (D.cell D.bigint D.cord)
59+ |> D.map GotListings
60+ }
61+ ]
62+
63+ _ ->
64+ Ur.Sub.none
65+ , onEventSourceMsg = onEventSourceMessage
66+ , onUrlChange = \_ -> Noop
67+ , onUrlRequest = \_ -> Noop
68+ , urbitUrl = \_ -> url
69+ }
70+
71+
72+type alias Model =
73+ { error : String
74+ , entries : Maybe (List ( BigInt, String ))
75+ , newEntry : String
76+ , shipName : Maybe String
77+ }
78+
79+
80+type Msg
81+ = Noop
82+ | Error String
83+ | GotListings (List ( BigInt, String ))
84+ | UpdateNewEntry String
85+ | DeleteEntry BigInt
86+ | AddEntry String
87+ | RunCmd (Ur.Cmd.Cmd Msg)
88+ | GotShipName String
89+
90+
91+update : Msg -> Model -> ( Model, Ur.Cmd.Cmd Msg )
92+update msg model =
93+ case msg of
94+ Noop ->
95+ ( model, Ur.Cmd.none )
96+
97+ Error err ->
98+ ( { model | error = err }, Ur.Cmd.none )
99+
100+ GotListings entries ->
101+ ( { model | entries = Just entries }, Ur.Cmd.none )
102+
103+ UpdateNewEntry txt ->
104+ ( { model | newEntry = txt }, Ur.Cmd.none )
105+
106+ DeleteEntry id ->
107+ ( model
108+ , Ur.Cmd.poke
109+ { ship = "~zod"
110+ , agent = "journal"
111+ , mark = "journal-action"
112+ , noun = C.cell (C.cord "del") (C.bigint id)
113+ }
114+ )
115+
116+ AddEntry txt ->
117+ ( { model | newEntry = "" }
118+ , Time.now
119+ |> Task.perform
120+ (\time ->
121+ Ur.Cmd.poke
122+ { ship = "~zod"
123+ , agent = "journal"
124+ , mark = "journal-action"
125+ , noun = C.cell (C.cord "add") (C.cell (time |> Time.posixToMillis |> BigInt.fromInt |> C.bigint) (C.cord txt))
126+ }
127+ |> RunCmd
128+ )
129+ |> Ur.Cmd.cmd
130+ )
131+
132+ RunCmd cmd ->
133+ ( model, cmd )
134+
135+ GotShipName name ->
136+ ( { model | shipName = Just name }, Ur.Cmd.none )
137+
138+
139+view : Model -> Document Msg
140+view model =
141+ { body =
142+ [ layout []
143+ ([ el [ alignTop ] (text model.error)
144+ , row [ spacing 8 ]
145+ [ Widget.textInput (Material.textInput Material.defaultPalette)
146+ { chips = []
147+ , text = model.newEntry
148+ , placeholder = Nothing
149+ , label = "New entry"
150+ , onChange = UpdateNewEntry
151+ }
152+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
153+ { text = "submit"
154+ , icon = Icon.elmHeroicons Heroicons.Outline.check
155+ , onPress = AddEntry model.newEntry |> Just
156+ }
157+ ]
158+ , model.entries
159+ |> Maybe.withDefault []
160+ |> List.map
161+ (\( id, txt ) ->
162+ row
163+ [ Border.rounded 10
164+ , Border.shadow { offset = ( 0, 5 ), size = 1, blur = 10, color = rgba 0 0 0 0.3 }
165+ , centerX
166+ , padding 10
167+ , spacing 12
168+ ]
169+ [ paragraph [] [ text txt ]
170+ , Widget.iconButton (Material.containedButton Material.defaultPalette)
171+ { text = "delete"
172+ , icon = Icon.elmHeroicons Heroicons.Outline.trash
173+ , onPress = DeleteEntry id |> Just
174+ }
175+ ]
176+ )
177+ |> column [ spacing 10, centerX ]
178+ ]
179+ |> column [ spacing 18, centerX ]
180+ )
181+ ]
182+ , title = "Airlock"
183+ }
184+
185+
186+result : (a -> c) -> (b -> c) -> Result a b -> c
187+result f g res =
188+ case res of
189+ Ok b ->
190+ g b
191+
192+ Err a ->
193+ f a
194+
195+
196+port createEventSource : String -> Cmd msg
197+
198+
199+port onEventSourceMessage : (JD.Value -> msg) -> Sub msg
example/src/Main.elm → example/src/Vanilla.elmrenamed+21−16
@@ -1,4 +1,4 @@
1−port module Main exposing (main)
1+port module Vanilla exposing (main)
22
33import BigInt exposing (BigInt)
44import Browser exposing (Document)
@@ -12,7 +12,6 @@ import Ur
1212import Ur.Cmd
1313import Ur.Constructor as C
1414import Ur.Deconstructor as D
15−import Ur.Requests
1615import Ur.Run
1716import Ur.Sub
1817import Widget
@@ -20,6 +19,11 @@ import Widget.Icon as Icon
2019import Widget.Material as Material
2120
2221
22+url : String
23+url =
24+ "http://localhost:8080"
25+
26+
2327main : Ur.Run.Program Model Msg
2428main =
2529 Ur.Run.application
@@ -31,20 +35,20 @@ main =
3135 , shipName = Nothing
3236 }
3337 , Cmd.batch
34− [ Ur.logIn "http://localhost:8080" "lidlut-tabwed-pillex-ridrup"
38+ [ Ur.logIn url "lidlut-tabwed-pillex-ridrup"
3539 |> Cmd.map (result (Debug.toString >> Error) (always Noop))
36− , Ur.getShipName "http://localhost:8080" |> Cmd.map (result (always Noop) GotShipName)
37− , Ur.Requests.scry
38− { url = "http://localhost:8080"
40+ , Ur.getShipName url |> Cmd.map (result (always Noop) GotShipName)
41+ , Ur.scry
42+ { url = url
3943 , agent = "journal"
4044 , path = [ "entries", "all" ]
4145 , error = Noop
4246 , success =
4347 D.cell D.ignore
4448 (D.cell (D.const D.cord "jrnl")
45− (D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b ))))
46− |> D.map GotListings
49+ (D.list (D.cell D.bigint D.cord))
4750 )
51+ |> D.map (\( (), ( (), listings ) ) -> GotListings listings)
4852 }
4953 ]
5054 |> Ur.Cmd.cmd
@@ -69,7 +73,7 @@ main =
6973 , onEventSourceMsg = onEventSourceMessage
7074 , onUrlChange = \_ -> Noop
7175 , onUrlRequest = \_ -> Noop
72− , urbitUrl = \_ -> "http://localhost:8080"
76+ , urbitUrl = \_ -> url
7377 }
7478
7579
@@ -173,14 +177,15 @@ type JournalUpdate
173177 | Delete BigInt
174178
175179
176−decodeJournalUpdate : D.Deconstructor (JournalUpdate -> a) a
180+decodeJournalUpdate : D.Deconstructor JournalUpdate
177181decodeJournalUpdate =
178− D.cell D.ignore <|
179− D.oneOf
180− [ (D.cell (D.const D.cord "add") <| D.cell D.bigint D.cord) |> D.map Add
181− , (D.cell (D.const D.cord "edit") <| D.cell D.bigint D.cord) |> D.map Edit
182− , D.cell (D.const D.cord "del") D.bigint |> D.map Delete
183− ]
182+ D.oneOf
183+ [ (D.cell (D.const D.cord "add") <| D.cell D.bigint D.cord) |> D.map (\( (), ( id, txt ) ) -> Add id txt)
184+ , D.cell (D.const D.cord "edit") (D.cell D.bigint D.cord) |> D.map (\( (), ( id, txt ) ) -> Edit id txt)
185+ , D.cell (D.const D.cord "del") D.bigint |> D.map (\( (), id ) -> Delete id)
186+ ]
187+ |> D.cell D.ignore
188+ |> D.map (\( (), upd ) -> upd)
184189
185190
186191view : Model -> Document Msg
example/journal/app/journal.hoon → example/urbit/app/journal.hoonrenamed+26−19
@@ -1,5 +1,5 @@
11/- *journal
2−/+ default-agent, dbug, agentio
2+/+ default-agent, dbug, agentio, *sink
33|%
44+$ versioned-state
55 $% state-0
@@ -17,11 +17,15 @@
1717 ?. (has:log-orm log unix-ms)
1818 unix-ms
1919 $(time (add unix-ms 1))
20−--
20+--
21+
2122%- agent:dbug
22−=| state-0
23−=* state -
23+=/ state *state-0
24+=* entries (tap:j-orm journal.state)
25+=/ snik (sink ~[/sync])
26+=/ sink (snik entries)
2427^- agent:gall
28+::
2529|_ =bowl:gall
2630+* this .
2731 def ~(. (default-agent this %|) bowl)
@@ -34,7 +38,8 @@
3438++ on-load
3539 |= old-vase=vase
3640 ^- (quip card _this)
37− `this(state !<(versioned-state old-vase))
41+ =/ state !<(versioned-state old-vase)
42+ `this(state state, sink (snik entries))
3843::
3944++ on-poke
4045 |= [=mark =vase]
@@ -42,27 +47,28 @@
4247 |^
4348 ?> (team:title our.bowl src.bowl)
4449 ?. ?=(%journal-action mark) (on-poke:def mark vase)
45− =/ now=@ (unique-time now.bowl log)
50+ =/ now=@ (unique-time now.bowl log.state)
4651 =/ act !<(action vase)
4752 =. state (poke-action act)
48− :_ this(log (put:log-orm log now act))
49− ~[(fact:io journal-update+!>(`update`[now act]) ~[/updates])]
53+ =^ card sink (sync:sink entries)
54+ :_ this(log.state (put:log-orm log.state now act))
55+ ~[(fact:io journal-update+!>(`update`[now act]) ~[/updates]) card]
5056 ::
5157 ++ poke-action
5258 |= act=action
5359 ^- _state
5460 ?- -.act
5561 %add
56− ?< (has:j-orm journal id.act)
57− state(journal (put:j-orm journal id.act txt.act))
62+ ?< (has:j-orm journal.state id.act)
63+ state(journal (put:j-orm journal.state id.act txt.act))
5864 ::
5965 %edit
60− ?> (has:j-orm journal id.act)
61− state(journal (put:j-orm journal id.act txt.act))
66+ ?> (has:j-orm journal.state id.act)
67+ state(journal (put:j-orm journal.state id.act txt.act))
6268 ::
6369 %del
64− ?> (has:j-orm journal id.act)
65− state(journal +:(del:j-orm journal id.act))
70+ ?> (has:j-orm journal.state id.act)
71+ state(journal +:(del:j-orm journal.state id.act))
6672 ==
6773 --
6874::
@@ -72,6 +78,7 @@
7278 ?> (team:title our.bowl src.bowl)
7379 ?+ path (on-watch:def path)
7480 [%updates ~] `this
81+ [%sync ~] [~[flush:sink] this]
7582 ==
7683::
7784++ on-peek
@@ -85,14 +92,14 @@
8592 [%all ~]
8693 :^ ~ ~ %journal-update
8794 !> ^- update
88− [now %jrnl (tap:j-orm journal)]
95+ [now %jrnl entries]
8996 ::
9097 [%before @ @ ~]
9198 =/ before=@ (rash i.t.t.t.path dem)
9299 =/ max=@ (rash i.t.t.t.t.path dem)
93100 :^ ~ ~ %journal-update
94101 !> ^- update
95− [now %jrnl (tab:j-orm journal `before max)]
102+ [now %jrnl (tab:j-orm journal.state `before max)]
96103 ::
97104 [%between @ @ ~]
98105 =/ start=@
@@ -101,7 +108,7 @@
101108 =/ end=@ (add 1 (rash i.t.t.t.t.path dem))
102109 :^ ~ ~ %journal-update
103110 !> ^- update
104− [now %jrnl (tap:j-orm (lot:j-orm journal `end `start))]
111+ [now %jrnl (tap:j-orm (lot:j-orm journal.state `end `start))]
105112 ==
106113 ::
107114 [%x %updates *]
@@ -109,13 +116,13 @@
109116 [%all ~]
110117 :^ ~ ~ %journal-update
111118 !> ^- update
112− [now %logs (tap:log-orm log)]
119+ [now %logs (tap:log-orm log.state)]
113120 ::
114121 [%since @ ~]
115122 =/ since=@ (rash i.t.t.t.path dem)
116123 :^ ~ ~ %journal-update
117124 !> ^- update
118− [now %logs (tap:log-orm (lot:log-orm log `since ~))]
125+ [now %logs (tap:log-orm (lot:log-orm log.state `since ~))]
119126 ==
120127 ==
121128::
example/journal/desk.bill → example/urbit/desk.billrenamed

The content is the same.

example/journal/desk.docket-0 → example/urbit/desk.docket-0renamed

The content is the same.

example/journal/gen/journal/add.hoon → example/urbit/gen/journal/add.hoonrenamed

The content is the same.

example/journal/lib/agentio.hoon → example/urbit/lib/agentio.hoonrenamed

The content is the same.

example/journal/lib/dbug.hoon → example/urbit/lib/dbug.hoonrenamed

The content is the same.

example/journal/lib/default-agent.hoon → example/urbit/lib/default-agent.hoonrenamed

The content is the same.

example/journal/lib/docket.hoon → example/urbit/lib/docket.hoonrenamed

The content is the same.

example/journal/lib/journal.hoon → example/urbit/lib/journal.hoonrenamed

The content is the same.

example/urbit/lib/noun-diff.hoonnew file+227
@@ -0,0 +1,227 @@
1+=<
2+|%
3+ ++ diff
4+ |= [old=* new=*]
5+ ^- patch
6+ =/ del (extract-del (oracle old new) old)
7+ =/ ins (extract-ins (oracle old new) new)
8+ =/ allowed-holes (~(int in (find-del-holes del)) (find-ins-holes ins))
9+ =. del (filter-del-holes allowed-holes del)
10+ =/ ins (filter-ins-holes allowed-holes ins)
11+ =/ closed-patch (closure (gcp [del ins]))
12+ ?> =(& +.closed-patch)
13+ -.closed-patch
14+ ++ apply
15+ |= [patch=_id noun=*]
16+ ?- -.patch
17+ %diff
18+ =/ var-map (del del.patch noun)
19+ (ins ins.patch var-map)
20+ %cell
21+ ?> ?=(^ noun)
22+ [$(patch lhs.patch, noun -.noun) $(patch rhs.patch, noun +.noun)]
23+ ==
24+ ++ id
25+ ^- patch
26+ [%diff [%hole ~] [%hole ~]]
27+ +$ patch
28+ $%
29+ [%cell lhs=patch rhs=patch]
30+ [%diff ^diff]
31+ ==
32+--
33+::
34+|%
35+ +$ del-diff
36+ $%
37+ [%hole @]
38+ [%cell lhs=del-diff rhs=del-diff]
39+ [%ignore ~]
40+ ==
41+ +$ ins-diff
42+ $%
43+ [%hole @ original=*]
44+ [%cell lhs=ins-diff rhs=ins-diff]
45+ [%atom @]
46+ ==
47+ +$ final-ins-diff
48+ $%
49+ [%hole @]
50+ [%cell lhs=final-ins-diff rhs=final-ins-diff]
51+ [%atom @]
52+ ==
53+ +$ diff [del=del-diff ins=final-ins-diff]
54+ +$ patch
55+ $%
56+ [%cell lhs=patch rhs=patch]
57+ [%diff diff]
58+ ==
59+ ++ insify-noun
60+ |= noun=*
61+ ^- final-ins-diff
62+ ?- noun
63+ ^ [%cell $(noun -.noun) $(noun +.noun)]
64+ @ [%atom noun]
65+ ==
66+ ++ empty-set (silt `(list @)`~)
67+ ++ find-del-holes
68+ |= diff=del-diff
69+ ~+
70+ ^- (set @)
71+ ?- -.diff
72+ %hole (silt ~[+.diff])
73+ %cell
74+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
75+ %ignore empty-set
76+ ==
77+ ++ find-final-ins-holes
78+ |= diff=final-ins-diff
79+ ~+
80+ ^- (set @)
81+ ?- -.diff
82+ %hole (silt ~[+.diff])
83+ %cell
84+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
85+ %atom empty-set
86+ ==
87+ ++ find-ins-holes
88+ |= diff=ins-diff
89+ ~+
90+ ^- (set @)
91+ ?- -.diff
92+ %hole (silt ~[+<.diff])
93+ %cell
94+ (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
95+ %atom empty-set
96+ ==
97+ ++ filter-del-holes
98+ |= [allowed-holes=(set @) diff=del-diff]
99+ ^- del-diff
100+ ?: ?=(%ignore -.diff) diff
101+ ?- -.diff
102+ %hole
103+ ?: (~(has in allowed-holes) +.diff) diff
104+ [%ignore ~]
105+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
106+ ==
107+ ++ filter-ins-holes
108+ |= [allowed-holes=(set @) diff=ins-diff]
109+ ^- final-ins-diff
110+ ?- -.diff
111+ %hole
112+ ?: (~(has in allowed-holes) +<.diff) [%hole +<.diff]
113+ (insify-noun original:diff)
114+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
115+ %atom diff
116+ ==
117+ ++ gcp
118+ |= diff=diff
119+ ^- patch
120+ ?- -.ins.diff
121+ %atom
122+ [%diff diff]
123+ %cell
124+ ?: ?=(%cell -.del.diff)
125+ [%cell $(diff [+<.del.diff +<.ins.diff]) $(diff [+>.del.diff +>.ins.diff])]
126+ [%diff diff]
127+ %hole
128+ [%diff diff]
129+ ==
130+ ++ closure
131+ |= =patch
132+ ^- [^patch ?]
133+ ?- -.patch
134+ %diff
135+ =/ del-holes (find-del-holes del:patch)
136+ =/ ins-holes (find-final-ins-holes ins:patch)
137+ =/ difference (~(dif in ins-holes) del-holes)
138+ [patch =(difference empty-set)]
139+ %cell
140+ =/ lhs $(patch lhs:patch)
141+ =/ rhs $(patch rhs:patch)
142+ ?: ?&(+.lhs +.rhs) [[%cell -.lhs -.rhs] &]
143+ $(patch (pull-diff [%cell -.lhs -.rhs]))
144+ ==
145+ ++ pull-diff
146+ |= =patch
147+ ^- [%diff del=del-diff ins=final-ins-diff]
148+ ?- -.patch
149+ %diff patch
150+ %cell
151+ =/ pulled-lhs $(patch lhs:patch)
152+ =/ pulled-rhs $(patch rhs:patch)
153+ :+
154+ %diff
155+ [%cell del:pulled-lhs del:pulled-rhs]
156+ [%cell ins:pulled-lhs ins:pulled-rhs]
157+ ==
158+ ++ is-subtree
159+ |= [tree=* subtree=*]
160+ ~+
161+ ^- ?
162+ ?: =(tree subtree) &
163+ ?@ tree |
164+ ?|
165+ (is-subtree -.tree subtree)
166+ (is-subtree +.tree subtree)
167+ ==
168+ ++ oracle
169+ |= [a=* b=*]
170+ |= subtree=*
171+ ^- (unit @)
172+ ?:
173+ ?&
174+ (is-subtree a subtree)
175+ (is-subtree b subtree)
176+ ==
177+ `(mug subtree)
178+ ~
179+ ++ extract-del
180+ |= [oracle=$-(* (unit @)) subtree=*]
181+ ~+
182+ ^- del-diff
183+ =/ hash (oracle subtree)
184+ ?^ hash [%hole +.hash]
185+ ?@ subtree [%ignore ~]
186+ [%cell (extract-del oracle -.subtree) (extract-del oracle +.subtree)]
187+ ++ extract-ins
188+ |= [oracle=$-(* (unit @)) subtree=*]
189+ ~+
190+ ^- ins-diff
191+ =/ hash (oracle subtree)
192+ ?^ hash [%hole +.hash subtree]
193+ ?@ subtree [%atom subtree]
194+ [%cell (extract-ins oracle -.subtree) (extract-ins oracle +.subtree)]
195+ ++ ins
196+ |= [diff=final-ins-diff var-map=(map @ *)]
197+ ^- *
198+ ?- -.diff
199+ %atom
200+ +.diff
201+ %cell
202+ [$(diff +<.diff) $(diff +>.diff)]
203+ %hole
204+ (~(got by var-map) +.diff)
205+ ==
206+ ++ del
207+ |= [diff=del-diff noun=*]
208+ ^- (map @ *)
209+ |^ (go diff noun ((map @ *) ~))
210+ ++ go
211+ |= [diff=del-diff noun=* var-map=(map @ *)]
212+ ^- (map @ *)
213+ ?- -.diff
214+ %ignore var-map
215+ %hole
216+ =/ subtree (~(get by var-map) +.diff)
217+ ?~ subtree (~(put by var-map) +.diff noun)
218+ ?> =(+.subtree noun)
219+ var-map
220+ %cell
221+ ?> ?=(^ noun)
222+ =/ lhs-var-map $(diff +<.diff, noun -.noun)
223+ =/ rhs-var-map $(diff +>.diff, noun +.noun, var-map lhs-var-map)
224+ rhs-var-map
225+ ==
226+ --
227+--
example/urbit/lib/sink.hoonnew file+26
@@ -0,0 +1,26 @@
1+/+ noun-diff
2+|%
3+++ clog
4+ |$ [stut]
5+ $%
6+ [%flush stut]
7+ [%drain patch:noun-diff]
8+ ==
9+++ sink
10+ |* pats=(list path)
11+ |* stat=*
12+ |@
13+ ++ sync
14+ |= [stat=_stat]
15+ ^- [card:agent:gall _..sync]
16+ =/ dif
17+ %+ diff:noun-diff ^stat stat
18+ :-
19+ [%give %fact pats %noun !>(^-((clog) [%drain dif]))]
20+ ..sync(stat stat)
21+ ++ paths pats
22+ ++ flush
23+ ^- card:agent:gall
24+ [%give %fact pats %noun !>(^-((clog) [%flush stat]))]
25+ --
26+--
example/journal/lib/skeleton.hoon → example/urbit/lib/skeleton.hoonrenamed

The content is the same.

example/journal/mar/bill.hoon → example/urbit/mar/bill.hoonrenamed

The content is the same.

example/journal/mar/docket-0.hoon → example/urbit/mar/docket-0.hoonrenamed

The content is the same.

example/journal/mar/hoon.hoon → example/urbit/mar/hoon.hoonrenamed

The content is the same.

example/journal/mar/jam.hoon → example/urbit/mar/jam.hoonrenamed

The content is the same.

example/journal/mar/journal/action.hoon → example/urbit/mar/journal/action.hoonrenamed

The content is the same.

example/journal/mar/journal/update.hoon → example/urbit/mar/journal/update.hoonrenamed

The content is the same.

example/journal/mar/json.hoon → example/urbit/mar/json.hoonrenamed

The content is the same.

example/journal/mar/kelvin.hoon → example/urbit/mar/kelvin.hoonrenamed

The content is the same.

example/journal/mar/mime.hoon → example/urbit/mar/mime.hoonrenamed

The content is the same.

example/journal/mar/noun.hoon → example/urbit/mar/noun.hoonrenamed

The content is the same.

example/journal/mar/txt-diff.hoon → example/urbit/mar/txt-diff.hoonrenamed

The content is the same.

example/journal/mar/txt.hoon → example/urbit/mar/txt.hoonrenamed

The content is the same.

example/journal/sur/docket.hoon → example/urbit/sur/docket.hoonrenamed

The content is the same.

example/journal/sur/journal.hoon → example/urbit/sur/journal.hoonrenamed

The content is the same.

example/journal/sys.kelvin → example/urbit/sys.kelvinrenamed

The content is the same.

src/BitWriter.elm+5−5
@@ -14,17 +14,17 @@ import Bytes.Encode as BE
1414
1515empty : BitWriter
1616empty =
17− BitWriter { running = Nothing, collected = BE.sequence [], offset = 0 }
17+ BitWriter { running = Nothing, collected = [], offset = 0 }
1818
1919
2020run : BitWriter -> Bytes
2121run (BitWriter { running, collected }) =
2222 case running of
2323 Nothing ->
24− BE.encode collected
24+ BE.encode (collected |> List.reverse |> BE.sequence)
2525
2626 Just { value } ->
27− BE.sequence [ collected, BE.unsignedInt8 value ] |> BE.encode
27+ BE.sequence (BE.unsignedInt8 value :: collected |> List.reverse) |> BE.encode
2828
2929
3030type BitWriter
@@ -35,7 +35,7 @@ type BitWriter
3535 , length : Int
3636 }
3737 , offset : Int
38− , collected : BE.Encoder
38+ , collected : List BE.Encoder
3939 }
4040
4141
@@ -68,7 +68,7 @@ bit b (BitWriter { running, collected, offset }) =
6868 if length == 7 then
6969 BitWriter
7070 { running = Nothing
71− , collected = BE.sequence [ collected, BE.unsignedInt8 newValue ]
71+ , collected = BE.unsignedInt8 newValue :: collected
7272 , offset = newOffset
7373 }
7474
src/Ur.elm+2−2
@@ -77,7 +77,7 @@ scry :
7777 , agent : Agent
7878 , path : Path
7979 , error : msg
80− , success : D.Deconstructor (msg -> msg) msg
80+ , success : D.Deconstructor msg
8181 }
8282 -> Cmd msg
8383scry args =
@@ -91,7 +91,7 @@ scryTask :
9191 , agent : Agent
9292 , path : Path
9393 , error : msg
94− , success : D.Deconstructor (msg -> msg) msg
94+ , success : D.Deconstructor msg
9595 }
9696 -> Task a msg
9797scryTask { url, agent, path, error, success } =
src/Ur/Constructor.elm+2−2
@@ -89,7 +89,7 @@ signedInt i =
8989 )
9090
9191
92−{-| Constructs an `Atom` from `Bytes`
92+{-| Constructs an `Atom` from `Bytes`.
9393-}
9494bytes : Bytes -> Noun
9595bytes =
@@ -103,7 +103,7 @@ cord s =
103103 Atom (BE.encode (BE.string s))
104104
105105
106−{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`
106+{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`.
107107-}
108108tape : String -> Noun
109109tape s =
src/Ur/Deconstructor.elm+211−218
@@ -6,24 +6,16 @@ module Ur.Deconstructor exposing
66 , int, signedInt, bigint
77 , float32, float64
88 , cord, tape
9− , bytes, sig, ignore, tar
9+ , bytes, sig, ignore, tar, lazy, debug
1010 )
1111
1212{-| This module provides an API to deconstruct `Noun`s into arbitrary Elm data structures.
1313
14−The principal (and types) are very similar to `Url.Parser` from `elm/url`.
14+You would parse a `[%edit @ cord]` like this:
1515
16−You would parse a `[%edit cord @]` like this:
16+ type alias Edit = {id: Int, text : String}
1717
18− type alias Edit = {text : String, id: Int}
19−
20− (D.cell (D.const D.cord "edit") (D.cell D.int D.cord)) |> D.map Edit
21−
22−When you `map` a `Deconstructor` the function you pass to `map` will accept exactly the number of arguments
23−that "captured" values in exacly the order they occured in the `Deconstructor`.
24−
25−In our case we `map` the `Edit` type, which accepts exaclty two arguments because there are two `Deconstructor`s
26−that "capture" a value: `D.int` and `D.cord`.
18+ (D.cell (D.const D.cord "edit") (D.cell D.bigint D.cord)) |> D.map (\( (), ( id, txt ) ) -> Edit id txt)
2719
2820@docs Deconstructor
2921
@@ -63,7 +55,7 @@ that "capture" a value: `D.int` and `D.cord`.
6355
6456# Miscellaneous
6557
66−@docs bytes, sig, ignore, tar
58+@docs bytes, sig, ignore, tar, lazy, debug
6759
6860-}
6961
@@ -74,139 +66,74 @@ import Bytes exposing (Bytes, Endianness(..))
7466import Bytes.Decode as BD
7567import Bytes.Encode as BE
7668import Bytes.Extra
77−import Ur.Jam exposing (cue)
69+import Ur.Jam exposing (cue, isSig)
7870import Ur.Types exposing (..)
7971
8072
8173{-| A `Noun` deconstructor.
8274-}
83−type Deconstructor a b
84− = Deconstructor (Noun -> a -> Maybe b)
75+type alias Deconstructor a =
76+ Noun -> Maybe a
8577
8678
8779{-| Executes a `Deconstructor` on a `Noun`.
8880-}
89−run : Deconstructor (a -> a) a -> Noun -> Maybe a
90−run (Deconstructor f) noun =
91− f noun identity
81+run : Deconstructor a -> Noun -> Maybe a
82+run f noun =
83+ f noun
9284
9385
9486{-| Executes a deconstructor on a `jam`med `Noun`.
9587-}
96−runBytes : Deconstructor (a -> a) a -> Bytes -> Maybe a
97−runBytes (Deconstructor f) bs =
98− cue bs
99− |> Maybe.andThen
100− (\noun -> f noun identity)
88+runBytes : Deconstructor a -> Bytes -> Maybe a
89+runBytes f bs =
90+ cue bs |> Maybe.andThen f
10191
10292
103−{-| Asserts that the value at the current position should be exactly equal to the second argument.
93+{-| Asserts that the value should be exactly equal to the second argument.
10494
105−The first argument is a `Deconstructor` for the gven tyoe.
95+The first argument is a `Deconstructor` for the given type.
10696
10797The second argument is the value to compare with.
10898
10999This is useful to match on `term`s when there are multiple possible cases in a head-tagged union.
110100
111101-}
112−const : Deconstructor (a -> a) a -> a -> Deconstructor c c
113−const (Deconstructor f) value =
114− Deconstructor
115− (\noun c ->
116− case f noun identity of
117− Just a ->
118− if a == value then
119− Just c
102+const : Deconstructor a -> a -> Deconstructor ()
103+const f value noun =
104+ case f noun of
105+ Just a ->
106+ if a == value then
107+ Just ()
120108
121− else
122− Nothing
109+ else
110+ Nothing
123111
124− Nothing ->
125− Nothing
126− )
112+ Nothing ->
113+ Nothing
127114
128115
129−{-| Extracts a `cord` at the current location.
116+{-| Extracts a `BigInt`.
130117-}
131−cord : Deconstructor (String -> a) a
132−cord =
133− Deconstructor
134− (\x f ->
135− case x of
136− Atom bs ->
137− BD.decode (BD.string (Bytes.width bs)) bs
138− |> Maybe.map f
139−
140− Cell _ ->
141− Nothing
142− )
118+bigint : Deconstructor BigInt
119+bigint x =
120+ case x of
121+ Atom bs ->
122+ BigInt.Bytes.decode bs |> Just
143123
144−
145−{-| Extracts a `tape` at the current location.
146−-}
147−tape : Deconstructor (String -> a) a
148−tape =
149− list cord |> fmap String.concat
124+ Cell _ ->
125+ Nothing
150126
151127
152−{-| Extracts a 32-bit unsigned `Int` at the given location.
128+{-| Extracts a 32-bit signed `Int`.
153129
154−If the `Atom` at the current location is larger than 32 bits the the behaviour is undefined.
130+If the `Atom` is larger than 32 bits the the behaviour is undefined.
155131
156132-}
157−int : Deconstructor (Int -> a) a
158−int =
159− Deconstructor
160− (\x f ->
161− case x of
162− Atom bs ->
163− (case Bytes.width bs of
164− 1 ->
165− BD.decode BD.unsignedInt8 bs
166−
167− 2 ->
168− BD.decode (BD.unsignedInt16 LE) bs
169−
170− 3 ->
171− BD.decode (BD.unsignedInt32 LE) (BE.encode (BE.sequence [ BE.bytes bs, BE.unsignedInt8 0 ]))
172−
173− 4 ->
174− BD.decode (BD.unsignedInt32 LE) bs
175−
176− _ ->
177− Nothing
178− )
179− |> Maybe.map f
180−
181− Cell _ ->
182− Nothing
183− )
184−
185−
186−{-| Extracts a `BigInt` at the current location.
187−-}
188−bigint : Deconstructor (BigInt -> a) a
189−bigint =
190− Deconstructor
191− (\x f ->
192− case x of
193− Atom bs ->
194− BigInt.Bytes.decode bs |> f |> Just
195−
196− Cell _ ->
197− Nothing
198− )
199−
200−
201−{-| Extracts a 32-bit signed `Int` at the given location.
202−
203−If the `Atom` at the current location is larger than 32 bits the the behaviour is undefined.
204−
205−-}
206−signedInt : Deconstructor (Int -> a) a
133+signedInt : Deconstructor Int
207134signedInt =
208135 int
209− |> fmap
136+ |> map
210137 (\i ->
211138 if Bitwise.and 1 i == 1 then
212139 -(Bitwise.shiftRightBy 1 (1 + i))
@@ -216,105 +143,85 @@ signedInt =
216143 )
217144
218145
219−{-| Extracts a 32-bit `Float` at the given location.
146+{-| Extracts a 32-bit `Float`.
220147-}
221−float32 : Deconstructor (Float -> a) a
222−float32 =
223− Deconstructor
224− (\x f ->
225− case x of
226− Atom bs ->
227− BD.decode (BD.float32 LE) bs
228− |> Maybe.map f
229−
230− Cell _ ->
231− Nothing
232− )
148+float32 : Deconstructor Float
149+float32 x =
150+ case x of
151+ Atom bs ->
152+ BD.decode (BD.float32 LE) bs
153+
154+ Cell _ ->
155+ Nothing
233156
234157
235−{-| Extracts a 64-bit `Float` at the given location.
158+{-| Extracts a 64-bit `Float`.
236159-}
237−float64 : Deconstructor (Float -> a) a
238−float64 =
239− Deconstructor
240− (\x f ->
241− case x of
242− Atom bs ->
243− BD.decode (BD.float64 LE) bs
244− |> Maybe.map f
245−
246− Cell _ ->
247− Nothing
248− )
160+float64 : Deconstructor Float
161+float64 x =
162+ case x of
163+ Atom bs ->
164+ BD.decode (BD.float64 LE) bs
165+
166+ Cell _ ->
167+ Nothing
249168
250169
251−{-| Extracts the raw `Byte`s of an `Atom` at the current location.
170+{-| Extracts the raw `Byte`s of an `Atom`.
252171-}
253−bytes : Deconstructor (Bytes -> a) a
254−bytes =
255− Deconstructor
256− (\n f ->
257− case n of
258− Atom bs ->
259− f bs |> Just
172+bytes : Deconstructor Bytes
173+bytes n =
174+ case n of
175+ Atom bs ->
176+ Just bs
260177
261− Cell _ ->
262− Nothing
263− )
178+ Cell _ ->
179+ Nothing
264180
265181
266−{-| Asserts the the `Atom` at the current position should be exactly `sig` (`~`).
182+{-| Asserts the the `Atom` should be exactly `sig` (`~`).
267183-}
268−sig : Deconstructor a a
269−sig =
270− Deconstructor
271− (\n a ->
272− case n of
273− Atom b ->
274− if (Bytes.Extra.toByteValues b |> List.filter (\x -> x /= 0)) == [] then
275− Just a
184+sig : Deconstructor ()
185+sig n =
186+ case n of
187+ Atom b ->
188+ if (Bytes.Extra.toByteValues b |> List.filter (\x -> x /= 0)) == [] then
189+ Just ()
276190
277− else
278− Nothing
191+ else
192+ Nothing
279193
280− Cell _ ->
281− Nothing
282− )
194+ Cell _ ->
195+ Nothing
283196
284197
285−{-| Extracts a sig-terminated list of arbitrary elements at the current position.
198+{-| Extracts a sig-terminated list of arbitrary elements.
286199
287200The first argument is a `Deconstructor` of the elements of the list.
288201
289202-}
290−list : Deconstructor (a -> a) a -> Deconstructor (List a -> b) b
291−list (Deconstructor f) =
292− Deconstructor
293− (\n g ->
294− let
295− go n_ =
296− case n_ of
297− Atom _ ->
298− Just []
203+list : Deconstructor a -> Deconstructor (List a)
204+list f n =
205+ let
206+ go n_ =
207+ case n_ of
208+ Atom _ ->
209+ Just []
299210
300− Cell ( el, tail ) ->
301− Maybe.map2 (\a b -> a :: b) (f el identity) (go tail)
302− in
303− go n |> Maybe.map g
304− )
211+ Cell ( el, tail ) ->
212+ Maybe.map2 (\a b -> a :: b) (f el) (go tail)
213+ in
214+ go n
305215
306216
307−alt : Deconstructor a b -> Deconstructor a b -> Deconstructor a b
308−alt (Deconstructor f) (Deconstructor g) =
309− Deconstructor
310− (\n a ->
311− case f n a of
312− Just x ->
313− Just x
217+alt : Deconstructor a -> Deconstructor a -> Deconstructor a
218+alt f g n =
219+ case f n of
220+ Just x ->
221+ Just x
314222
315− Nothing ->
316− g n a
317− )
223+ Nothing ->
224+ g n
318225
319226
320227{-| Try to execute all of the `Deconstructor`s in order until one succeeds.
@@ -322,34 +229,24 @@ alt (Deconstructor f) (Deconstructor g) =
322229This is especially useful for deconstructing head-tagged unions from Hoon.
323230
324231-}
325−oneOf : List (Deconstructor a b) -> Deconstructor a b
232+oneOf : List (Deconstructor a) -> Deconstructor a
326233oneOf l =
327234 case l of
328235 [] ->
329− Deconstructor (\_ _ -> Nothing)
236+ always Nothing
330237
331238 x :: xs ->
332239 alt x (oneOf xs)
333240
334241
335−{-| Extract the raw `Noun` at the current position.
336−
337−Always succeeds.
338−
339−-}
340−tar : Deconstructor (Noun -> a) a
341−tar =
342− Deconstructor (\noun f -> Just (f noun))
343−
344−
345−{-| Ignore any value at the current position.
242+{-| Ignore any value.
346243
347244This is useful when you have a value you don't care about. `ignore` allows you to just skip the value.
348245
349246-}
350−ignore : Deconstructor a a
247+ignore : Deconstructor ()
351248ignore =
352− Deconstructor (\_ f -> Just f)
249+ always (Just ())
353250
354251
355252{-| Extracts a [`Cell`](https://developers.urbit.org/reference/glossary/cell) (pair) of two arbitrary values.
@@ -357,17 +254,14 @@ ignore =
357254Accepts two arbitrary `Deconstructor`s that form a Cell.
358255
359256-}
360−cell : Deconstructor a b -> Deconstructor b c -> Deconstructor a c
361−cell (Deconstructor l) (Deconstructor r) =
362− Deconstructor
363− (\noun a ->
364− case noun of
365− Cell ( lhs, rhs ) ->
366− l lhs a |> Maybe.andThen (\b -> r rhs b)
257+cell : Deconstructor a -> Deconstructor b -> Deconstructor ( a, b )
258+cell l r noun =
259+ case noun of
260+ Cell ( lhs, rhs ) ->
261+ l lhs |> Maybe.andThen (\a -> r rhs |> Maybe.map (\b -> ( a, b )))
367262
368− Atom _ ->
369− Nothing
370− )
263+ Atom _ ->
264+ Nothing
371265
372266
373267{-| Maps (applies) a function to all of the values deconstructed.
@@ -375,11 +269,110 @@ cell (Deconstructor l) (Deconstructor r) =
375269This is useful when you want to create a data type with extracted values as fields.
376270
377271-}
378−map : a -> Deconstructor a b -> Deconstructor (b -> c) c
379−map a (Deconstructor f) =
380− Deconstructor (\noun g -> f noun a |> Maybe.map g)
272+lazy : (() -> Deconstructor a) -> Deconstructor a
273+lazy f noun =
274+ f () noun
381275
382276
383−fmap : (a -> b) -> Deconstructor (a -> c) c -> Deconstructor (b -> c) c
384−fmap f (Deconstructor g) =
385− Deconstructor (\noun h -> g noun (f >> h))
277+{-| Maps over the result of the deconstructor.
278+-}
279+map : (a -> b) -> Deconstructor a -> Deconstructor b
280+map g f noun =
281+ f noun |> Maybe.map g
282+
283+
284+{-| Extracts a `cord`.
285+-}
286+cord : Deconstructor String
287+cord x =
288+ case x of
289+ Atom bs ->
290+ BD.decode (BD.string (Bytes.width bs)) bs
291+
292+ Cell _ ->
293+ Nothing
294+
295+
296+{-| Extracts a `tape`.
297+-}
298+tape : Deconstructor String
299+tape =
300+ list cord |> map String.concat
301+
302+
303+{-| Extracts a 32-bit unsigned `Int`.
304+
305+If the `Atom` is larger than 32 bits the the behaviour is undefined.
306+
307+-}
308+int : Deconstructor Int
309+int x =
310+ case x of
311+ Atom bs ->
312+ case Bytes.width bs of
313+ 1 ->
314+ BD.decode BD.unsignedInt8 bs
315+
316+ 2 ->
317+ BD.decode (BD.unsignedInt16 LE) bs
318+
319+ 3 ->
320+ BD.decode (BD.unsignedInt32 LE) (BE.encode (BE.sequence [ BE.bytes bs, BE.unsignedInt8 0 ]))
321+
322+ 4 ->
323+ BD.decode (BD.unsignedInt32 LE) bs
324+
325+ _ ->
326+ Nothing
327+
328+ Cell _ ->
329+ Nothing
330+
331+
332+{-| Extract the raw `Noun`.
333+
334+Always succeeds.
335+
336+-}
337+tar : Deconstructor Noun
338+tar =
339+ Just
340+
341+
342+{-| Debug a deconstructor.
343+If the deconstructor from the second argument fails then the failed nock will be printed to the console.
344+-}
345+debug : (String -> String) -> Deconstructor a -> Deconstructor a
346+debug log f noun =
347+ case f noun of
348+ Just x ->
349+ Just x
350+
351+ Nothing ->
352+ let
353+ _ =
354+ log (prettyNoun noun)
355+ in
356+ Nothing
357+
358+
359+prettyNoun : Noun -> String
360+prettyNoun noun =
361+ let
362+ go isRhs n =
363+ case n of
364+ Atom a ->
365+ if isSig a then
366+ "~"
367+
368+ else
369+ "@"
370+
371+ Cell ( lhs, rhs ) ->
372+ if isRhs then
373+ go False lhs ++ " " ++ go True rhs
374+
375+ else
376+ "[" ++ go False lhs ++ " " ++ go True rhs ++ "]"
377+ in
378+ go False noun
src/Ur/NounDiff.elmnew file+115
@@ -0,0 +1,115 @@
1+module Ur.NounDiff exposing (Patch, apply, deconstructPatch)
2+
3+import Bytes.Extra
4+import Dict exposing (Dict)
5+import Ur.Deconstructor as D
6+import Ur.Types exposing (..)
7+
8+
9+type Patch
10+ = PatchCell Patch Patch
11+ | Diff DelDiff InsDiff
12+
13+
14+deconstructPatch : () -> D.Deconstructor Patch
15+deconstructPatch () =
16+ D.oneOf
17+ [ D.cell (D.const D.cord "cell")
18+ (D.cell
19+ (D.lazy deconstructPatch)
20+ (D.lazy deconstructPatch)
21+ )
22+ |> D.map (\( (), ( a, b ) ) -> PatchCell a b)
23+ , D.cell (D.const D.cord "diff") (D.cell (D.lazy deconstructDel) (D.lazy deconstructIns))
24+ |> D.map (\( (), ( a, b ) ) -> Diff a b)
25+ ]
26+
27+
28+apply : Patch -> Noun -> Noun
29+apply patch noun =
30+ case ( patch, noun ) of
31+ ( PatchCell lhs rhs, Cell ( lhs_, rhs_ ) ) ->
32+ Cell ( apply lhs lhs_, apply rhs rhs_ )
33+
34+ ( Diff delDiff insDiff, _ ) ->
35+ ins insDiff (del delDiff noun)
36+
37+ _ ->
38+ noun
39+
40+
41+del : DelDiff -> Noun -> Dict Int Noun
42+del diff noun =
43+ let
44+ go : DelDiff -> Noun -> Dict Int Noun -> Dict Int Noun
45+ go diff_ noun_ dict =
46+ case ( diff_, noun_ ) of
47+ ( Ignore, _ ) ->
48+ dict
49+
50+ ( DelCell lhsDiff rhsDiff, Cell ( lhs, rhs ) ) ->
51+ go lhsDiff lhs (go rhsDiff rhs dict)
52+
53+ ( DelHole hole, _ ) ->
54+ -- There should be a continuity check here.
55+ -- Check that the noun_ is equal to whatever the hole maps to in dict.
56+ -- I don't include it for speed and laziness reasons.
57+ Dict.insert hole noun_ dict
58+
59+ _ ->
60+ dict
61+ in
62+ go diff noun Dict.empty
63+
64+
65+ins : InsDiff -> Dict Int Noun -> Noun
66+ins diff dict =
67+ case diff of
68+ InsAtom a ->
69+ Atom a
70+
71+ InsHole hole ->
72+ Dict.get hole dict |> Maybe.withDefault (Atom Bytes.Extra.empty)
73+
74+ InsCell lhs rhs ->
75+ Cell ( ins lhs dict, ins rhs dict )
76+
77+
78+type DelDiff
79+ = Ignore
80+ | DelHole Int
81+ | DelCell DelDiff DelDiff
82+
83+
84+deconstructDel : () -> D.Deconstructor DelDiff
85+deconstructDel () =
86+ D.oneOf
87+ [ D.cell (D.const D.cord "ignore") D.ignore |> D.map (\( (), () ) -> Ignore)
88+ , D.cell (D.const D.cord "hole") D.int |> D.map (\( (), x ) -> DelHole x)
89+ , D.cell (D.const D.cord "cell")
90+ (D.cell
91+ (D.lazy deconstructDel)
92+ (D.lazy deconstructDel)
93+ )
94+ |> D.map (\( (), ( lhs, rhs ) ) -> DelCell lhs rhs)
95+ ]
96+
97+
98+type InsDiff
99+ = InsAtom Atom
100+ | InsHole Int
101+ | InsCell InsDiff InsDiff
102+
103+
104+deconstructIns : () -> D.Deconstructor InsDiff
105+deconstructIns () =
106+ D.oneOf
107+ [ D.cell (D.const D.cord "hole") D.int |> D.map (\( (), x ) -> InsHole x)
108+ , D.cell (D.const D.cord "atom") D.bytes |> D.map (\( (), bs ) -> InsAtom bs)
109+ , D.cell (D.const D.cord "cell")
110+ (D.cell
111+ (D.lazy deconstructIns)
112+ (D.lazy deconstructIns)
113+ )
114+ |> D.map (\( (), ( a, b ) ) -> InsCell a b)
115+ ]
src/Ur/Phonemic.elm+1−1
@@ -13,7 +13,7 @@ import Urbit.Encoding.Atom exposing (toBigInt)
1313import Urbit.Encoding.Phonemic exposing (..)
1414
1515
16−{-| Converts a ship name like `~zod` into an Atom.
16+{-| Converts a ship name like `~zod` into an `Atom`.
1717-}
1818fromString : Ship -> Atom
1919fromString s =
src/Ur/Requests.elm+1−1
@@ -98,7 +98,7 @@ toNoun eventId req =
9898 C.cell (C.cord app) (C.listOf C.cord path)
9999
100100 Unsubscribe subId ->
101− C.cell (C.cord "usubscribe") <|
101+ C.cell (C.cord "unsubscribe") <|
102102 C.cell (C.int eventId) (C.int subId)
103103
104104 Poke { ship, agent, mark, noun } ->
src/Ur/Run.elm+80−23
@@ -18,6 +18,7 @@ import Ur.Cmd
1818import Ur.Cmd.Internal
1919import Ur.Constructor as C
2020import Ur.Deconstructor as D
21+import Ur.NounDiff exposing (Patch, deconstructPatch)
2122import Ur.Requests exposing (..)
2223import Ur.Sub
2324import Ur.Sub.Internal
@@ -30,8 +31,9 @@ type alias SubDict msg =
3031 Dict
3132 -- (ship, agent, path)
3233 ( String, String, List String )
33− { deconstructor : D.Deconstructor (msg -> msg) msg
34+ { deconstructor : D.Deconstructor msg
3435 , number : Int
36+ , sink : Bool
3537 }
3638
3739
@@ -49,6 +51,7 @@ type alias Model app msg =
4951 , eventId : Int
5052 , flags : Flags
5153 , requestsToRetry : List UrbitRequest
54+ , sinks : Dict Int Noun
5255 }
5356
5457
@@ -171,12 +174,11 @@ update inp msg model =
171174 ( appModel, appCmds ) =
172175 inp.update msg_ model.app
173176
174− -- { subscriptions, eventId, subscriptionRequests, subscriptionIntMapping } =
175177 subsResult =
176178 processUrSubs
177179 model.eventId
178180 model.subscriptions
179− (inp.urbitSubscriptions model.app |> (\(Ur.Sub.Internal.Sub x) -> x))
181+ (inp.urbitSubscriptions appModel |> (\(Ur.Sub.Internal.Sub x) -> x))
180182
181183 ( eventId_, cmds, urReqs ) =
182184 processCmd subsResult.eventId appCmds
@@ -209,12 +211,10 @@ update inp msg model =
209211 Ok string ->
210212 case
211213 D.runBytes
212− (D.cell D.int (D.cell D.cord D.tar)
213− |> D.map (\a b c -> ( a, b, c ))
214− )
214+ (D.cell D.int (D.cell D.cord D.tar))
215215 (Ur.Uw.decode string)
216216 of
217− Just ( messageId, messageType, rest ) ->
217+ Just ( messageId, ( messageType, rest ) ) ->
218218 let
219219 ( eventId, ackReqs ) =
220220 tag model.eventId [ Ack messageId ]
@@ -244,14 +244,51 @@ update inp msg model =
244244 Dict.get messageId model.subscriptionIntMapping
245245 |> Maybe.andThen (\key -> Dict.get key model.subscriptions)
246246 of
247− Just { deconstructor } ->
248− case D.run (D.cell D.ignore (D.cell D.ignore deconstructor)) rest of
249− Just subMsg ->
250− ( model_, pureCmd (AppMsg subMsg) )
251−
252− -- Got gargbage
253− Nothing ->
254− ( model_, ackCmd )
247+ Just { deconstructor, sink } ->
248+ if sink then
249+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructSink) |> D.map (\( (), ( (), s ) ) -> s)) rest of
250+ Just (Flush noun) ->
251+ ( { model | sinks = Dict.insert messageId noun model.sinks }
252+ , case D.run deconstructor noun of
253+ Just subMsg ->
254+ pureCmd (AppMsg subMsg)
255+
256+ -- Got garbage
257+ Nothing ->
258+ Cmd.none
259+ )
260+
261+ Just (Drain patch) ->
262+ case Dict.get messageId model.sinks of
263+ Just oldNoun ->
264+ let
265+ newNoun =
266+ Ur.NounDiff.apply patch oldNoun
267+ in
268+ ( { model | sinks = Dict.insert messageId newNoun model.sinks }
269+ , case D.run deconstructor newNoun of
270+ Just subMsg ->
271+ pureCmd (AppMsg subMsg)
272+
273+ Nothing ->
274+ Cmd.none
275+ )
276+
277+ Nothing ->
278+ ( model, Cmd.none )
279+
280+ -- Got garbage
281+ Nothing ->
282+ ( model, Cmd.none )
283+
284+ else
285+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructor) |> D.map (\( (), ( (), m ) ) -> m)) rest of
286+ Just subMsg ->
287+ ( model_, pureCmd (AppMsg subMsg) )
288+
289+ -- Got gargbage
290+ Nothing ->
291+ ( model_, ackCmd )
255292
256293 -- Got a fact for a subscription we do not hold
257294 Nothing ->
@@ -276,11 +313,11 @@ update inp msg model =
276313 NeedsActivation ->
277314 let
278315 ( eventId, reqs ) =
279− [ Poke { ship = "~zod", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
316+ [ Poke { ship = "", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
280317 |> tag model.eventId
281318 in
282319 ( { model | eventId = eventId }
283− , send { url = url, requests = reqs, success = OpenConnection, error = NeedsActivation }
320+ , send { url = url, requests = reqs, success = OpenConnection, error = Noop }
284321 )
285322
286323 Noop ->
@@ -307,6 +344,19 @@ update inp msg model =
307344 )
308345
309346
347+type Sink
348+ = Flush Noun
349+ | Drain Patch
350+
351+
352+deconstructSink : D.Deconstructor Sink
353+deconstructSink =
354+ D.oneOf
355+ [ D.cell (D.const D.cord "flush") D.tar |> D.map (\( (), n ) -> Flush n)
356+ , D.cell (D.const D.cord "drain") (D.lazy deconstructPatch) |> D.map (\( (), patch ) -> Drain patch)
357+ ]
358+
359+
310360processCmd : EventId -> Ur.Cmd.Cmd msg -> ( EventId, Cmd (Msg msg), List ( EventId, UrbitRequest ) )
311361processCmd eventId urCmds =
312362 let
@@ -336,10 +386,10 @@ pureCmd msg =
336386
337387processUrSubs :
338388 EventId
339− -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
340− -> Dict ( Ship, Agent, Path ) d
389+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
390+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, sink : Bool }
341391 ->
342− { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
392+ { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
343393 , eventId : EventId
344394 , subscriptionRequests : List ( EventId, UrbitRequest )
345395 , subscriptionIntMapping : Dict EventId ( Ship, Agent, Path )
@@ -348,7 +398,7 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
348398 let
349399 urbitSubs =
350400 urbitSubs_
351− |> Dict.map (\_ deconstructor -> { deconstructor = deconstructor })
401+ |> Dict.map (\_ { deconstructor, sink } -> { deconstructor = deconstructor, sink = sink })
352402
353403 ( eventId_, newSubscriptionActions ) =
354404 Dict.diff urbitSubs existingSubscriptions
@@ -371,10 +421,11 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
371421 newSubscriptions =
372422 Dict.merge
373423 (\_ _ x -> x)
374− (\key number { deconstructor } ->
424+ (\key number { deconstructor, sink } ->
375425 Dict.insert key
376426 { deconstructor = deconstructor
377427 , number = number
428+ , sink = sink
378429 }
379430 )
380431 (\_ _ x -> x)
@@ -417,6 +468,7 @@ init inp ( app, appCmds ) flags =
417468 inp.urbitUrl app ++ "/~/channel/" ++ flags.uid
418469 in
419470 ( { subscriptions = subsResult.subscriptions
471+ , sinks = Dict.empty
420472 , subscriptionIntMapping = subsResult.subscriptionIntMapping
421473 , app = app
422474 , connected = False
@@ -439,7 +491,7 @@ init inp ( app, appCmds ) flags =
439491
440492subscriptions :
441493 { a | subscriptions : b -> Sub msg, onEventSourceMsg : (JD.Value -> Msg c) -> Sub (Msg msg) }
442− -> { d | app : b, requestsToRetry : List e }
494+ -> { d | app : b, requestsToRetry : List e, connected : Bool }
443495 -> Sub (Msg msg)
444496subscriptions inp model =
445497 Sub.batch
@@ -450,4 +502,9 @@ subscriptions inp model =
450502
451503 else
452504 Time.every 1000 (always RetryRequests)
505+ , if not model.connected then
506+ Time.every 10000 (always NeedsActivation)
507+
508+ else
509+ Sub.none
453510 ]
src/Ur/Sub.elm+21−6
@@ -1,8 +1,8 @@
1−module Ur.Sub exposing (Sub, subscribe, none, batch)
1+module Ur.Sub exposing (Sub, subscribe, none, batch, sink)
22
3−{-| This module is conceptually similar to `Platform.Sub`, but also you to subscribe to Urbit channels.
3+{-| This module is conceptually similar to `Platform.Sub`, but allows you to subscribe to Urbit channels.
44
5−@docs Sub, subscribe, none, batch
5+@docs Sub, subscribe, none, batch, sink
66
77-}
88
@@ -11,7 +11,7 @@ import Ur.Deconstructor as D
1111import Ur.Sub.Internal
1212
1313
14−{-| Like `Sub` from `Platform.Sub`, but for Urbit subscriptions.
14+{-| Like `Sub` from `Platform.Sub` but for Urbit subscriptions.
1515-}
1616type alias Sub msg =
1717 Ur.Sub.Internal.Sub msg
@@ -27,9 +27,24 @@ type alias Sub msg =
2727 }
2828
2929-}
30−subscribe : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor (msg -> msg) msg } -> Sub msg
30+subscribe : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor msg } -> Sub msg
3131subscribe { ship, app, path, deconstructor } =
32− Dict.singleton ( ship, app, path ) deconstructor |> Ur.Sub.Internal.Sub
32+ Dict.singleton ( ship, app, path )
33+ { deconstructor = deconstructor
34+ , sink = False
35+ }
36+ |> Ur.Sub.Internal.Sub
37+
38+
39+{-| Creates a %sink subscription.
40+-}
41+sink : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor msg } -> Sub msg
42+sink { ship, app, path, deconstructor } =
43+ Dict.singleton ( ship, app, path )
44+ { deconstructor = deconstructor
45+ , sink = True
46+ }
47+ |> Ur.Sub.Internal.Sub
3348
3449
3550{-| A subscription that does exactly nothing. (Does not subscribe to anything)
src/Ur/Sub/Internal.elm+3−1
@@ -10,5 +10,7 @@ type Sub msg
1010 (Dict
1111 -- key is (ship, app, path)
1212 ( Ship, Agent, Path )
13− (D.Deconstructor (msg -> msg) msg)
13+ { deconstructor : D.Deconstructor msg
14+ , sink : Bool
15+ }
1416 )
src/Ur/Uw.elm+8−8
@@ -1,6 +1,6 @@
11module Ur.Uw exposing (decode, encode)
22
3−{-| This module works with Urbit base-64 encoded strings.
3+{-| This module works with Urbit base-64 encoded strings aka `@uw`.
44
55@docs decode, encode
66
@@ -29,24 +29,24 @@ decode string =
2929 -- 0w
3030 |> List.drop 2
3131
32− go : List Char -> BW.BitWriter -> BW.BitWriter
33− go cs writer =
32+ go : List Char -> List Int -> BW.BitWriter -> BW.BitWriter
33+ go cs append writer =
3434 case cs of
3535 [] ->
36− writer
36+ BW.bits append writer
3737
3838 '.' :: rest ->
39− go rest writer
39+ go rest append writer
4040
4141 c :: rest ->
4242 case Dict.get c charToBits of
4343 Just bits ->
44− go rest writer |> BW.bits bits
44+ go rest (bits ++ append) writer
4545
4646 Nothing ->
47− go rest writer
47+ go rest [] writer
4848 in
49− BW.run (go chars BW.empty)
49+ BW.run (go chars [] BW.empty)
5050
5151
5252{-| -}
tests/Test/Urbit.elm+11−11
@@ -77,7 +77,7 @@ tests =
7777 Expect.equal
7878 (Just ( 1, 2 ))
7979 (D.runBytes
80− (D.cell D.int D.int |> D.map Tuple.pair)
80+ (D.cell D.int D.int)
8181 (Bytes.fromByteValues [ 0x31, 0x12 ])
8282 )
8383 )
@@ -96,7 +96,7 @@ tests =
9696 Expect.equal
9797 (Just ( 4, [ 1, 2, 3 ] ))
9898 (D.runBytes
99− (D.cell D.int (D.list D.int) |> D.map Tuple.pair)
99+ (D.cell D.int (D.list D.int))
100100 (Bytes.fromByteValues [ 0x61, 0xC6, 0x21, 0x43, 0x0B ])
101101 )
102102 )
@@ -105,7 +105,7 @@ tests =
105105 Expect.equal
106106 (Just ( 8, 11 ))
107107 (D.runBytes
108− (D.cell D.float32 D.float32 |> D.map Tuple.pair)
108+ (D.cell D.float32 D.float32)
109109 (Bytes.fromByteValues [ 0x01, 0x1F, 0x00, 0x00, 0x20, 0x08, 0x7C, 0x00, 0x00, 0x98, 0x20 ])
110110 )
111111 )
@@ -114,7 +114,7 @@ tests =
114114 Expect.equal
115115 (Just ( 8, "hi" ))
116116 (D.runBytes
117− (D.cell D.int D.cord |> D.map Tuple.pair)
117+ (D.cell D.int D.cord)
118118 (Bytes.fromByteValues [ 0x41, 0x10, 0x3C, 0x5A, 0x1A ])
119119 )
120120 )
@@ -123,7 +123,7 @@ tests =
123123 Expect.equal
124124 (Just ( 8, "hi" ))
125125 (D.runBytes
126− (D.cell D.int D.tape |> D.map Tuple.pair)
126+ (D.cell D.int D.tape)
127127 (Bytes.fromByteValues [ 0x41, 0x30, 0x38, 0x3A, 0x78, 0x5A ])
128128 )
129129 )
@@ -134,8 +134,8 @@ tests =
134134 (Just "hi")
135135 (D.runBytes
136136 (D.oneOf
137− [ D.cell (D.const D.cord "tape") D.tape
138− , D.cell (D.const D.cord "cord") D.cord
137+ [ D.cell (D.const D.cord "tape") D.tape |> D.map (\((), t) -> t)
138+ , D.cell (D.const D.cord "cord") D.cord |> D.map (\((), c) -> c)
139139 ]
140140 )
141141 (Bytes.fromByteValues [ 0x01, 0x9F, 0x2E, 0x0C, 0xAE, 0x1C, 0x1C, 0x1D, 0x3C, 0x2D ])
@@ -147,8 +147,8 @@ tests =
147147 (Just "hi")
148148 (D.runBytes
149149 (D.oneOf
150− [ D.cell (D.const D.cord "tape") D.tape
151− , D.cell (D.const D.cord "cord") D.cord
150+ [ D.cell (D.const D.cord "tape") D.tape |> D.map (\((), t) -> t)
151+ , D.cell (D.const D.cord "cord") D.cord |> D.map (\((), c) -> c)
152152 ]
153153 )
154154 (Bytes.fromByteValues [ 0x01, 0x7F, 0xEC, 0x4D, 0x8E, 0x0C, 0x1E, 0x2D, 0x0D ])
@@ -160,7 +160,7 @@ tests =
160160 Expect.equal
161161 (Just ( 8, -8 ))
162162 (D.runBytes
163− (D.cell D.signedInt D.signedInt |> D.map Tuple.pair)
163+ (D.cell D.signedInt D.signedInt)
164164 (Bytes.fromByteValues [ 0xC1, 0x20, 0xE4, 0x01 ])
165165 )
166166 )
@@ -190,7 +190,7 @@ tests =
190190 (\( f, i, ui ) ->
191191 Expect.equal (Just ( f, i, ui ))
192192 (D.run
193− (D.cell D.float64 (D.cell D.signedInt D.int) |> D.map (\a b c -> ( a, b, c )))
193+ (D.cell D.float64 (D.cell D.signedInt D.int) |> D.map (\(a, (b, c)) -> ( a, b, c )))
194194 (C.cell (C.float64 f) (C.cell (C.signedInt i) (C.int ui)))
195195 )
196196 )