holli has asked for the wisdom of the Perl Monks concerning the following question:

So, I was toying around with Perl 6 lately. The following is a first iteration for a class to calculate all things triangle. I'd be happy about input about the code as I am new to Perl 6 and I am sure there is a lot to be improved. Also, when I post this thing (and the rest of its package), should I name the package Math::Geometry? Or better something less intrusive like Homework::Geometry?

Edit: The site mangles up the unicode operators. △ is a triangle, α to γ are the greek letters alpha to gamma.

use v6; use Math::Angle; subset DefinedAngle of Math::Angle where *.defined; subset UndefinedAngle of Math::Angle where !*.defined; subset DefinedCool of Cool where *.defined; subset UndefinedCool of Cool where !*.defined; unit class Math::Triangle; has Cool $.a; has Cool $.b; has Cool $.c; has Math::Angle $.alpha; has Math::Angle $.beta; has Math::Angle $.gamma; my @all_angles = ('alpha', 'beta', 'gamma'); my @all_sides = ('a', 'b', 'c'); sub prefix:<&#9651;> (*@things) is looser(&[,]) returns Math::Triangle + is export( :operators ) { my %things = @things; return Math::Triangle.new: |%things; }; sub triangle ( Cool :$a, Cool :$b, Cool :$c, Math::Angle :&#945;(:$alp +ha), Math::Angle :&#946;(:$beta), Math::Angle :&#947;(:$gamma) ) is e +xport( :operators ) { return Math::Triangle.new( a => $a, b => $b, c => $c, alpha => $alph +a, beta => $beta, gamma => $gamma ); } method Str() { return "&#9651; &#945;<$!alpha>, &#946;<$!beta>,&#947;<$!gamma>, a +<{$!a||''}>, b<{$!b||''}>, c<{$!c||''}>"; } method BUILD( *%_ [ Cool :$a, Cool :$b, Cool :$c, Math::Angle :&#945;( +:$alpha), Math::Angle :&#946;(:$beta), Math::Angle :&#947;(:$gamma) ] + ) { $!a = $a; $!alpha = $alpha; $!b = $b; $!beta = $beta; $!c = $c; $!gamma = $gamma; self.check(); self.compute(); } method angles() { my %angles = ( alpha => $!alpha, beta => $!beta, gamma => $!gamma ); @all_angles.grep( { %angles{$_}:exists && %angles{$_}.defined } ); } method sides () { my %sides = ( a => $!a, b => $!b, c => $!c ); @all_sides.grep( { %sides{$_}:exists && %sides{$_}.defined } ); } method check() { my @angles = self.angles; my @sides = self.sides; die "Sides do not satisfy the triangle inequality" if @sides == 3 && !satifies_triangle_inequality( $!a, $!b, $!c ); if ( @angles > 0 ) { my $angle_sum = 0; for ($!alpha, $!beta, $!gamma) -> $angle { next unless $angle.defined; die "Invalid angle ($angle). An angle in a triangle must be grea +ter than zero and smaller than 180°" unless 0 < $angle.degrees < 180; $angle_sum += $angle.degrees; } die "The sum of two angles must be smaller than 180°" if @angles == 2 && $angle_sum >= 180; die "The sum of three angles must be 180°" if @angles == 3 && $angle_sum != 180; die "Triangle with three known sides only is not determined" if @angles == 3 && @sides == 0; } } multi method compute() { repeat { say "intermediate ", self.Str; } until self.compute( sides => self.sides.Array, angles => self.angl +es.Array ); say "final ", self.Str; } multi method compute( :@sides where *.elems == 3, :@angles where *.ele +ms == 3 ) { return True; } multi method compute( :@sides where *.elems == 3, :@angles where *.ele +ms == 0 ) { #say "compute sides<3>, angles<0>"; $!alpha = cosine_law_a( $!a, $!b, $!c ); $!beta = cosine_law_a( $!b, $!a, $!c ); $!gamma = cosine_law_a( $!c, $!a, $!b ); return True; } multi method compute( :@sides where *.elems == 2, :@angles where *.ele +ms == 3 ) { #say "compute sides<2>, angles<3>"; $!a = sine_law($!alpha, $!beta, $!b ) unless $!a.defined; $!b = sine_law($!beta, $!alpha, $!a ) unless $!b.defined; $!c = sine_law($!gamma, $!alpha, $!a ) unless $!c.defined; return False; #say self.Str; } multi method compute( :@sides where *.elems == 2, :@angles where *.ele +ms == 1 ) { say "compute sides<2>, angles<1>"; # SAS-case $!a = cosine_law($!alpha, $!b, $!c).round(0.000001) if !$!a.defined && ( $!alpha.defined && $!b.defined && $!c.defined + ); $!b = cosine_law($!beta, $!a, $!c).round(0.000001) if !$!b.defined && ( $!beta.defined && $!a.defined && $!c.defined +); $!c = cosine_law($!gamma, $!a, $!b).round(0.000001) if !$!c.defined && ( $!gamma.defined && $!a.defined && $!b.defined + ); # SSA if $!alpha.defined && $!a.defined { ($!beta, $!gamma) = self.solve_SsA( $!alpha, $!a, $!b, $!c ) } elsif $!beta.defined && $!b.defined { ($!alpha, $!gamma) = self.solve_SsA( $!beta, $!b, $!a, $!c ) } elsif $!gamma.defined && $!c.defined { ($!alpha, $!beta) = self.solve_SsA( $!gamma, $!c, $!a, $!b ) } return False; } # WWS- and WSW- cases multi method compute( :@sides where *.elems == 1, :@angles where *.ele +ms == 2 ) { say "compute sides<1>, angles<2>"; # calculate the missing angle $!alpha = self.angle_sum_law( $!beta, $!gamma ) unless $!alpha.defined; $!beta = self.angle_sum_law( $!alpha, $!gamma ) unless $!beta.defined; $!gamma = self.angle_sum_law( $!alpha, $!beta ) unless $!gamma.defined; #the missing sides $!a = $!b.defined ?? self.sine_law($!alpha, $!beta, $!b ) !! $!c.defined ?? self.sine_law($!alpha, $!gamma, $!c ) !! 0 unless $!a.defined; $!b = $!a.defined ?? self.sine_law($!beta, $!alpha, $!a ) !! $!c.defined ?? self.sine_law($!beta, $!gamma, $!c ) !! 0 unless $!b.defined; $!c = $!a.defined ?? self.sine_law($!gamma, $!alpha, $!a ) !! $!b.defined ?? self.sine_law($!gamma, $!beta, $!b ) !! 0 unless $!b.defined; return False; } # The SSA- or ASS-case is only unique, if the known angle is is opposi +te # to the bigger one of the known sides ( SsA case) method solve_SsA( Math::Angle $angle, Cool $opposite_side_of_angle, Co +ol $other_side1, Cool $other_side2 ) { #say "solve_SsA"; return self.solve_ssw( $angle, $opposite_side_of_angle, $other_sid +e1 ) if $other_side1.defined && $opposite_side_of_angle >= $other_sid +e1; return self.solve_ssw( $angle, $opposite_side_of_angle, $other_sid +e2 ) if $other_side2.defined && $opposite_side_of_angle >= $other_sid +e2; } #alpha, a, b : where a > b method solve_ssw( Math::Angle $angle, Cool $opposite_side_of_angle, Co +ol $other_side ) { #say "solve_ssw $angle, $opposite_side_of_angle, $other_side"; return gather { take $_ = sine_law_a( $other_side, $angle, $opposite_side_of_ang +le ); take angle_sum_law( $angle, $_ ); }; } # isoceles: all sides of equal length method is_isoceles() { return $!a == $!b == $!c; } # scalene: two sides have the same length method is_scalene() { return $!a == $!b || $!a == $!c || $!c == $!b; } # oblique: none of the angles is a right method is_oblique() { my $right = Math::Angle.new( degrees => 90); return $!alpha != $right && $!beta != $right && $!gamma != $right; } # right: one of the angles is 90° method is_right() { my $right = Math::Angle.new( degrees => 90); return $!alpha == $right || $!beta == $right || $!gamma == $right; } #acute: all angles < 90° method is_acute() { my $right = Math::Angle.new( degrees => 90); return $!alpha < $right && $!beta < $right && $!gamma < $right; } #obtuse: one angle > 90° method is_obtuse() { my $right = Math::Angle.new( degrees => 90); return $!alpha > $right || $!beta > $right || $!gamma > $right; } method is_similar_to( Math::Triangle ) { } sub cosine_law( Math::Angle $angle, Cool $cathede1, Cool $cathede2 ) { return sqrt( $cathede1**2 + $cathede2**2 - ( 2 * $cathede1 * $cathed +e2 * cos($angle.radians) ) ); } sub cosine_law_a( Cool $opposite_side_of_angle, Cool $other_side1, Coo +l $other_side2 ) { return Math::Angle.new( radians => acos( ( $other_side1**2 + $other_side2**2 - $opposite_side_of_angle**2 + ) / (2 * $other_side1 * $other_side2) )); } sub sine_law( Math::Angle $angle, Math::Angle $known_angle, Cool $know +n_side ) { # $side / sin($angle) = $known_side / sin( $known_angle ) return ( sin($angle.radians) * $known_side ) / sin( $known_angle.ra +dians ) ; } sub sine_law_a( Cool $side, Math::Angle $known_angle, Cool $known_side + ) { # $side / sin($angle) = $known_side / sin( $known_angle ) # $side = ( $known_side * sin( $angle ) ) / sin( $known_side ) # $side * sin( $known_side ) = $known_side * sin( $angle ) # sin( $angle ) = $side * sin( $known_side ) / $known_side return Math::Angle.new( radians => asin( $side * sin( $known_angle.r +adians ) / $known_side ) ) ; } sub angle_sum_law(DefinedAngle $a, DefinedAngle $b) { return Math::Angle.new( radians => pi - $a.radians - $b.radians ); } sub satifies_triangle_inequality($a, $b, $c) { return $a + $b > $c && $c + $a > $b && $b + $c > $a; }

Replies are listed 'Best First'.
Re: RFC: Math::Triangle (Perl 6)
by Laurent_R (Canon) on Sep 18, 2017 at 05:32 UTC
    Just a very quick note. An isosceles triangle has two sides of equal length. A triangle with three sides equal is equilateral. Also, a scalene triangle is a triangle where all sides are different.
Re: RFC: Math::Triangle (Perl 6)
by salva (Canon) on Sep 18, 2017 at 06:52 UTC
    I find using a class just to represent angles, overkill.
      That is because you think Perl 5. Perl 6 is full OO. There is absolutely nothing in the language that isn't an class/object, not even things like keywords. Thus there is no overhead to avoid.
      use v6; 1.say; #prints 1
      So using a class even for something relatively simple is not overkill, instead not using a class is impossible.


      holli

      You can lead your users to water, but alas, you cannot drown them.
        That is because you think Perl 5. Perl 6 is full OO. There is absolutely nothing in the language that isn't an class/object, not even things like keywords. (...)
        use v6; 1.say; #prints 1 [download]
        So using a class even for something relatively simple is not overkill, instead not using a class is impossible.
        I disagree with this view.

        Granted, there is one (low) level of abstraction where this is kind of true. Kind of true, because the fact that you can write 1.say; does not necessarily mean that the literal integer 1 is an object. As far as I can say, this is more a form of boxing or perhaps autoboxing: in a sense, the primitive type is converted into an object for the sake of using methods on it, but 1 is really a primitive or native type, not really an object. But that's not my main point.

        Even if this view of yours were to hold and if I were to admit (solely for the sake of argument) that "everything is an object," this would be a very low-level view of the language. You admittedly need OO programming to create new types. But that does not mean that you have to do all your programming at this low level. Perl 6 offers many (often higher) levels of abstraction (or programming paradigms), such as declarative programming (e.g. regexes and grammars), functional programming (data pipelines, higher order functions, closures, etc.), concurrent and parallel programming (promises, etc.), and of course more traditional procedural or imperative programming. Or even logic programming, if you are willing to take some extra efforts. So that not using a class is actually far from impossible.

        We, as programmers, have to decide which level of abstraction to use, and you and I may agree or disagree on that. But saying that everything should be OO is just at variance with the Perl (5 or 6) philosophy, and, IMHO, plainly wrong.

        That is because you think Perl 5

        Not really. I was actually thinking in a mathematical way.

        I didn't mean overkill as inefficiency from a computational point of view, but overkill as introducing a new concept for something that is just a number.

Re: RFC: Math::Triangle (Perl 6)
by Arunbear (Prior) on Sep 19, 2017 at 11:00 UTC
    Somewhat OT, but "Cool" reminds me of the "shagadelic" method that Mojolicious used to have.

    Thankfully they replaced it with the more sane app->start