Skip to content

Lazy Relation - #125

Merged
badetitou merged 12 commits into
developmentfrom
lazy-relations
Sep 3, 2026
Merged

Lazy Relation#125
badetitou merged 12 commits into
developmentfrom
lazy-relations

Conversation

@badetitou

@badetitou badetitou commented Sep 2, 2026

Copy link
Copy Markdown
Member

For incremental loading, we might need to perform lazy loading of some relation.
To do so, this PR introduces a first implementation of lazyness for relations.

Design

The idea is to apply the update at how Fame slots are working to introduce an extensibility endpoint instead of modifying the existing model accessors.
The modification must work on existing relation generated by the generator.

The idea is to use polymorphism to check if we are in a lazy relation, or in a normal relation.

  1. In case of lazy -> materialize the relation
  2. In normal case -> return the value

Implementation

The PR introduces the class FMLazyValue that represent a relation/Value that needs to be resolved when first accessed.
For instance, when installed on methods of FamixJavaClass, it will computes a custom block that can be used to do whatever things, and fill the future methods relationship.

API

You can execute the following piece of code to install the lazy relation on a slot of a specific entity.

  • relation will contains the
    • FMSlotMultivalueLink that need to be used (for Multi relation)
    • an FMLazyOneHolder that will (for One relation)
  • entity the entity that hold the slot
FMLazyRelation makeRelation: #kills of: entity lazyUsing: [ :relation :entity |
		relation addAll:
		        {
			        RPGLazyDragon new.
			        RPGLazyDragon new } ].

Or the One relation

FMLazyRelation makeRelation: #kills of: entity lazyUsing: [ :relation :entity |
		relation value: RPGLazyDragon new ].

Polymorphism

I introduce the method fmValueReadFrom:owner: in Object and FMLazyValue. It is called when reading the value of a FMRelationSlot.

The FMRelationSlot are used to represent the relation in Moose.

A first implementation would have been to use isKindOf: but this version uses polymorphism instead.

I dislike the idea of modyfing the read: of FMRelationSlot.
I can see two others options:

  1. Using Pharo Reflexivity: but it would hide stuff when reading the code, meaning it will be harder to understand in future maintenance task.
  2. Using a decorator pattern on slot: but it does not exist in Pharo and so need long time before integration in Moose. This is what I wanted to do in the first place.

This two other version would have allowed to remove the polymorphism dispatch (and so performance).

I did not check the performance update, but this is a simple dispatch. Should not be that much. Should we check on large graph exploration?

Consideration

We ask ourselves: What is the behavior of setting up an array to a Multi-Multi lazy oppositite relationships?

Motivation example.

entity := RPGLazyHero new.

FMLazyValue
	makeRelation: #kills
	of: entity
	lazyUsing: [ :rel :entit |
		rel addAll: {
				RPGLazyDragon new.
				RPGLazyDragon new } ].
dragon := RPGLazyDragon new.
dragon killedBy: { entity }.

Should the hero have killed 1 dragon (the dragon variable), 2 dragons (the lazy ones), 3 dragons (lazy + variable).

We decided for three.
The accessor should not remove first lazyness setup.
The dragon variable cannot be ignore.

Future consideration: we might think of an API to explicitly control the behavior

Architecture

classDiagram
    FMLazyValue <|-- FMLazyMany
    FMLazyValue <|-- FMLazyOne
    FMLazyOne "1" --> "1" FMLazyOneHolder : value
Loading

Limitation

The current design of FMMany and FMOne makes things difficult in some cases.

In particular, using our design, updating the opposite of a Lazy One relation will not remove the Lazyness of the One side.

Example:

entity := RPGLazyTalisman new.
hero := RPGLazyHero new.
hero2 := RPGLazyHero new.

FMLazyValue
	makeRelation: #owner
	of: entity
	lazyUsing: [ :rel :entit | rel value: hero ].
hero2 talismans: { entity }.
self assert: entity owner identicalTo: hero2.
self assert: hero talismans size equals: 0.
self assert: hero2 talismans anyOne identicalTo: entity

When executing entity owner, it will trigger again the lazy part of the code.
This is because FMOne>>#writeInverse:to: uses an inner API instead of the default write API to avoid recursive call of the two side update of Fame

This limitation exists for both One-Many and One-One relationship. there are two tests marked as self flag: #currentLimitation that can be used to highlight the limitation

@Gabriel-Darbord Gabriel-Darbord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Benoit.
I'm a bit afraid of the current limitations, I can foresee weird bugs happening in the future, but fixing them would require modifying other parts of Fame, so I think this is fine for a first version.

I have just a few change requests: skip tests rather than comment them, and format the code.

Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st
Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st Outdated
Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st Outdated
Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st Outdated
Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st Outdated
Comment thread src/Fame-LazyRelations-Tests/FMLazyRelationTest.class.st Outdated
Comment thread src/Fame-LazyRelations/FMLazyOne.class.st Outdated
Comment thread src/Fame-LazyRelations/FMLazyValue.class.st Outdated
Comment thread src/Fame-LazyRelations/FMLazyValue.class.st Outdated
Comment thread src/Fame-LazyRelations/FMLazyValue.class.st Outdated
badetitou and others added 5 commits September 3, 2026 14:48
Co-authored-by: Gabriel Darbord <78592838+Gabriel-Darbord@users.noreply.github.com>
Co-authored-by: Gabriel Darbord <78592838+Gabriel-Darbord@users.noreply.github.com>
Co-authored-by: Gabriel Darbord <78592838+Gabriel-Darbord@users.noreply.github.com>

@Gabriel-Darbord Gabriel-Darbord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@badetitou
badetitou merged commit 84db223 into development Sep 3, 2026
5 checks passed
@badetitou
badetitou deleted the lazy-relations branch September 3, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants