Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Create Mutable Reference in Lil

A topic by pauladam94 created 15 days ago Views: 89 Replies: 4
Viewing posts 1 to 5
(4 edits)

Here is a piece of code that bugs me a bit :

x : nil
A : on _ do 
  x : x + 1
end
# A stores a global variables and do a operation on 
# it here it is a increment
A[] # 1
A[] # 2
# Here I was expected to copy the value
# of A inside of B without any side effect
B : A
B[] # 3
B[] # 4
# B seems to work normally
A[] # 5 ?? I was expecting here 3
A[] # 6

Here, I understand that now A and B share a common reference to the global variable x.

By doing something like this to create the function inside of A we can even make the global variable hidden from the outer scope :

on f x do
  on _ do
    x : x + 1
  end
end
A : f[0]
B : A
x # is nil not accessible
A[] # 1
B[] # 2
A[] # 3
B[] # 4

 

Reading about Lil I thought that the "graph" of pointers inside the program was very simple because everything is a value  and everything is copied. However, if it is possible to store hidden information inside the function I might have not understood something. Is this normal behavior ?

(2 edits)

I think the most important thing about the previous code is creating mutable reference that can be passed around. This is fine in most programming languages however it does not seem to be a design choice of Lil at all to have this design pattern.

By putting global variable inside of functions we can create a reference.
Then it is also possible to modify and get the value of the reference.

# Create a reference
on create_ref do
  # we give ourselves the possibility to apply a function on the reference
  on _ f do
    x : f[x]
  end
end
# sets the value of a reference "ref" to "val"
on set ref val do
  ref[on _ x do val end] 
end
# get the value of the reference "ref"
on get ref do
  ref[on id x do x end]
end
# this reference can be passed around in function argument
a : create_ref[]
set[a 0] # 0
set[a 1] # 1
# here we copy the reference
b : a
# when b is modified, a is also modified
set[b 42] # 42
get[a] # 42

Then we can pass this reference around in functions :

on complex_computation ref do
  value_res : ... # result of the computation
  # instead of returning the computation we can modify 
  # the reference to get the result just like a buffer 
  # given in argument in a C program for example
  set[ref value_res]
  nil
end
buffer : create_ref[]
complex_computation[buffer] # returns nil
get[buffer] # gets us the value of the computation 
(+1)

Reading about Lil I thought that the “graph” of pointers inside the program was very simple because everything is a value and everything is copied. However, if it is possible to store hidden information inside the function I might have not understood something. Is this normal behavior ?

Yes, this is normal behaviour - like a lot of functional languages, Lil has closures. The Lil docs say:

Lil uses lexical scope: variables will resolve to the closest nested binding available, and the local variables of a caller to a function will not be visible or modified by the callee (unless the callee’s definition is nested in the caller)…

Furthermore, functions close over variables in their lexical scope, allowing for encapsulated “objects” with their own mutable state

It even gives an example of putting x : x + 1 inside a nested function so you can make a counter, as you did in your second example.

Ok, I haven't seen the "mutable state objects" remark in the docs. Thanks !

This can be used in script to mutable reference.

However, this mutable objects will not be able to be persistent because no widget can store function right ?

Developer(+1)

Lil variables are mutable; Lil primitive datatypes (numbers, strings, lists, dictionaries, tables) are immutable. Decker provides several "interface" types which can represent mutable data structures: Array, Image, Sound, and (much more flexibly in the v1.64 Decker release than in v1.63 and earlier) KeyStore.

Within the Decker environment, most closures only live as long as a single event, though "blocking"/synchronous scripts could persist for many frames.

There is no built-in mechanism for serializing a function along with its captured closure, so you can't e.g. stash a callback for later in a field's .data attribute. None of Decker's APIs are designed around callbacks for this reason; we instead send events to deck-parts like widgets or cards.

Modules have closures which persist as long as a deck is open and their script isn't modified, so in principle they offer opportunities for weird and fanciful (ab)uses of mutable closure.

And of course, in Lil scripts run with Lilt, outside the Decker environment, the world is your oyster.