Skip to content

Commit 38bb1e7

Browse files
committed
init
0 parents  commit 38bb1e7

27 files changed

+2212
-0
lines changed

.gitignore

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
*.annot
2+
*.cmo
3+
*.cma
4+
*.cmi
5+
*.a
6+
*.o
7+
*.cmx
8+
*.cmxs
9+
*.cmxa
10+
11+
.merlin
12+
*.install
13+
*.coverage
14+
*.sw[a-z]
15+
16+
_build/
17+
_doc/
18+
_coverage/
19+
_opam/
20+
playground/codemirror/

.ocamlformat

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
profile = janestreet
2+
let-binding-spacing = compact
3+
sequence-style = separator
4+
doc-comments = after-when-possible

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Arthur Wendling
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Can GUI programming be purely functional? This library demonstrates a small algebra of 7 combinators with intuitive laws to make GUI programming super concise, declarative, easy to reason and efficient to use. It's a bold claim! As the humble name of this library suggests, I'm too biased to be trusted. You will have to judge by yourself if it delivers:
2+
3+
[**Try the interactive online tutorial**](https://art-w.github.io/unicorn/playground.html) | [**Documentation**](https://art-w.github.io/unicorn/unicorn_jsoo/Unicorn_jsoo/index.html)
4+
5+
This is a very early release focused on showing off the combinators API. A lot of features and optimizations are still missing for real world usage! Bugs are expected. You will get the most of it if you enjoy playing with weird new toys... or if you have an interest in the theoretical questions: How can widgets have internal state but also be referentially transparent? What does it mean to "move" a pure widget? Can we do reactive GUIs without FRP or monads?
6+
7+
Unicorn took years of inspiration from previous works, ranging from the immediate mode community to the most hardcore of Haskell's "Arrowized FRP" GUIs. It should look familiar at first -- but different choices allow it to sidestep the complexity and the semantic hacks. The `dynamic` value turns this API into a different beast, one that has as much to teach us as it is avid on learning purely functional tricks. I'm still discovering new ideas and having fun, so I hope you will also find something cool in there. Get in touch if you do!

dune-project

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
(lang dune 2.8)
2+
(generate_opam_files true)
3+
4+
(name unicorn)
5+
(source (github art-w/unicorn))
6+
(license MIT)
7+
(authors "Arthur Wendling")
8+
(maintainers "[email protected]")
9+
(version 0.1)
10+
11+
(package
12+
(name unicorn_jsoo)
13+
(synopsis "Purely functional GUI library (js_of_ocaml backend)")
14+
(depends
15+
(ocaml (>= "4.12.0"))
16+
js_of_ocaml
17+
optic))
18+
19+
(package
20+
(name optic)
21+
(synopsis "Isomorphisms, lenses and prisms")
22+
(depends
23+
(ocaml (>= "4.08"))))
24+
25+
(package
26+
(name ppx_deriving_optic)
27+
(synopsis "PPX deriver for lenses and prisms")
28+
(depends
29+
(ocaml (>= "4.08"))
30+
ppxlib
31+
ppx_deriving))

jsoo/algebra.ml

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
open Optic
2+
open Type
3+
4+
type 'a t = 'a Type.t
5+
6+
let empty : type a. a t = W ((), (), Eq.unit, fun x -> x, Dag.empty ())
7+
8+
let ( & ) (W (c0, s0, _, w0)) (W (c1, s1, _, w1)) =
9+
W
10+
( (None, c0, c1)
11+
, (s0, s1)
12+
, Eq.create ()
13+
, fun ((x, (s0, s1), (cache, c0, c1)) as input) ->
14+
match cache with
15+
| Some (x', s0', s1', img) when x == x' && s0 == s0' && s1 == s1' -> input, img
16+
| _ ->
17+
let (x, s0, c0), img0 = w0 (x, s0, c0) in
18+
let (x, s1, c1), img1 = w1 (x, s1, c1) in
19+
let img = Dag.seq img0 img1 in
20+
let cache = Some (x, s0, s1, img) in
21+
(x, (s0, s1), (cache, c0, c1)), img )
22+
23+
let iso iso (W (c, s, _, w)) =
24+
let iso' =
25+
{ Iso.ltor = (fun (x, y) -> Iso.ltor iso x, y)
26+
; rtol = (fun (x, y) -> Iso.rtol iso x, y)
27+
}
28+
in
29+
let eq_iso = Eq.create () in
30+
W
31+
( (None, c)
32+
, s
33+
, Eq.create ()
34+
, fun ((x, s, (cache, c)) as input) ->
35+
match cache with
36+
| Some (x', s', img) when x == x' && s == s' -> input, img
37+
| _ ->
38+
let y = Optic.Iso.ltor iso x in
39+
let (y, s, c), img = w (y, s, c) in
40+
let x = Optic.Iso.rtol iso y in
41+
let img = Dag.iso eq_iso iso' img in
42+
let cache = Some (x, s, img) in
43+
(x, s, (cache, c)), img )
44+
45+
let on lens (W (c, s, _, w)) =
46+
let eq_lens = Eq.create () in
47+
W
48+
( (None, c)
49+
, s
50+
, Eq.create ()
51+
, fun ((x, s, (cache, c)) as input) ->
52+
match cache with
53+
| Some (x', s', img) when x == x' && s == s' -> input, img
54+
| _ ->
55+
let y = Optic.Lens.get lens x in
56+
let (y, s, c), img = w (y, s, c) in
57+
let x = Optic.Lens.put lens y x in
58+
let img = Dag.on eq_lens lens img in
59+
let cache = Some (x, s, img) in
60+
(x, s, (cache, c)), img )
61+
62+
let into prism (W (c, s, _, w)) =
63+
let eq_prism = Eq.create () in
64+
W
65+
( (None, c)
66+
, s
67+
, Eq.create ()
68+
, fun ((x, s, (cache, c)) as input) ->
69+
match cache with
70+
| Some (x', s', img) when x == x' && s == s' -> input, img
71+
| _ ->
72+
(match Optic.Prism.extract prism x with
73+
| None ->
74+
let img = Dag.empty () in
75+
let cache = Some (x, s, img) in
76+
(x, s, (cache, c)), img
77+
| Some y ->
78+
let (y, s, c), img = w (y, s, c) in
79+
let x = Optic.Prism.make prism y in
80+
let img = Dag.into eq_prism prism img in
81+
let cache = Some (x, s, img) in
82+
(x, s, (cache, c)), img) )
83+
84+
let cond predicate w = into (Prism.satisfy predicate) w
85+
86+
let ifte predicate if_true if_false =
87+
cond predicate if_true & cond (fun x -> not (predicate x)) if_false
88+
89+
let reorder : type a b c. (a * (b * c), (b * a) * c) Optic.iso =
90+
{ Optic.Iso.ltor = (fun (x, (s0, s1)) -> (s0, x), s1)
91+
; rtol = (fun ((s0, x), s1) -> x, (s0, s1))
92+
}
93+
94+
let stateful s0 (W (c, s1, _, w)) =
95+
let eq_iso = Eq.create () in
96+
W
97+
( (None, c)
98+
, (s0, s1)
99+
, Eq.create ()
100+
, fun ((x, (s0, s1), (cache, c)) as input) ->
101+
match cache with
102+
| Some (x', s0', s1', img) when x == x' && s0 == s0' && s1 == s1' -> input, img
103+
| _ ->
104+
let ((s0, x), s1, c), img = w ((s0, x), s1, c) in
105+
let img = Dag.iso eq_iso reorder img in
106+
let cache = Some (x, s0, s1, img) in
107+
(x, (s0, s1), (cache, c)), img )
108+
109+
let dynamic : type a. (a t * a) t =
110+
W
111+
( ()
112+
, ()
113+
, Eq.unit
114+
, fun (wx, (), ()) ->
115+
let W (c, s, seq, w), x = wx in
116+
let (x, s, c), img = w (x, s, c) in
117+
let wx = W (c, s, seq, w), x in
118+
let img = Dag.dynamic seq img in
119+
(wx, (), ()), img )
120+
121+
(********************************************************************************)
122+
123+
let ( <*> ) a b = on Lens.fst a & on Lens.snd b
124+
125+
let initialize : type a b. (a -> b) -> (b option * a, b * a) Optic.iso =
126+
fun fn ->
127+
{ Optic.Iso.ltor =
128+
(fun (s0, x) ->
129+
let s0 =
130+
match s0 with
131+
| None -> fn x
132+
| Some s0 -> s0
133+
in
134+
s0, x)
135+
; rtol = (fun (s0, x) -> Some s0, x)
136+
}
137+
138+
let stateful_by fn w = stateful None (iso (initialize fn) w)
139+
let of_lazy w = stateful_by (fun _ -> Lazy.force w) dynamic
140+
let apply f x = of_lazy (lazy (f x))
141+
142+
let fix fn =
143+
let rec self = lazy (fn (of_lazy self)) in
144+
Lazy.force self
145+
146+
let of_list ws = List.fold_left ( & ) empty ws
147+
let list w = fix (fun lst -> into Prism.cons (w <*> lst))

jsoo/attr.ml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
open! Js_of_ocaml
2+
module M = Map.Make (String)
3+
4+
type value =
5+
| String of string
6+
| Bool of bool
7+
8+
type t = value M.t
9+
10+
let empty = M.empty
11+
let single key value = M.singleton key value
12+
let add key value t = M.add key value t
13+
14+
let merge a b =
15+
M.merge
16+
(fun _ ox oy ->
17+
match ox, oy with
18+
| _, Some _ -> oy
19+
| _, None -> ox)
20+
a
21+
b
22+
23+
let dom_remove ~(node : Dom_html.element Js.t) key =
24+
let key = Js.string key in
25+
node##removeAttribute key ;
26+
Js.Unsafe.delete node key
27+
28+
let dom_add ~(node : Dom_html.element Js.t) key value =
29+
let key = Js.string key in
30+
match value with
31+
| String value ->
32+
let value = Js.string value in
33+
Js.Unsafe.set node key value ;
34+
node##setAttribute key value
35+
| Bool value ->
36+
Js.Unsafe.set node key (Js.bool value) ;
37+
if value then node##setAttribute key (Js.string "") else node##removeAttribute key
38+
39+
let update ~(node : Dom_html.element Js.t) ~old ~latest =
40+
ignore
41+
@@ M.merge
42+
(fun key ox oy ->
43+
(match ox, oy with
44+
| Some _, None -> dom_remove ~node key
45+
| _, Some attr -> dom_add ~node key attr
46+
| _ -> ()) ;
47+
None)
48+
old
49+
latest

0 commit comments

Comments
 (0)