Skip to content

Instantly share code, notes, and snippets.

@lifepillar
Created December 29, 2023 17:33
Show Gist options
  • Save lifepillar/d44e6ca33f0b1f66a0b403e133413699 to your computer and use it in GitHub Desktop.
Save lifepillar/d44e6ca33f0b1f66a0b403e133413699 to your computer and use it in GitHub Desktop.
Building a Reactive Library from Scratch in Vim 9 Script
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