Is there a 'clamp' method/sub for ranges/Num etc in Raku (i.e. Perl6)?
Asked Answered
H

3

7

Is there a 'clamp' or equivalent method or sub in Perl6?

eg

my $range= (1.0 .. 9.9)
my $val=15.3;

my $clamped=$range.clamp($val);
# $clamped would be 9.9

$val= -1.3;
$clamped=$range.clamp($val);
# $clamped would be 1.0
Hoxha answered 19/3, 2019 at 22:9 Comment(1)
Ha ha the line noise version: my ($i,$l,$ll)=(50,1,10); say $i>$l??$l!!$i <$ll??$ll!!$i;Hoxha
W
10

Another tact you might like to explore is using a Proxy, which allows you to define "hooks" when fetching or storing a value from a container

sub limited-num(Range $range) is rw {
    my ($min, $max) = $range.minmax;
    my Numeric $store = $min;
    Proxy.new(
        FETCH => method () { $store },
        STORE => method ($new) {
            $store = max($min, min($max, $new));
        }
    )
}

# Note the use of binding operator `:=`
my $ln := limited-num(1.0 .. 9.9);
say $ln;     # OUTPUT: 1

$ln += 4.2;  
say $ln;     # OUTPUT: 5.2

$ln += 100;  
say $ln;     # OUTPUT: 9.9

$ln -= 50;   
say $ln;     # OUTPUT: 1

$ln = 0;     
say $ln;     # OUTPUT: 1

This particular limited-num will initialise with it's min value, but you can also set it at declaration

my $ln1 := limited-num(1.0 .. 9.9) = 5.5;
say $ln1;    # OUTPUT 5.5;

my $ln2 := limited-num(1.0 .. 9.9) = 1000;
say $ln2;    # OUTPUT 9.9
Waksman answered 20/3, 2019 at 6:24 Comment(0)
C
6

I don't think so. So, perhaps:

multi clamp ($range, $value) {
  given $range {
    return .max when (($value cmp .max) === More);
    return .min when (($value cmp .min) === Less);
  }
  return $value
} 
my $range = (1.0 .. 9.9);
say $range.&clamp: 15.3; # 9.9
say $range.&clamp: -1.3; # 1

my $range = 'b'..'y';
say $range.&clamp: 'a'; # b
say $range.&clamp: 'z'; # y

The MOP allows direct exploration of the objects available in your P6 system. A particularly handy metamethod is .^methods which works on most built in objects:

say Range.^methods; # (new excludes-min excludes-max infinite is-int ...

By default this includes just the methods defined in the Range class, not the methods it inherits. (To get them all you could use say Range.^methods: :all. That'll net you a much bigger list.)

When I just tried it I found it also included a lot of methods unhelpfully named Method+{is-nodal}.new. So maybe use this instead:

say Range.^methods.grep: * !~~ / 'is-nodal' /;

This netted:

(new excludes-min excludes-max infinite is-int elems iterator
flat reverse first bounds int-bounds fmt ASSIGN-POS roll pick
Capture push append unshift prepend shift pop sum rand in-range
hyper lazy-if lazy item race of is-lazy WHICH Str ACCEPTS perl
Numeric min max BUILDALL)

That's what I used to lead me to my solution above; I sort of know the methods but use .^methods to remind me.


Another way to explore what's available is doc, eg the official doc's Range page. That netted me:

ACCEPTS min excludes-min max excludes-max bounds
infinite is-int int-bounds minmax elems list flat
pick roll sum reverse Capture rand

Comparing these two lists, sorted and bagged, out of curiosity:

say

<ACCEPTS ASSIGN-POS BUILDALL Capture Numeric Str WHICH append
 bounds elems excludes-max excludes-min first flat fmt hyper 
 in-range infinite int-bounds is-int is-lazy item iterator
 lazy lazy-if max min new of perl pick pop prepend push
 race rand reverse roll shift sum unshift>.Bag

 ∩

<ACCEPTS Capture bounds elems excludes-max excludes-min flat
 infinite int-bounds is-int list max min minmax pick
 rand reverse roll sum>.Bag

displays:

Bag(ACCEPTS, Capture, bounds, elems, excludes-max, excludes-min,
flat, infinite, int-bounds, is-int, max, min, pick,
rand, reverse, roll, sum)

So for some reason, list, minmax, and sum are documented as Range methods but are not listed by my .^methods call. Presumably they're called Method+{is-nodal}.new. Hmm.

say Range.^lookup('minmax'); # Method+{is-nodal}.new
say Range.^lookup('minmax').name; # minmax

Yep. Hmm. So I could have written:

say Range.^methods>>.name.sort;

(ACCEPTS ASSIGN-POS AT-POS BUILDALL Bag BagHash Capture EXISTS-POS
 Mix MixHash Numeric Set SetHash Str WHICH append bounds elems
 excludes-max excludes-min first flat fmt hyper in-range infinite
 int-bounds is-int is-lazy item iterator lazy lazy-if list max min
 minmax new of perl pick pop prepend push race rand reverse roll
 shift sum unshift)

Anyhow, hope that's helpful.

Courtenay answered 19/3, 2019 at 23:9 Comment(3)
Thanks for that. Simple enough make a sub, I just wasn't sure if it was implemented as some perl6-ism. I haven't gone into the MOP in any depth ATM, so more to learn ;)Hoxha
For my immediate purpose I went for an 'inline' : $rate= $rate > 0.1??0.1!!$rate; $rate= $rate < -0.1??-0.1!!$rate;Hoxha
Yw. I think most folk could use P6 for years without ever needing to look into the MOP and not realizing they're using it when they write things like say WHAT 42. About the only thing I recall off the top of my head ever using it for beyond that (and similar like HOW) is .^methods and .^lookup.Courtenay
B
0

Strange that no one has suggested using augment. Admittedly, it creates global changes, but that might not be an issue.

augment class Range {
   method clamp ($value) { ... }
}

You will need to use the pragmause MONKEY-TYPING in the same scope before the augment in order to use it though. But this way, you can simply say $range.clamp(5), for instance. It saves you one character over raiph's answer, but at the (not insignificant) cost of breaking precompilation.

Baseline answered 19/9, 2020 at 2:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.