B4J Question Immutable Map

aeric

Expert
Licensed User
Longtime User
While developing Pakai v6, I faced an issue that I wish I don't want to talk about.
I tried numerous time but still failed to achieve a concept that I thought was easy.

I have forgotten that Map is always passed by reference.
I wished we have something like immutable map in B4J or B4X where I can declare a map, assign the value once and use many times which the value is not going to change.
Or we can pass by value to the map.

What I was trying to achieve is once I have generated a "static" html template (using MiniHtml), I want to keep the object inside a memory so I don't need to regenerate it again every time a request is made. I think this will boost the performance or make the app more efficient.

I wanted to use a global map in the server app Main module or maybe a thread safe map if it is necessary.
I like map because I can retrieve the value by a key very fast.

When I tried to assign the generated tag object into a map, the value can mutate and reference back to the map. This is not what I want.

For example, I have generated a html table row template look like this:
<tr><td></td><td></td><td></td></tr>

On first loop, I will pass the id = 1 and other values from the database query to this template object.
<tr><td>1</td><td>AAA</td><td>0001</td></tr>

On second loop, the template has already mutated with the updated value above. It is no longer the original template.

I tried to use KeyvalueStore.
However, the value is a non primitive type or more specificly the Tag class of MiniHtml. it will failed to deserialize from KVS.

I can convert the object to String first before storing to KVS but this will defeat the purpose. I have to parse the String and convert it back to Tag object. I think this will involve some CPU computation again. Why not just use something we have already processed?

So, I think immutable map is a good feature to add into B4J.

Or does any member have any alternative solution that is easy to implement?
 
Solution
B4X:
Private Sub Test8 ' Magma (edited)
    Dim trTemplate As Tag
    trTemplate = Tr.init
    Td.up(trTemplate)
    Td.up(trTemplate)
    Td.up(trTemplate)

    Dim Cache As Map = CreateMap("trTemplate": trTemplate)
        
    Dim Data As List = Array(CreateMap("name": "AAA"), CreateMap("name": "BBB"), CreateMap("name": "CCC"))

    Dim tbody1 As Tag = Tbody.init
    For Each item As Map In Data
        ' use DeepCloneTag <> Clone
        Dim trRow1 As Tag =  Cache.Get("trTemplate").As(Tag).DeepCloneTag(Cache.Get("trTemplate").As(Tag))
        trRow1.Child(1).text2(item.Get("name")) ' use text2 to be sure 
        LogColor(trRow1.Build, -16776961)
        trRow1.up(tbody1)
    Next
    
    LogColor(tbody1.Build, -65536)
End Sub
...

Magma

Expert
Licensed User
Longtime User
How to implement... ?
How about a List with rows of maps ? (So getting row with list.get(x))
Also if you want a predefined map... have a defaultMap assigning before adding at list the row (data)

You know that Is not a standard way - it has to do with your thoughts - but I think that for you... is a piece of cake
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
How to implement... ?
How about a List with rows of maps ? (So getting row with list.get(x))
Also if you want a predefined map... have a defaultMap assigning before adding at list the row (data)

You know that Is not a standard way - it has to do with your thoughts - but I think that for you... is a piece of cake
List and Map are passed by reference.
Once you assigned a new value to it, the original map get the same value.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
List and Map are passed by reference.
Once you assigned a new value to it, the original map get the same value.
Hmmm

B4X:
    Dim envfees As List
    envfees.Initialize
    Dim mapitems As Map
    mapitems.Initialize
    mapitems.put("Enviromental Fee","")
    mapitems.put("Env.Fee","0.00")
    envfees.Add(mapitems)

    mapitems.Initialize
    mapitems.put("Enviromental Fee","Plastic")
    mapitems.put("Env.Fee","0.08")
    envfees.Add(mapitems)

    mapitems.Initialize
    mapitems.put("Enviromental Fee","Glass")
    mapitems.put("Env.Fee","0.11")
    envfees.Add(mapitems)

* Also sometimes need the power of wait for () complete --- especially for async... works - have this in mind too
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Hmmm

B4X:
    Dim envfees As List
    envfees.Initialize
    Dim mapitems As Map
    mapitems.Initialize
    mapitems.put("Enviromental Fee","")
    mapitems.put("Env.Fee","0.00")
    envfees.Add(mapitems)

    mapitems.Initialize
    mapitems.put("Enviromental Fee","Plastic")
    mapitems.put("Env.Fee","0.08")
    envfees.Add(mapitems)

    mapitems.Initialize
    mapitems.put("Enviromental Fee","Glass")
    mapitems.put("Env.Fee","0.11")
    envfees.Add(mapitems)

* Also sometimes need the power of wait for () complete --- especially for async... works - have this in mind too
You don't get what I mean.
I already provided an example in first post. I am assigning a non primitive value (not String) to a new map and the original map value is updated too.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
You don't get what I mean.
I already provided an example in first post. I am assigning a non primitive value (not String) to a new map and the original map value is updated too.
with non primitive values... you mean objects like tag of Customviews, Buttons, etc ?
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
with non primitive values... you mean objects like tag of Customviews, Buttons, etc ?
I tried to use KVS but the stored object instantiated from Tag class has failed to deserialized.
But I am looking for something like Map or Collections instead of a persistent solution.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
Wiki: "an immutable object is an object whose state cannot be modified after it is created"
If you accept that "hard to change by accident" is good enough, you can create a master map of maps that stores each newly created map under a unique context determined name. Then access would require a double reference: master.get("context").as(map).get("mapName").
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
May be Python can help... if you want to mess it...​
No Python. Why no B4X? Why make it more complicated?

The attempt was in Pakaiv6 beta4. I have missed to made some commits.

When I try to reuse a template, the previous items are overwitten by the next item.
Means, if I have 3 items with sample below
id​
name​
1​
Teddy Bear​
2​
Hammer​
3​
Optimus Prime​

I get the result as:
id​
name​
3​
Optimus Prime​
3​
Optimus Prime​
3​
Optimus Prime​
 

Attachments

  • ProductsHandler.bas
    20.4 KB · Views: 32
  • CategoriesHandler.bas
    14 KB · Views: 40
Upvote 0

Magma

Expert
Licensed User
Longtime User
I know t
No Python. Why no B4X? Why make it more complicated?

The attempt was in Pakaiv6 beta4. I have missed to made some commits.

When I try to reuse a template, the previous items are overwitten by the next item.
Means, if I have 3 items with sample below
id​
name​
1​
Teddy Bear​
2​
Hammer​
3​
Optimus Prime​

I get the result as:
id​
name​
3​
Optimus Prime​
3​
Optimus Prime​
3​
Optimus Prime​
I ve understand what you ve mean

ok now you are doing to me - complicated... need to have pakai and all things... that may be i love them... but what about targetting at the problem with simple b4j code without extras :)

Need to spend a lot of time...
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Consider the following code:
B4X:
Private Sub RenderPage
    If KVS.ContainsKey("/categories") = False Then
        Dim main1 As MainView
        main1.Initialize
        main1.LoadContent(ContentContainer)
        main1.LoadModal(ModalContainer)
        main1.LoadToast(ToastContainer)
   
        Dim page1 As Tag = main1.Render
        Dim doc As Document
        doc.Initialize
        doc.AppendDocType
        doc.Append(page1.build)

        ' Store for reuse
        KVS.Put("/categories", App.ReplaceMap(doc.ToString, App.ctx))
    End If
    App.WriteHtml(Response, KVS.Get("/categories"))
End Sub
This is already converted to String.

But if I set the value as Tag, it failed.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
Consider the following code:
B4X:
Private Sub RenderPage
    If KVS.ContainsKey("/categories") = False Then
        Dim main1 As MainView
        main1.Initialize
        main1.LoadContent(ContentContainer)
        main1.LoadModal(ModalContainer)
        main1.LoadToast(ToastContainer)
  
        Dim page1 As Tag = main1.Render
        Dim doc As Document
        doc.Initialize
        doc.AppendDocType
        doc.Append(page1.build)

        ' Store for reuse
        KVS.Put("/categories", App.ReplaceMap(doc.ToString, App.ctx))
    End If
    App.WriteHtml(Response, KVS.Get("/categories"))
End Sub
This is already converted to String.

But if I set the value as Tag, it failed.
....
what replacemap doing ?
where response coming from ?
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
This works because the value is String:
B4X:
' Store for reuse
KVS.Put("/categories", "<html> ... </html>")

This is not working:
B4X:
Dim page1 As Tag = main1.Render
' Store for reuse
KVS.Put("/categories", page1)
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Actually the easiest solution is not to use Map. Just use a variable. 😁

... and forget about the concept of reusable template.
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
See #9!
Make the master map into a class to ease use.

B4X:
'ImMap Class'
Sub Class_Globals
    Private fx As JFX
    Private ThisMap As Map
    Private defaultObject As Object
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(default As Object) As ImMap
    ThisMap.initialize
    defaultObject = default
    Return Me
End Sub

Public Sub Put(mapId As String, mp As Map)
    Dim tempMap As Map
    tempMap.Initialize
    For Each key As Object In mp.Keys
        tempMap.Put(key, mp.Get(key))
    Next
    ThisMap.put(mapId, tempMap)
End Sub

Public Sub Get(mapId As String, key As Object) As Object
    Dim tempMap As Map = ThisMap.get(mapId)
    Return tempMap.GetDefault(key, defaultObject)
End Sub

The use as this

B4X:
    Private immp As ImMap
    immp.Initialize("???")


    immp.Put("Context1", CreateMap("A": 1000))
    immp.Put("Context2", CreateMap("A": 3000, "C": 5000))
    
    Log(immp.Get("Context2", "A"))
 
Upvote 0
Top