humbleui

fricze 2024-08-06T06:50:51.850069Z

I’ve been having some fun with Humble OOP system, trying to make fields declared in parent nodes visible to IDEs/editors I don’t see easy way to make editors just “know” what fields come from parents, so they have to be somehow declared/referenced on a call-site. one idea I had was to switch from :extends Parent field to (extends Parent [par-field1 par-field2] (method-def [])) call, that would allow referencing only fields from given parent. then editor might treat this call as deftype , see field references and you could even add check, on (extends) macro level, to see whether fields you reference are actually declared on parent level

fricze 2024-08-06T06:54:43.848799Z

this one seemed a bit tedious though, so I went an implemented other idea, where parents declare not only their fields, but also declare “accessors” to those fields, that might be used in children extending those parents. the downside of this solution is that consumer who’s extending the parent has to access parents’ fields via those accessors, so it’s a bit more work. upside is that editors understand it easily, implementation was also pretty easy, and it makes code (maybe) bit more readable, because you directly see that field you access was declared on parent level

fricze 2024-08-06T06:55:55.288829Z

defparent and deftype+ that handle those changes live here https://github.com/fricze/hnhnhn/blob/main/src/oop.clj

fricze 2024-08-06T06:57:17.227069Z

defparent declares not only the definition map, as before, but also declares a record with those “field accessors”, so editors understand everything. you access field from the record, normal stuff

fricze 2024-08-06T06:58:03.254999Z

deftype+ has only one change, additional replace-parent-fields call on body. it looks for all those “field accessors” and replaces them with symbols for particular fields

fricze 2024-08-06T06:58:58.169099Z

https://github.com/fricze/hnhnhn/blob/main/src/scrollable.clj here you can see how it’s called

fricze 2024-08-06T06:59:35.346099Z

instead of writing (set! child-size (protocols/-measure child ctx child-cs)) I do (set! child-size (protocols/-measure (.child AWrapperNode) ctx child-cs)) so you see that child field is accessed via parent

fricze 2024-08-06T07:01:49.667279Z

another upside of this implementation is that when you try to access non-existent field on a parent it immediately throws, on a compile-time

fricze 2024-08-06T07:11:18.347109Z

I would risk an opinion that it looks fairly clean

fricze 2024-08-06T07:49:18.093799Z

@tonsky it’d need some polishing but curious whether you see something like that an acceptable solution for editor support of Humble parents fields

fricze 2024-08-11T09:00:35.027649Z

I was looking at consuming Java abstract classes from Clojure. it doesn’t look fun. there’s no way to dynamically extend Java abstract classes. there’s only gen-class that requires AOT compilation, so basically no-go for any proper REPL-based development. there’re two proposed patches that would make it easier, but they’re both stale for last 5 years https://clojure.atlassian.net/browse/CLJ-1255 https://clojure.atlassian.net/browse/CLJ-2343 so we’re left with using Java interfaces + classes, but without abstract classes there’s no gain over Clojure protocols + deftypes. and Java interfaces do not allow mutable fields, so yeah. I’ll look around is there a userland solution for that, in the meantime I’m trying to teach clj-kondo how to parse deftype+

Niki 2024-08-06T10:14:44.478909Z

I’m not sure. (.child AWrapperNode) this reads accessing method on a class

Niki 2024-08-06T10:15:20.120419Z

What kind of editor are you testing this on? You were able to teach it about custom .field meaning?

Niki 2024-08-06T10:16:01.239629Z

Or does it just stopped complaining because it looks like reflection call? If I did (.notchild AWrapperNode) will the be a warning?

fricze 2024-08-06T10:38:05.257139Z

I’m using spacemacs and it works without additional config. I only added clj-kondo config to treat defparent and deftype+ as clojure.core deftype. If you call this with unknown field it will throw immediately on macroexpansion. Function that replaces parent field access with simple field names is checking whether the field actually exist and whether it’s configured to be a parent field

fricze 2024-08-06T10:38:38.151229Z

It’s not perfect, because it looks like field access on a class, agreed

fricze 2024-08-06T10:41:15.211939Z

But there’s no static check whether the field exists. You have to call deftype+ for macroexpansion to happen

Niki 2024-08-06T10:42:35.307199Z

Wait the editor is doing macroexpansion?

Niki 2024-08-06T10:43:45.868309Z

If yes, then I don’t understand why it doesn’t see fields, if it expanded the macro anyway

fricze 2024-08-06T10:47:04.567119Z

No, at this point editor is just seeing field access and dont care whether the field exists. So from editor point of view you can put any name of the field, sadly. The check happens on macroexpansion, so if you put field that doesn’t exist it won’t compile

fricze 2024-08-06T10:48:03.928599Z

So I didn’t work out yet how to make editor check it before compilation, but at least it checks on compile time

fricze 2024-08-06T10:48:51.211099Z

But with clj-kondo hooks maybe it’s possible to actually make editor check it, I think there’s macroexpansion available there

fricze 2024-08-06T10:50:36.626319Z

I’ll try to move this check to clj-kondo level

Niki 2024-08-06T11:14:41.412899Z

Okay but error on macroexpansion you’ll get even in my version. What are the upsides here?

fricze 2024-08-06T11:52:47.883699Z

Upside is that editor doesn’t complain about reference to non-existing field

Niki 2024-08-06T14:16:25.929349Z

Ah, because child is checked but (.child this) isn’t. Got it

👍 1
Niki 2024-08-06T14:16:58.015289Z

What if ANode/ATerminalNode/... will be implemented in Java?

fricze 2024-08-06T16:51:01.427559Z

Good question, I can try that tomorrow. Also probably depends what constructs would those be in Java? Interfaces, classes, abstract classes?

Niki 2024-08-06T16:52:10.026899Z

I guess all of them. Starting from protocols that will have to become interfaces. I don’t want to depend from Java to Clojure

👍 1
JAtkins 2024-08-06T21:20:49.496169Z

Implementing them in java may be a PITA for clj-kondo people ... I'm not entirely sure. Speaking of kondo, I saw some basic config for it in the humble repo, but using that config most code is showing errors still - is that an ongoing effort or could I have been using it wrong?

fricze 2024-08-07T06:51:57.820639Z

some errors are surely not covered with this config, like referencing parent fields in deftype+ . other than that I needed to copy this config over to my local config to make it work properly

Niki 2024-08-07T08:29:05.859469Z

re: clj-kondo, I did it once in the past but didn’t pay attention since

Niki 2024-08-07T08:30:13.284819Z

clj-kondo should be already working with java, no? Hard to imagine it not knowing about it, I bet almost any Clojure codebase has some level of jvm interop somewhere

Niki 2024-08-07T08:43:39.261889Z

That would actually look like (.-field this) btw so kondo should be happy

Niki 2024-08-12T08:32:12.209129Z

reify, no?

fricze 2024-08-12T08:52:20.542739Z

reify no, maybe proxy

fricze 2024-08-12T08:52:36.781489Z

I’ll see how proxy works with mutable fields from abstract classes

Niki 2024-08-12T09:02:18.321459Z

Oh yeah right proxy

fricze 2024-08-12T09:28:23.627319Z

ok, proxy works well with abstract class and you can make public fields and access them with (.-size this)

fricze 2024-08-12T09:31:40.646459Z

what about inheritance then? if you base it on Java abstract classes I think you lose possibility to give Clojure consumer defparent. you can implement new class from abstract class, you cannot create new abstract class though

fricze 2024-08-12T09:32:14.994539Z

but yeah, proxy works ok 👍 but fields need to be public, or protected with setters/getters, because there’s no access to protected fields via proxy

Niki 2024-08-12T11:05:17.140039Z

As I imagine it, there’s not much need to implement new abstract classes

Niki 2024-08-12T11:05:37.201139Z

All this time I had four and it seems to be good enough so far

Niki 2024-08-12T11:05:45.389369Z

If needed, can always go to java :)

fricze 2024-08-12T11:43:46.906979Z

true actually

fricze 2024-08-12T11:44:36.820409Z

if it’s not something that has to be done often, than you can write it in Java and if somebody wants to add one they can always recompile

Niki 2024-08-12T12:02:34.889789Z

It would actually feel pretty good to get rid of that hacky defparent thing and switch to well-understood java

fricze 2024-08-12T12:13:12.811119Z

Sounds easy enough. I can rewrite it someday this week

fricze 2024-08-12T12:13:47.489919Z

With abst classes and Clojure proxy

Niki 2024-08-12T12:15:36.167339Z

Can you check if proxy works well with code reloading in REPL? If yes then go ahead

👍 1
fricze 2024-08-12T12:27:56.515109Z

Sure. but we’re speaking about reloading only Clojure code, right? because I don’t think reloading Java sources will be easy

Niki 2024-08-12T14:23:07.004599Z

Yes