The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

LINQ::Util - useful utilities to make working with LINQ collections easier

SYNOPSIS

  use feature qw( say );
  use LINQ qw( LINQ )';
  use LINQ::Util qw( fields  );
  
  my $collection = LINQ( [
    { name => 'Alice', age => 30, dept => 'IT'        },
    { name => 'Bob'  , age => 29, dept => 'IT'        },
    { name => 'Carol', age => 32, dept => 'Marketing' },
    { name => 'Dave',  age => 33, dept => 'Accounts'  },
  ] );
  
  my $name_and_dept = $collection->select( fields( 'name', 'dept' ) );
  
  for ( $name_and_dept->to_list ) {
    printf( "Hi, I'm %s from %s\n", $_->name, $_->dept );
  }

DESCRIPTION

LINQ::Util provides a collection of auxiliary functions to make working with LINQ collections a little more intuitive and perhaps avoid passing a bunch of sub { ... } arguments to select and where.

FUNCTIONS

fields( SPEC )

Creates a coderef (actually a blessed object overloading &{}) which takes a hashref or object as input, selects just the fields/keys given in the SPEC, and returns an object with those fields.

A simple example would be:

  my $selector = fields( 'name' );
  my $object   = $selector->( { name => 'Bob', age => 29 } );

In this example, $object would be a blessed object with a name method which returns "Bob".

Fields can be renamed:

  my $selector = fields( 'name', -as => 'moniker' );
  my $object   = $selector->( { name => 'Bob', age => 29 } );
  say $object->moniker;  # ==> "Bob"

A coderef can be used as a field:

  my $selector = fields(
    sub { uc( $_->{'name'} ) }, -as => 'moniker',
  );
  my $object = $selector->( { name => 'Bob', age => 29 } );
  say $object->moniker;  # ==> "BOB"

An asterisk field selects all the input fields:

  my $selector = fields(
    sub { uc( $_->{'name'} ) }, -as => 'moniker',
    '*',
  );
  my $object = $selector->( { name => 'Bob', age => 29 } );
  say $object->moniker;  # ==> "BOB"
  say $object->name;     # ==> "Bob"
  say $object->age;      # ==> 29

The aim of the fields function is to allow the LINQ select method to function more like an SQL SELECT, where you give a list of fields you wish to select.

field( NAME )

Conceptually similar to fields() but for a single field. Returns the field value instead of a hashref of field values.

  my $field = field('name');
  say $field->( $_ ) for (
    { name => 'Alice' },
    { name => 'Bob' },
  );

If called in list context with extra arguments after the field name, a list will be returned, including the extra arguments unchanged.

  my $people = LINQ( [
    { name => 'Alice', age => 30, dept => 3 },
    { name => 'Bob'  , age => 29, dept => 3 },
    { name => 'Carol', age => 32, dept => 4 },
    { name => 'Dave',  age => 33, dept => 1 },
  ] );
  
  my $depts = LINQ( [
    { id => 3, name => 'IT'        },
    { id => 4, name => 'Marketing' },
    { id => 1, name => 'Accounts'  },
  ] );
  
  my $joiner = sub {
    my ( $person, $dept ) = @_;
    return {
      person_name => $person->{name},
      person_age  => $person->{age},
      dept_name   => $dept->{name},
    };
  };
  
  my $joined = $people->join( $depts, field 'dept', field 'id', $joiner );
  
  print Dumper( $joined->to_array );
check_fields( SPEC )

If fields() can be compared to SQL SELECT, then check_fields() can be compared to SQL WHERE. Like fields() it assumes your data is hashrefs or blessed objects with attributes.

  # Select people called Bob.
  $people
    ->where( check_fields( 'name', -is => 'Bob' ) )
    ->select( fields( 'name', 'age', 'dept' ) );

Different operators can be used. Whether performing string or numeric comparison, ">", "<", ">=", "<=", "==", and "!=" are used. (And the -is parameter is used to provide the right hand side of the comparison, even for comparisons like "!=".)

  $people
    ->where( check_fields( 'name', -cmp => '>', -is => 'Bob' ) );

check_fields() will probably guess correctly whether you want numeric or string comparison, but if you need to specify, you can:

  $people
    ->where( check_fields( 'phone', -is => '012345679', -string );
  
  $people
    ->where( check_fields( 'age', -is => '33', -numeric );

String comparisons can be made case-insensitive:

  $people
    ->where( check_fields( 'name', -is => 'Bob', -nocase ) );

You can use -in to find a value in an arrayref. These comparisons are always stringy and case-sensitive.

  $people
    ->where( check_fields( 'name', -in => ['Alice', 'Bob'] ) );

You can invert any comparison using -nix.

  $people
    ->where( check_fields( 'name', -nix, -in => ['Alice', 'Bob'] ) );

You can perform more complex matches using match::simple:

  $people
    ->where( check_fields( 'name', -match => qr/^[RB]ob(ert)?$/i ) );

SQL LIKE is also supported:

  $people
    ->where( check_fields( 'name', -like => 'Bob%', -nocase ) );

You can check multiple fields at once. There's an implied "AND" between the conditions.

  $people
    ->where( check_fields(
      'name',       -is => 'Bob',
      'age',  -nix, -is => 33,
    ) );

You can compare one field to another field using -to:

  # Says all the values which are between the min and max.
  LINQ(
    { min => 10, max => 100, value => 50 },
    { min => 10, max => 100, value =>  5 },
    { min => 10, max =>  20, value => 50 },
  )->where( check_fields(
    'value', -cmp => '>=', -to => 'min', -numeric,
    'value', -cmp => '<=', -to => 'max', -numeric,
  ) )->foreach( sub {
    say $_->value;
  } );

You can invert a whole check_fields() using the not method:

  my $where_not_bob = check_fields( 'name', -is => 'Bob' )->not;
  
  $people->where( $where_not_bob );

Generally, you can use not, and, and or methods to compose more complex conditions. The ~, &, and | bitwise operators are also overloaded to compose conditions.

  my $where_alice = check_fields( 'name', -is => 'Alice' );
  my $where_bob   = check_fields( 'name', -is => 'Bob' );
  
  my $where_alice_or_bob = $where_alice->or( $where_bob );
  
  # Or...
  my $where_alice_or_bob = $where_alice | $where_bob;
  
  # Or...
  my $where_alice_or_bob =
    check_fields( 'name', -is => 'Alice' )
            ->or( 'name', -is => 'Bob' );

Like with fields(), fields can be a coderef.

  my $where_bob = check_fields(
    sub { $_->get_name("givenName") }, -is => 'Bob'
  );

BUGS

Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=LINQ.

SEE ALSO

LINQ::Collection, LINQ.

https://en.wikipedia.org/wiki/Language_Integrated_Query

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2021 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.