Created
December 29, 2023 17:33
-
-
Save lifepillar/d44e6ca33f0b1f66a0b403e133413699 to your computer and use it in GitHub Desktop.
Building a Reactive Library from Scratch in Vim 9 Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vim9script | |
# Translation into Vim 9 script of the tutorial at: | |
# https://dev.to/ryansolid/building-a-reactive-library-from-scratch-1i0p | |
# Helper functions and classes {{{ | |
def IndexOf(items: list<any>, item: any): number | |
return indexof(items, (_, e) => e is item) | |
enddef | |
def NotIn(v: any, items: any): bool | |
return indexof(items, (_, u) => u is v) == -1 | |
enddef | |
class Set | |
var _set: list<any> = [] | |
def Items(): list<any> | |
return this._set | |
enddef | |
def Add(item: any) | |
if item->NotIn(this._set) | |
this._set->add(item) | |
endif | |
enddef | |
def Remove(item: any) | |
const pos = IndexOf(this._set, item) | |
if pos != -1 | |
this._set->remove(pos) | |
endif | |
enddef | |
def Clear() | |
this._set = [] | |
enddef | |
endclass | |
def AsSet(v: Set): Set | |
return v | |
enddef | |
class Reaction | |
var Execute: func(): void | |
var dependencies: Set | |
endclass | |
def AsReaction(v: Reaction): Reaction | |
return v | |
enddef | |
class Stack | |
var _stack: list<Reaction> = [] | |
def Empty(): bool | |
return empty(this._stack) | |
enddef | |
def Push(r: Reaction) | |
this._stack->add(r) | |
enddef | |
def Pop(): Reaction | |
return this._stack->remove(-1) | |
enddef | |
def Top(): Reaction | |
return this._stack[-1] | |
enddef | |
endclass | |
# }}} | |
var gContext = Stack.new() | |
def Cleanup(running: Reaction) | |
for dep in running.dependencies.Items() | |
AsSet(dep).Remove(running) | |
endfor | |
running.dependencies.Clear() | |
enddef | |
def CreateEffect(Fn: func) | |
var running: Reaction | |
const Execute = () => { | |
Cleanup(running) | |
gContext.Push(running) | |
try | |
Fn() | |
finally | |
gContext.Pop() | |
endtry | |
} | |
running = Reaction.new(Execute, Set.new()) | |
Execute() | |
enddef | |
def Subscribe(running: Reaction, subscriptions: Set) | |
subscriptions.Add(running) | |
running.dependencies.Add(subscriptions) | |
enddef | |
def CreateSignal(value: any = null): list<func> | |
var _value = value | |
var _subscriptions = Set.new() | |
const Read = () => { | |
if !gContext.Empty() | |
const running = gContext.Top() | |
Subscribe(running, _subscriptions) | |
endif | |
return _value | |
} | |
const Write = (v: any) => { | |
_value = v | |
for sub in _subscriptions.Items() | |
AsReaction(sub).Execute() | |
endfor | |
} | |
return [Read, Write] | |
enddef | |
def CreateMemo(Fn: func): func | |
const [Re, Wr] = CreateSignal() | |
CreateEffect(() => Wr(Fn())) | |
return Re | |
enddef | |
echo "EXAMPLE 1" | |
echo "1. Create signal" | |
const [Count, SetCount] = CreateSignal(0) | |
echo "2. Create Reaction" | |
CreateEffect(() => { | |
echo "The count is" Count() | |
}) | |
echo "3. Set count to 5" | |
SetCount(5) | |
echo "4. Set count to 10" | |
SetCount(Count() * 2) | |
echo "EXAMPLE 2" | |
echo "1. Create signals" | |
const [FirstName, SetFirstName] = CreateSignal("John") | |
const [LastName, SetLastName] = CreateSignal("Smith") | |
const FullName = (): string => { | |
echo "Creating/updating full name" | |
return FirstName() .. ' ' .. LastName() | |
} | |
echo "2. Create Reactions" | |
CreateEffect(() => { | |
echo "My name is" FullName() | |
}) | |
CreateEffect(() => { | |
echo "Your name is not" FullName() | |
}) | |
echo "3. Set new first name" | |
SetFirstName("Jacob") | |
echo "EXAMPLE 3" | |
echo "1. Create signals" | |
const [FirstNameBis, SetFirstNameBis] = CreateSignal("John") | |
const [LastNameBis, SetLastNameBis] = CreateSignal("Smith") | |
echo "2. Create Derivation" | |
const FullNameBis = CreateMemo((): string => { | |
echo "Creating/updating full name" | |
return FirstNameBis() .. ' ' .. LastNameBis() | |
}) | |
echo "3. Create Reactions" | |
CreateEffect(() => { | |
echo "My name is" FullNameBis() | |
}) | |
CreateEffect(() => { | |
echo "Your name is not" FullNameBis() | |
}) | |
echo "3. Set new first name" | |
SetFirstNameBis("Jacob") | |
echo "EXAMPLE 4" | |
echo "1. Create signals" | |
const [FirstNameTer, SetFirstNameTer] = CreateSignal("John") | |
const [LastNameTer, SetLastNameTer] = CreateSignal("Smith") | |
const [ShowFullName, SetShowFullName] = CreateSignal(true) | |
echo "2. Create Derivation" | |
const DisplayName = CreateMemo((): string => { | |
if !ShowFullName() | |
return FirstNameTer() | |
endif | |
return FirstNameTer() .. ' ' .. LastNameTer() | |
}) | |
echo "3. Create Reaction" | |
CreateEffect(() => { | |
echo "My name is" DisplayName() | |
}) | |
echo "4. Update names" | |
SetShowFullName(false) | |
SetLastNameTer("Legend") # No side effect because last name is not tracked | |
SetShowFullName(true) | |
SetLastNameTer("Denver") # Full name is tracked, so side effect is triggered |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment