public inbox for kawa@sourceware.org
 help / color / mirror / Atom feed
* Having Named Parts
@ 2022-12-01 11:19 Panicz Maciej Godek
  2022-12-01 22:16 ` Per Bothner
  0 siblings, 1 reply; 3+ messages in thread
From: Panicz Maciej Godek @ 2022-12-01 11:19 UTC (permalink / raw)
  To: kawa

[-- Attachment #1: Type: text/plain, Size: 3706 bytes --]

Hi,
I've been trying to build a simple wrapper for a hash table,
that I call Bundle, such that

(Bundle x: 5 y: 10)

would return a bundle object, let's call it b, so that I could refer to its
fields using

b:x

and modify them using

(set! b:x new-value)

And such that it would print itself as

(Bundle x: 5 y: 10)

and be equal to every other object with the same keys and values.

I have found that Kawa offers a gnu.mapping.HasNamedParts interface which
facilitates value reference, so that if I define the following class, it
lets me refer to the contents of a hash map:

(define-simple-class Bundle (gnu.mapping.HasNamedParts)
  (table ::java.util.Map (java.util.HashMap))
  ((get key::String)
   (table:get key))
  ((isConstant key::String)::boolean #f)
  ((*init*) ;for test purposes
   (table:put ("x":toString) 5)))

(define b ::Bundle (Bundle))

#|kawa|# b:x
5

However, when I invoke
(set! b:x 5)
Kawa responds with the following error:

java.lang.RuntimeException: no such field x in Bundle
        at gnu.kawa.reflect.SlotSet.apply(SlotSet.java:115)
        at gnu.kawa.reflect.SlotSet.setField(SlotSet.java:25)
        at gnu.kawa.functions.SetNamedPart.apply3(SetNamedPart.java:52)
        at gnu.mapping.Procedure3.applyToObject(Procedure3.java:61)
        at gnu.mapping.Procedure.applyToConsumerDefault(Procedure.java:75)
        at gnu.mapping.CallContext.runUntilDone(CallContext.java:586)
        at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:343)
        at kawa.Shell.run(Shell.java:300)
        at kawa.Shell.run(Shell.java:183)
        at kawa.repl.processArgs(repl.java:724)
        at kawa.repl.main(repl.java:830)

I also saw that there is the gnu.mapping.HasSetter interface, but the
getSetter method seems not to be invoked in that context.

The source code for the gnu.kawa.functions.SetNamedPart.apply3 method
reveals, that there was a plan for supporting this -- the method begins
with a commented-out fragment:

    /*
    if (container implements HasNamedParts)
      return ((HasNamedParts) container).getNamedPart(part);
    */

which wouldn't work with current Kawa, because the HasNamedParts interface
doesn't containt getNamedPart method, and instead it contains a method
called get. I think that this could be fixed by:
- adding a method Object set(String key, Object value) to the HasNamedParts
interface
- uncommenting and fixing the above snippet to invoke the above method.

By the way, the interface also contains isConstant method, which doesn't
seem to be documented anywhere. I think that also - for consistency - the
interface should also provide a boolean hasPartNamed(String name) method.

But this only solves one problem, and there's another one, namely - when I
try to initialize Bundle as, say

(Bundle x: 5 y: 10)

I get the following warnings:

/dev/tty:6:20: warning - no field or setter 'x' in class Bundle
/dev/tty:6:20: warning - no field or setter 'y' in class Bundle

These are issued from the build() method in
gnu.kawa.reflect.CompileBuildObject. It does seem that this class doesn't
take into account the existence of the HasNamedParts interface at all (but
I don't understand it well enough to come up with any idea of fixing it).
And the *init* method doesn't seem to accept variable-length arguments.

On the other hand, if I wanted to provide a lot of *init* methods like

   ((*init* k1::gnu.expr.Keyword v1)
   (table:put (keyword->string k1) v1))

  ((*init* k1::gnu.expr.Keyword v1 k2::gnu.expr.Keyword v2)
   (table:put (keyword->string k1) v1)
   (table:put (keyword->string k2) v2))

then I'd need to quote the keywords in order to make it work (which isn't
something that I want).

Is there any way out of this situation?

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Having Named Parts
  2022-12-01 11:19 Having Named Parts Panicz Maciej Godek
@ 2022-12-01 22:16 ` Per Bothner
  2022-12-01 23:15   ` Panicz Maciej Godek
  0 siblings, 1 reply; 3+ messages in thread
From: Per Bothner @ 2022-12-01 22:16 UTC (permalink / raw)
  To: Panicz Maciej Godek, kawa



On 12/1/22 03:19, Panicz Maciej Godek via Kawa wrote:
> I've been trying to build a simple wrapper for a hash table,
> that I call Bundle, such that
> 
> (Bundle x: 5 y: 10)
> 
> would return a bundle object, let's call it b, so that I could refer to its
> fields using
> 
> b:x

That would be a nicer syntax, but there could be some problems combining
the concepts of hash-table (extensible with arbitrary keys, not necessarily strings)
and structures (with a fixed set of keys).  JavaScript does it, and it does make
things more convenient.

There are a lot of issues to consider. Should key names be strings or symbols?
What about keys that are not names (keys or strings)?  How does this conflict
with existing usage of colon-notation, and how do we deal with those?

So I'm not sure if hash tables should implement HasNamedParts by default.

An alternative is to use function-call notation, like we do for vectors:
   (TABLE KEY)
   (set! (TABLE KEY) VALUE)
This has the advantage that it does't conflict with other uses of colon-notation.
However, it doesn't provide a constructor syntax "for free".

I brought up these concerns on the mailing list years ago.  If someone can
find these old message it might be helpful ...

> I have found that Kawa offers a gnu.mapping.HasNamedParts interface which
> facilitates value reference, ...s
> The source code for the gnu.kawa.functions.SetNamedPart.apply3 method
> reveals, that there was a plan for supporting this -- the method begins
> with a commented-out fragment:
> 
>      /*
>      if (container implements HasNamedParts)
>        return ((HasNamedParts) container).getNamedPart(part);
>      */
> 
> which wouldn't work with current Kawa, because the HasNamedParts interface
> doesn't containt getNamedPart method, and instead it contains a method
> called get. I think that this could be fixed by:
> - adding a method Object set(String key, Object value) to the HasNamedParts
> interface
> - uncommenting and fixing the above snippet to invoke the above method.

That is probably OK, except use 'put' instead of 'set' to be consistent with java.util.Map.
Also, 'put' needs a default implementation, which should probably
throw UnsupportedOperationException.  (Defaults were added in Java 8. It is probably
OK to require Java 8 as a minimum at this point - though Android might be an issue.)

Do compare with the getNamedPart and apply 2 method in GetNamedPart.
They handles various extra cases, that might not be appropriate for a setter.

> By the way, the interface also contains isConstant method, which doesn't
> seem to be documented anywhere.

isConstant(KEY) returns true is the binding for KEY is immutable.
It is used to optimize namespace resolution.

  I think that also - for consistency - the
> interface should also provide a boolean hasPartNamed(String name) method.
> 
> But this only solves one problem, and there's another one, namely - when I
> try to initialize Bundle as, say
> 
> (Bundle x: 5 y: 10)
> 
> I get the following warnings:
> 
> /dev/tty:6:20: warning - no field or setter 'x' in class Bundle
> /dev/tty:6:20: warning - no field or setter 'y' in class Bundle
> 
> These are issued from the build() method in
> gnu.kawa.reflect.CompileBuildObject. It does seem that this class doesn't
> take into account the existence of the HasNamedParts interface at all.

That is probably the case.

> Is there any way out of this situation?

If there was a clean problem-free solution I would have already done so ...
That doesn't mean we shouldn't have a better syntax for hash-tables,
or that this is a dead end - but there are definitely a number of issues to consider.
-- 
	--Per Bothner
per@bothner.com   http://per.bothner.com/

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: Having Named Parts
  2022-12-01 22:16 ` Per Bothner
@ 2022-12-01 23:15   ` Panicz Maciej Godek
  0 siblings, 0 replies; 3+ messages in thread
From: Panicz Maciej Godek @ 2022-12-01 23:15 UTC (permalink / raw)
  To: Per Bothner; +Cc: kawa

[-- Attachment #1: Type: text/plain, Size: 7193 bytes --]

czw., 1 gru 2022 o 23:16 Per Bothner <per@bothner.com> napisał(a):

>
>
> On 12/1/22 03:19, Panicz Maciej Godek via Kawa wrote:
> > I've been trying to build a simple wrapper for a hash table,
> > that I call Bundle, such that
> >
> > (Bundle x: 5 y: 10)
> >
> > would return a bundle object, let's call it b, so that I could refer to
> its
> > fields using
> >
> > b:x
>
> That would be a nicer syntax, but there could be some problems combining
> the concepts of hash-table (extensible with arbitrary keys, not
> necessarily strings)
> and structures (with a fixed set of keys).  JavaScript does it, and it
> does make
> things more convenient.
>
> There are a lot of issues to consider. Should key names be strings or
> symbols?
> What about keys that are not names (keys or strings)?


I think they have to be Strings, because of how the HasNamedPart interface
is defined.


>   How does this conflict
> with existing usage of colon-notation, and how do we deal with those?
>
> So I'm not sure if hash tables should implement HasNamedParts by default.
>
> No, I don't think so either.
But I think it would be nice if Kawa facilitated adding such classes.

For the development of GRASP, I have built a macro called define-type
<https://github.com/panicz/grasp-android/blob/master/stages/retreat/GRASP/src/define-type.scm>,
which is used for defining record-like classes.
It is used like this:

(define-type (Extent width: real height: real))

And it defines a class that can be instantiated as

(define e ::Extent (Extent width: 10 height: 20))

I also have a pattern matcher which supports this notation, so I can write
things like

(match e
  ((Extent width: size height: size)
   'square)
  ((Position width: w height: h)
   'rectangle))

I like it way more than any SRFI record proposals I've seen.

Also, with Kawa's syntax extensions I can write

(set! e:width 50)

and it Just Works. I find this experience very satisfying (to be honest,
I'm quite surprised that many modern languages like Java or Haskell have
much worse capabilities of defining structures than the C language).

And I thought it would be nice to have a more flexible record-like type
that I call Bundle (referring to David Hume's "bundle theory of meaning")
-- so that it would also work in the context of pattern matching etc.

I don't expect it to be the main interface to hash tables.

An alternative is to use function-call notation, like we do for vectors:
>    (TABLE KEY)
>    (set! (TABLE KEY) VALUE)
> This has the advantage that it does't conflict with other uses of
> colon-notation.
> However, it doesn't provide a constructor syntax "for free".
>
>
Yes, I actually use that a lot in GRASP.
I have defined a bunch of macros such as define-mapping
<https://github.com/panicz/grasp-android/blob/master/stages/retreat/GRASP/src/mapping.scm>
(which uses a normal hash table underneath) or define-property
<https://github.com/panicz/grasp-android/blob/master/stages/retreat/GRASP/src/define-property.scm>
(which uses a weak-key table) that is used like this:

(define-property (dotted? cell::cons)::boolean #f)

The nice thing about this is that it lets me provide the default value very
naturally (and I could also signal error for missing keys).
I also have a variant of weak-key which can accept more than one argument,
that is called define-cache
<https://github.com/panicz/grasp-android/blob/master/stages/retreat/GRASP/src/define-cache.scm>
(and
it's achieved by means of "currying", i.e. constructing hash tables that
return hash tables etc.)

But the difference between those hash tables and bundles is that bundles
are meant to be externalizable, whereas properties are essentially opaque.

I brought up these concerns on the mailing list years ago.  If someone can
> find these old message it might be helpful ...
>
> > I have found that Kawa offers a gnu.mapping.HasNamedParts interface which
> > facilitates value reference, ...s
> > The source code for the gnu.kawa.functions.SetNamedPart.apply3 method
> > reveals, that there was a plan for supporting this -- the method begins
> > with a commented-out fragment:
> >
> >      /*
> >      if (container implements HasNamedParts)
> >        return ((HasNamedParts) container).getNamedPart(part);
> >      */
> >
> > which wouldn't work with current Kawa, because the HasNamedParts
> interface
> > doesn't containt getNamedPart method, and instead it contains a method
> > called get. I think that this could be fixed by:
> > - adding a method Object set(String key, Object value) to the
> HasNamedParts
> > interface
> > - uncommenting and fixing the above snippet to invoke the above method.
>
> That is probably OK, except use 'put' instead of 'set' to be consistent
> with java.util.Map.
> Also, 'put' needs a default implementation, which should probably
> throw UnsupportedOperationException.  (Defaults were added in Java 8. It
> is probably
> OK to require Java 8 as a minimum at this point - though Android might be
> an issue.)
>
>
Actually Android SDK ships with a tool called d8, which is a replacement
for their dx converter,
which handles the Java 8 bytecode (I was even able to generate byte code
for API level 1
with it)


> Do compare with the getNamedPart and apply 2 method in GetNamedPart.
> They handles various extra cases, that might not be appropriate for a
> setter.
>
> > By the way, the interface also contains isConstant method, which doesn't
> > seem to be documented anywhere.
>
> isConstant(KEY) returns true is the binding for KEY is immutable.
> It is used to optimize namespace resolution.
>
>   I think that also - for consistency - the
> > interface should also provide a boolean hasPartNamed(String name) method.
> >
> > But this only solves one problem, and there's another one, namely - when
> I
> > try to initialize Bundle as, say
> >
> > (Bundle x: 5 y: 10)
> >
> > I get the following warnings:
> >
> > /dev/tty:6:20: warning - no field or setter 'x' in class Bundle
> > /dev/tty:6:20: warning - no field or setter 'y' in class Bundle
> >
> > These are issued from the build() method in
> > gnu.kawa.reflect.CompileBuildObject. It does seem that this class doesn't
> > take into account the existence of the HasNamedParts interface at all.
>
> That is probably the case.
>
> > Is there any way out of this situation?
>
> If there was a clean problem-free solution I would have already done so ...
> That doesn't mean we shouldn't have a better syntax for hash-tables,
> or that this is a dead end - but there are definitely a number of issues
> to consider.
>
>
 Again, I don't want to push for having an opinionated way of dealing with
hash tables.
I think it would be sufficient if Kawa could provide their users (or me in
particular :D)
with the means of creating various different solutions and experimenting
with them.

I think that supporting setters for HasNamedPart and in CompileBuildObject
would simply be consistent with the idea of the HasNamedPart interface,
and it doesn't seem to break anything

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2022-12-01 23:15 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-01 11:19 Having Named Parts Panicz Maciej Godek
2022-12-01 22:16 ` Per Bothner
2022-12-01 23:15   ` Panicz Maciej Godek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).