Skip to content

Access to constructor params from within ADJUST and field-initialiser blocks #77

@leonerd

Description

@leonerd

Current state in Object::Pad

First, a little background on the current state of Object::Pad. There are ADJUST blocks, and there are field-initialiser blocks. They behave in very different ways.

The ADJUST keyword creates an entire (anonymous) function which behaves as such when invoked. It is passed a hashref to the constructor params as its first argument. It can access that with $_[0] or shift or optionally by providing a signatured parameter name:

ADJUST { say "The foo param was $_[0]->{foo}"; }
ADJUST { say "The foo param was ", shift->{foo}; }
ADJUST ($params) { say "The foo param was $params->{foo}"; }

(Of course, these examples are stupid in that they're doing things that :param would be far better suited to, but I didn't want to cloud up the examples with more complex real-world examples.)

By comparison, field-initialiser blocks are simple blocks, being parsed by no small amount of parser trickery into believing they're all just sequential blocks of the same function. That function is a single (anonymous) function stored as part of the class, used to initialise all the fields. These initialiser blocks do not currently have any access to the constructor parameters; which is somewhat limiting.

Both ADJUST and field-initialiser blocks get access to a $self lexical, the same as other methods.

Current state in feature-class branch

The current feature-class branch provides ADJUST blocks that allow either of the first two forms, but not the third form (with a signature parameter). The branch does not currently provide field-initialiser blocks at all.

The Question

And now we get on to my question. I would like to unify these two things together and at the same time make them better. In particular, I would like both ADJUST and field-initialiser to be simple blocks within one (implied) function rather than having one entersub overhead for every block. Additionally, I would like all of them to have the same access to the constructor parameters.

In particular, it would be great if we could say that a field-initialiser block is really just the same as an ADJUST block that assigns the field from its result, give or take some code that inspects the parameters hash to see if the caller already provided a value.

field $f { EXPR }   <==>   field $f;
                           ADJUST { $f = exists $params{f} ? $params{f} : do { EXPR }; }

The trouble though is how to provide this params hash(ref). It can't just come as the first value in @_ because any block would too easily be able to break access to it for all the later ones by just doing shift, or something equally dumb. It needs to be provided by something guaranteed to be visible to all the blocks, that no earlier block can get in the way of. So far I can only think of two ideas, neither of them are great:

Special %params lexical

In the same way that $self is special, make %params special in these blocks:

  field $x { delete $params{x} }

  field $y; ADJUST { $y = delete $params{y} }

It's a simple idea and easily understandable, but it does mean that no class is permitted a field called %params itself, because then there'd be no way for an ADJUST block to see it. Of course we're already in that situation at the moment with $self, so it doesn't make the problem much worse. But perhaps enough classes might themselves want a field %params, that this would become awkward.

Use the %_ superglobal

By considering analogy to the @_ superglobal already in perl, we can consider using the %_ hash as a storage of these name/value pairs:

  field $x { delete $_{x} }

  field $y; ADJUST { $y = delete $_{y} }

I think there's a certain neatness to this, and a certain symmetry with using $_[0], $_[1], etc... However, some folks might object to it on grounds that "newbies to perl might be confused by lots of symbol syntax". Personally I'm not very swayed by this particular argument, but it seems important to some. Another potential argument against doing this is that it seems weird to introduce a new use for %_ while at the same time trying to get rid of @_ in favour of function signatures.

Other Designs

Another way to look at it entirely, is to observe that the main reason for wanting the hash(ref) of params available in these blocks in the first place, was to do things with constructor parameters that aren't just simply "copy the value into a field". This was originally discussed for Object::Pad in RT137209. I say "discussed" - I thought out loud on a few ideas but nobody else has commented so far.

In many ways the current solution of just passing a hash(ref) around isn't very nice. It means that the actual parameter names used by the construction process overall are not manifestly expressed anywhere, and only become apparent in side-effects of the actual process of constructing an object. You can only find out those names by running it and seeing if it complains about missing or extra ones. It would be nice to find a nicer overall design for this sort of pattern, but so far one has not emerged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions