Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

smartmatch signatures #16260

Open
p5pRT opened this issue Nov 20, 2017 · 7 comments
Open

smartmatch signatures #16260

p5pRT opened this issue Nov 20, 2017 · 7 comments

Comments

@p5pRT
Copy link

p5pRT commented Nov 20, 2017

Migrated from rt.perl.org#132473 (status was 'open')

Searchable as RT132473$

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

From zefram@fysh.org

Created by zefram@fysh.org

Everyone would like to have type constraints in signatures, but a
prohibitive issue is that the core doesn't have a general type system.
(There are classes, but they don't address any of the type distinctions we
frequently need to make between unblessed data types.) Smartmatch could
become the core's type system, as we've discussed a bit when talking
about the future of smartmatch.

Suppose that the smartmatch/switch discussions lead to a version of
smartmatch that we can all live with. (In the last round of discussion we
seemed to be agreed on smartmatch semantics, and it's just the given/when
stuff that's holding us back.) This would be almost entirely based on
explicit overloading of the smartmatch operator by blessed objects that
appear on the rhs of smartmatch. We would expect to see a rich ecosystem
of matcher object classes that implement all kinds of predicate, including
composite predicates that incorporate subordinate matcher objects.

Matcher objects can then be used as type metaobjects. In general
they're types in only the minimal sense that they have a concept of type
membership. Specific kinds of matcher object may also have other kinds
of type-related behaviour; for example, Moose class metaobjects would
presumably overload smartmatch to implement a class membership check,
while also having introspective methods that only make sense for the Moose
concept of a class. But we don't need to worry about that; for signatures
the only aspect of a type that we care about is the membership predicate.

So a scalar signature item could specify a type constraint by referring
to an appropriate matcher object. We'd want to permit an arbitrary
expression for the matcher object, to allow for parameterised and
composite types. So very little signature syntax is required; really
just a signifier for smartmatching and a grammatical slot into which to
put the matcher expression. Using signature syntax otherwise as it is
today, this would look something like​:

  use Smart_Type qw(Num Str union HashRef_of); # imaginary module
  use feature "signatures";
  sub gronk ($why ~Str, $how_hard ~Num = 5) {...}
  sub annotate ($subject, $msg ~ union(Str, HashRef_of(Str))) {...}
  sub nom ($food ~ My​::Food->meta = My​::Apple->new) {...}

"~" references the smartmatch operator while not being longer than
necessary. The matcher expression would have to be restricted to
expressions above some middling precedence level, so that in the case of
an optional parameter it doesn't read the "=" and default value expression
as part of the matcher expression. But I imagine matcher objects would
also overload the bitwise operators to perform Boolean compositions,
so it would be nice for the threshold to be lower than those operators.
Any expression of lower precedence than the threshold could of course
be parenthesised. So we'd have things like​:

  use Smart_Type qw(Str HashRef_of);
  use feature "signatures";
  sub annotate ($subject, $msg ~ Str | HashRef_of(Str)) {...}
  sub frob ($spec ~ (state $fspec_type = gen_fspec_type())) {...}

With respect to the last of those examples, usually the type we want to
smartmatch against is fixed for all time, and recomputing the matcher
for each execution of the sub would be unwanted expense, potentially
significant. So making the matcher be implicitly saved in an invisible
state variable might be a better way to go. However, it's impossible to
get fully dynamic type constraint logic (referring to earlier parameters)
if that's always done, so consideration should then be given to having
a variant of "~" that explicitly makes the matcher expression dynamic.

In optional parameters I imagine the default value expression would be
exempt from the type constraint, but this could go either way.

It's a little more difficult to apply type constraints to array and
hash parameters. We probably want to apply the type constraint to the
whole array or hash, for maximum flexibility, but the actual smartmatch
operand would have to be *a reference to* the array or hash, so how do
we indicate that in the signature syntax?

It's also necessary to think about how type constraints would interact
with aliasing in signatures [perl #132472].

Perl Info

Flags:
    category=core
    severity=wishlist

Site configuration information for perl 5.27.5:

Configured by zefram at Fri Oct 20 23:24:00 BST 2017.

Summary of my perl5 (revision 5 version 27 subversion 5) configuration:
   
  Platform:
    osname=linux
    osvers=3.16.0-4-amd64
    archname=x86_64-linux-thread-multi
    uname='linux barba.rous.org 3.16.0-4-amd64 #1 smp debian 3.16.43-2+deb8u2 (2017-06-26) x86_64 gnulinux '
    config_args='-des -Dprefix=/home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52 -Duselargefiles -Dusethreads -Uafs -Ud_csh -Uusesfio -Uusenm -Duseshrplib -Dusedevel -Uversiononly -Ui_db'
    hint=recommended
    useposix=true
    d_sigaction=define
    useithreads=define
    usemultiplicity=define
    use64bitint=define
    use64bitall=define
    uselongdouble=undef
    usemymalloc=n
    default_inc_excludes_dot=define
    bincompat5005=undef
  Compiler:
    cc='cc'
    ccflags ='-D_REENTRANT -D_GNU_SOURCE -fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2'
    optimize='-O2'
    cppflags='-D_REENTRANT -D_GNU_SOURCE -fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
    ccversion=''
    gccversion='4.9.2'
    gccosandvers=''
    intsize=4
    longsize=8
    ptrsize=8
    doublesize=8
    byteorder=12345678
    doublekind=3
    d_longlong=define
    longlongsize=8
    d_longdbl=define
    longdblsize=16
    longdblkind=3
    ivtype='long'
    ivsize=8
    nvtype='double'
    nvsize=8
    Off_t='off_t'
    lseeksize=8
    alignbytes=8
    prototype=define
  Linker and Libraries:
    ld='cc'
    ldflags =' -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib /usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed /usr/include/x86_64-linux-gnu /usr/lib /lib/x86_64-linux-gnu /lib/../lib /usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib
    libs=-lpthread -lnsl -ldb -ldl -lm -lcrypt -lutil -lc
    perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
    libc=libc-2.19.so
    so=so
    useshrplib=true
    libperl=libperl.so
    gnulibc_version='2.19'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs
    dlext=so
    d_dlsymun=undef
    ccdlflags='-Wl,-E -Wl,-rpath,/home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/lib/5.27.5/x86_64-linux-thread-multi/CORE'
    cccdlflags='-fPIC'
    lddlflags='-shared -O2 -L/usr/local/lib -fstack-protector-strong'



@INC for perl 5.27.5:
    /home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/lib/site_perl/5.27.5/x86_64-linux-thread-multi
    /home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/lib/site_perl/5.27.5
    /home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/lib/5.27.5/x86_64-linux-thread-multi
    /home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/lib/5.27.5


Environment for perl 5.27.5:
    HOME=/home/zefram
    LANG (unset)
    LANGUAGE (unset)
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/zefram/usr/perl/perl_install/perl-5.27.5-i64-f52/bin:/home/zefram/usr/perl/util:/home/zefram/pub/x86_64-unknown-linux-gnu/bin:/home/zefram/pub/common/bin:/usr/bin:/bin:/usr/local/bin:/usr/games
    PERL_BADLANG (unset)
    SHELL=/usr/bin/zsh

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

From @cpansprout

On Mon, 20 Nov 2017 06​:59​:19 -0800, zefram@​fysh.org wrote​:

Everyone would like to have type constraints in signatures, but a
prohibitive issue is that the core doesn't have a general type system.
(There are classes, but they don't address any of the type
distinctions we
frequently need to make between unblessed data types.) Smartmatch
could
become the core's type system, as we've discussed a bit when talking
about the future of smartmatch.

Suppose that the smartmatch/switch discussions lead to a version of
smartmatch that we can all live with. (In the last round of
discussion we
seemed to be agreed on smartmatch semantics, and it's just the
given/when
stuff that's holding us back.) This would be almost entirely based on
explicit overloading of the smartmatch operator by blessed objects
that
appear on the rhs of smartmatch. We would expect to see a rich
ecosystem
of matcher object classes that implement all kinds of predicate,
including
composite predicates that incorporate subordinate matcher objects.

Matcher objects can then be used as type metaobjects. In general
they're types in only the minimal sense that they have a concept of
type
membership. Specific kinds of matcher object may also have other
kinds
of type-related behaviour; for example, Moose class metaobjects would
presumably overload smartmatch to implement a class membership check,
while also having introspective methods that only make sense for the
Moose
concept of a class. But we don't need to worry about that; for
signatures
the only aspect of a type that we care about is the membership
predicate.

So a scalar signature item could specify a type constraint by
referring
to an appropriate matcher object. We'd want to permit an arbitrary
expression for the matcher object, to allow for parameterised and
composite types. So very little signature syntax is required; really
just a signifier for smartmatching and a grammatical slot into which
to
put the matcher expression. Using signature syntax otherwise as it is
today, this would look something like​:

use Smart_Type qw(Num Str union HashRef_of); # imaginary module

Those would be subs (maybe constants), right?

use feature "signatures";
sub gronk ($why ~Str, $how_hard ~Num = 5) {...}
sub annotate ($subject, $msg ~ union(Str, HashRef_of(Str))) {...}
sub nom ($food ~ My​::Food->meta = My​::Apple->new) {...}

"~" references the smartmatch operator while not being longer than
necessary. The matcher expression would have to be restricted to
expressions above some middling precedence level, so that in the case
of
an optional parameter it doesn't read the "=" and default value
expression
as part of the matcher expression. But I imagine matcher objects
would
also overload the bitwise operators to perform Boolean compositions,
so it would be nice for the threshold to be lower than those
operators.
Any expression of lower precedence than the threshold could of course
be parenthesised.

?​: is also immensely useful. Make ?​: the lowest precedence permitted.

So we'd have things like​:

use Smart_Type qw(Str HashRef_of);
use feature "signatures";
sub annotate ($subject, $msg ~ Str | HashRef_of(Str)) {...}
sub frob ($spec ~ (state $fspec_type = gen_fspec_type())) {...}

With respect to the last of those examples, usually the type we want
to
smartmatch against is fixed for all time, and recomputing the matcher
for each execution of the sub would be unwanted expense, potentially
significant. So making the matcher be implicitly saved in an
invisible
state variable might be a better way to go. However, it's impossible
to
get fully dynamic type constraint logic (referring to earlier
parameters)
if that's always done, so consideration should then be given to having
a variant of "~" that explicitly makes the matcher expression dynamic.

Or the other way round.

So it’s a matter of arguing about the syntax. Finding something that conveys permanence without confusion with other operators is hard. My first thought was = to imply state variable assignment, but then you have ~=, which means something completely different elsewhere.

Since the syntax before the ~ is not restricted so much, maybe​:

  sub fronk ($foo state~predicate())

Unambiguous, but confusing!

I think the indicator, whatever it is, ought to be before the ~, since putting it after the ~ limits the syntax, unless we start requiring whitespace after the ~.

I will trying brainstorming with every symbol of my keyboard​:

`~ -- confusing
!~ -- confusing
@​~ -- confusing
#~ -- very confusing
$~ -- confusing
%~ -- confusing
^~ -- ‘pin’ the predicate to a state var
&~ -- confusing
*~ -- Perl 6’s ‘whatever’ star; use ‘whatever’ the predicate expression produces each time (dynamic)
(~ -- confusing
(same with {}[])
)~ -- ambiguous
'~ -- confusing
"~ -- confusing
<~ -- unambiguous, but couldn’t say whether that means dynamic or fixed

~ -- same
,~ -- ambiguous
.~ -- may ‘pin’ the predicate, as with ^=
?~ -- conflicts with other proposed syntax
/~ -- usable, like <~
=~ -- confusing
+~ -- usable
\~ -- conflicts with other proposed syntax
|~ -- Makes me think of bitwise ops
-~ -- usable
_~ -- usable, but requires whitespace​: previous predicate is ‘under’stood
;~ -- confusing; looks like a multidimensional syntax
:~ -- usable
⌘~ -- I did say I was using every symbol on the keyboard. :-)

So the only options that I do not find objectionable are​:

If we want fixed predicates to be the default​:

*~
<~

~
/~
+~
-~
:~

If we want dynamic predicates to be the default​:

^~
<~

~
.~
/~
+~
-~
_~
:~

If we go with that last one, we will have people deliberately parenthesizing their predicates, just to make funny faces​:

sub fronk (@​a :~( Str )) {...}
  ^^^

In optional parameters I imagine the default value expression would be
exempt from the type constraint, but this could go either way.

Skipping the constraint would be faster. That would assume the programmer knows what he is doing, which is not an unreasonable assumption.

It's a little more difficult to apply type constraints to array and
hash parameters. We probably want to apply the type constraint to the
whole array or hash, for maximum flexibility, but the actual
smartmatch
operand would have to be *a reference to* the array or hash, so how do
we indicate that in the signature syntax?

Don’t indicate it; just document it as the behaviour. People can write array-specific predicates.

  sub foo (@​aStr) # will probably fail; \@​a is not a Str
  sub foo (@​a
Array_of_Str)
  sub foo (@​a~HasEvenNumberOfElements)

It's also necessary to think about how type constraints would interact
with aliasing in signatures [perl #132472].

In terms of what the smartmatcher sees, I think the only reasonable behaviour is to do the aliasing first, and then apply the type constraint to the lexical variable created by the parameter.

So (\$x~Str) will effectively do (\$_[0] ~~ Str)
and ($x\~Str) will effectively do ( $_[0] ~~ Str)

In the former case, $_[0] would not necessarily be a ref, but in the latter case it would have to be.

There is still the question of what order the \ and ~ come​:

  ($x\Str) and ($x\Str=default)
  ($x
Str\) and ($x
Str\=default)

The former would be much easier to parse.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

From zefram@fysh.org

Father Chrysostomos via RT wrote​:

On Mon, 20 Nov 2017 06​:59​:19 -0800, zefram@​fysh.org wrote​:

use Smart_Type qw(Num Str union HashRef_of); # imaginary module

Those would be subs (maybe constants), right?

Yes. All are subs. Num and Str are constant-valued nullary subs,
presumably with () prototypes and the usual machinery to constant-fold.
The other two take parameters (union() is variadic and HashRef_of() is
unary), so can't be ordinary constants, but they're pure functions and
so could potentially constant-fold by means of a special call checker.
Apart from that optional call checker, and optional XS implementation for
performance, the Smart_Type module could be easily written in pure Perl,
and would export the subs in the ordinary way.

?​: is also immensely useful. Make ?​: the lowest precedence permitted.

That's the precedence level immediately above assignment, so that
just fits. We should add a parse_*expr() function for that level of
precedence, since there isn't currently one.

if that's always done, so consideration should then be given to having
a variant of "~" that explicitly makes the matcher expression dynamic.

Or the other way round.

Think of the Huffman coding​: a constant type is the usual case. However,
if matcher-generating functions routinely have call checkers for constant
folding, maybe we could arrange for most matcher-constructing expressions
to constant-fold. That could remove the pressure for implicit init-once
semantics, in which case we can just go with always-dynamic semantics.
In that case, the occasional non-constant one that we want to make
constant can be handled by explicit use of state variable, Memoize​::Lift,
et al.

_~ -- usable, but requires whitespace​: previous predicate is `under'stood

Not really usable, because it would interact with nameless parameters.
"($_~...)" looks like an attempt to name the parameter "$_". We don't
permit $_ as a lexical variable any more, but because of the syntactic
proximity and the history we shouldn't make "$_" mean something else
in signatures.

In optional parameters I imagine the default value expression would be
exempt from the type constraint, but this could go either way.

Skipping the constraint would be faster. That would assume the
programmer knows what he is doing, which is not an unreasonable
assumption.

I was thinking more about deliberate use of an out-of-constraint default.
In some cases that could be used as an unambiguous indicator that the
argument wasn't passed in. I have difficulty imagining a case where
an out-of-constraint default could actually be a useful value for the
parameter, requiring no explicit check later, but it probably could
happen. I think those are reasonable things to do, which we shouldn't
lightly prohibit. On the other hand, mistaken out-of-constraint defaults
probably wouldn't happen much​: most default value expressions are simple,
and it's easy for the programmer to make sure they're of the right type.

In terms of what the smartmatcher sees, I think the only reasonable
behaviour is to do the aliasing first, and then apply the type constraint
to the lexical variable created by the parameter.

Is it? With \$x type aliasing, it would make just as much sense to
apply the type constraint to \$x, or equivalently to the actual argument.

With \@​x aliasing, we can't apply the smartmatch to @​x itself, only
to \@​x. This argues in favour of smartmatching on \$x for \$x.

So (\$x~Str) will effectively do (\$_[0] ~~ Str)

That doesn't make sense. Perhaps you meant "(${$_[0]} ~~ Str)".

In the former case, $_[0] would not necessarily be a ref, but in the
latter case it would have to be.

Those are the other way round, according to the aliasing rules that
I proposed.

There is still the question of what order the \ and ~ come​:

Yeah. "($x~Str\)" seems more logical to me, but it does raise a
tokenisation issue. It's less of a problem if we actually implement the
"\=" operator as Ilmari suggested, but that still leaves us with bare
"\" having to be treated as an infix operator of assignment precedence
which it normally isn't. The aliasing syntax seems more malleable here,
so might be the better side from which to approach it. I don't have
a firm opinion about this, other than that I wouldn't accept a fudge.
This is all new syntax, we can get it right.

-zefram

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

From @cpansprout

On Mon, 20 Nov 2017 10​:03​:05 -0800, sprout wrote​:

I will trying brainstorming with every symbol of my keyboard​:

Sigh. If I don’t proofreading my own writing, I end up sounding like Google Translate.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Nov 20, 2017

From @cpansprout

On Mon, 20 Nov 2017 10​:46​:50 -0800, zefram@​fysh.org wrote​:

Father Chrysostomos via RT wrote​:

?​: is also immensely useful. Make ?​: the lowest precedence permitted.

That's the precedence level immediately above assignment, so that
just fits. We should add a parse_*expr() function for that level of
precedence, since there isn't currently one.

Perhaps parse_condexpr().

if that's always done, so consideration should then be given to having
a variant of "~" that explicitly makes the matcher expression dynamic.

Or the other way round.

Think of the Huffman coding​: a constant type is the usual case. However,
if matcher-generating functions routinely have call checkers for constant
folding, maybe we could arrange for most matcher-constructing expressions
to constant-fold. That could remove the pressure for implicit init-once
semantics, in which case we can just go with always-dynamic semantics.
In that case, the occasional non-constant one that we want to make
constant can be handled by explicit use of state variable, Memoize​::Lift,
et al.

I think I prefer this idea.

In terms of what the smartmatcher sees, I think the only reasonable
behaviour is to do the aliasing first, and then apply the type constraint
to the lexical variable created by the parameter.

Is it? With \$x type aliasing, it would make just as much sense to
apply the type constraint to \$x, or equivalently to the actual argument.

With \@​x aliasing, we can't apply the smartmatch to @​x itself, only
to \@​x. This argues in favour of smartmatching on \$x for \$x.

But you already proposed earlier than (@​xfoo) would implicitly enreference \@​x whereas ($xfoo) will not.

If we want *some* consistency, then array parameters should always get one level of referencing more than scalar parameters.

So (\$x~Str) will effectively do (\$_[0] ~~ Str)

That doesn't make sense. Perhaps you meant "(${$_[0]} ~~ Str)".

In the former case, $_[0] would not necessarily be a ref, but in the
latter case it would have to be.

Those are the other way round, according to the aliasing rules that
I proposed.

Yeah, I wrote it really quickly.

There is still the question of what order the \ and ~ come​:

Yeah. "($x~Str\)" seems more logical to me, but it does raise a
tokenisation issue. It's less of a problem if we actually implement the
"\=" operator as Ilmari suggested, but that still leaves us with bare
"\" having to be treated as an infix operator of assignment precedence
which it normally isn't. The aliasing syntax seems more malleable here,
so might be the better side from which to approach it. I don't have
a firm opinion about this, other than that I wouldn't accept a fudge.
This is all new syntax, we can get it right.

There is a fundamental problem with the way the parsing functions work. Namely, the token that follows the code being parsed must be recognizable by the lexer as being of a particular precedence. This severely limits the usefulness of recursive-descent parsing functions.

In an ideal world, parse_fooexpr() would parse as much as possible until it reaches a token that does fit in the ‘foo’ rule in the grammar. (Sorry, I can’t remember offhand the term for that.)

I am by no means an experienced yacc user, so I don’t know how you go about this. But I think it would entail changing the way errors are handled in perly.y. Currently sideff is defined in terms of error | ....

With strict recursive-descent parsing (not involving any yaks), for each precedence level you have a function that does​:

  if (!parse_lower_precedence())
  position = old_position;

and it’s pretty straightforward. I don’t know whether perl could switch to something like that, though, because of do{BEGIN...} etc.

Fixing this, however, would open up the parsing possibilities without adding more special cases to the lexer for the tokens we need to end on.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Nov 24, 2017

From @Leont

On Mon, Nov 20, 2017 at 3​:59 PM, Zefram <perlbug-followup@​perl.org> wrote​:

Everyone would like to have type constraints in signatures, but a
prohibitive issue is that the core doesn't have a general type system.
(There are classes, but they don't address any of the type distinctions we
frequently need to make between unblessed data types.) Smartmatch could
become the core's type system, as we've discussed a bit when talking
about the future of smartmatch.

Suppose that the smartmatch/switch discussions lead to a version of
smartmatch that we can all live with. (In the last round of discussion we
seemed to be agreed on smartmatch semantics, and it's just the given/when
stuff that's holding us back.) This would be almost entirely based on
explicit overloading of the smartmatch operator by blessed objects that
appear on the rhs of smartmatch. We would expect to see a rich ecosystem
of matcher object classes that implement all kinds of predicate, including
composite predicates that incorporate subordinate matcher objects.

Matcher objects can then be used as type metaobjects. In general
they're types in only the minimal sense that they have a concept of type
membership. Specific kinds of matcher object may also have other kinds
of type-related behaviour; for example, Moose class metaobjects would
presumably overload smartmatch to implement a class membership check,
while also having introspective methods that only make sense for the Moose
concept of a class. But we don't need to worry about that; for signatures
the only aspect of a type that we care about is the membership predicate.

So a scalar signature item could specify a type constraint by referring
to an appropriate matcher object. We'd want to permit an arbitrary
expression for the matcher object, to allow for parameterised and
composite types. So very little signature syntax is required; really
just a signifier for smartmatching and a grammatical slot into which to
put the matcher expression. Using signature syntax otherwise as it is
today, this would look something like​:

use Smart\_Type qw\(Num Str union HashRef\_of\);   \# imaginary module
use feature "signatures";
sub gronk \($why ~Str\, $how\_hard ~Num = 5\) \{\.\.\.\}
sub annotate \($subject\, $msg ~ union\(Str\, HashRef\_of\(Str\)\)\) \{\.\.\.\}
sub nom \($food ~ My&#8203;::Food\->meta = My&#8203;::Apple\->new\) \{\.\.\.\}

"~" references the smartmatch operator while not being longer than
necessary. The matcher expression would have to be restricted to
expressions above some middling precedence level, so that in the case of
an optional parameter it doesn't read the "=" and default value expression
as part of the matcher expression. But I imagine matcher objects would
also overload the bitwise operators to perform Boolean compositions,
so it would be nice for the threshold to be lower than those operators.
Any expression of lower precedence than the threshold could of course
be parenthesised. So we'd have things like​:

use Smart\_Type qw\(Str HashRef\_of\);
use feature "signatures";
sub annotate \($subject\, $msg ~ Str | HashRef\_of\(Str\)\) \{\.\.\.\}
sub frob \($spec ~ \(state $fspec\_type = gen\_fspec\_type\(\)\)\) \{\.\.\.\}

With respect to the last of those examples, usually the type we want to
smartmatch against is fixed for all time, and recomputing the matcher
for each execution of the sub would be unwanted expense, potentially
significant. So making the matcher be implicitly saved in an invisible
state variable might be a better way to go. However, it's impossible to
get fully dynamic type constraint logic (referring to earlier parameters)
if that's always done, so consideration should then be given to having
a variant of "~" that explicitly makes the matcher expression dynamic.

In optional parameters I imagine the default value expression would be
exempt from the type constraint, but this could go either way.

It's a little more difficult to apply type constraints to array and
hash parameters. We probably want to apply the type constraint to the
whole array or hash, for maximum flexibility, but the actual smartmatch
operand would have to be *a reference to* the array or hash, so how do
we indicate that in the signature syntax?

It's also necessary to think about how type constraints would interact
with aliasing in signatures [perl #132472].

I don't really see the point of discussing this now, when we're probably
years away from having stable and sane smartmatch.

Leon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants