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

NAME

LINQ - the interface which all LINQ collections share

SYNOPSIS

  use feature 'say';
  use LINQ 'LINQ';
  
  my $double_even_numbers =
    LINQ( [1..100] )
      ->where( sub { $_ % 2 == 0 } )
      ->select( sub { $_ * 2 } );
  
  if ( not $double_even_numbers->DOES( 'LINQ::Collection' ) ) {
    die "What? But you said they all do it!";
  }
  
  for my $n ( $double_even_numbers->to_list ) {
    say $n;
  }

DESCRIPTION

Objects returned by the LINQ(), LINQ::Repeat(), and LINQ::Range() functions all provide the LINQ::Collection interface. Many of the methods in this interface also return new objects which provide this interface.

METHODS

Many methods take a parameter "CALLABLE". This means they can accept a coderef, an object overloading &{}, or an arrayref where the first item is one of the previous two things and the remainder are treated as arguments to curry to the first argument. A quoted regexp qr/.../ can also be used as a callable.

If using an arrayref, it is generally permissable to flatten it into a list, unless otherwise noted. An example of this can be seen in the documentation for select.

select( CALLABLE )

LINQ's version of map, except that the code given is always called in scalar context, being expected to return exactly one result.

Returns a LINQ::Collection of the results.

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my $names = $people->select( sub {
    return $_->{name};
  } );
  
  for my $name ( $names->to_list ) {
    print "$name\n";
  }

Another way of doing the same thing, using currying:

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my $BY_HASH_KEY = sub {
    my ($key) = @_;
    return $_->{$key};
  };
  
  my $names = $people->select( $BY_HASH_KEY, 'name' );
  
  for my $name ( $names->to_list ) {
    print "$name\n";
  }
select_many( CALLABLE )

If you wanted select to be able to return a list like map does, then select_many is what you want. However, rather than returning a Perl list, your callable should return a LINQ::Collection or an arrayref.

where( CALLABLE )

LINQ's version of grep. Returns a LINQ::Collection of the filtered results.

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my $young_people = $people->where( sub {
    return $_->{age} < 33;
  } );
min( CALLABLE? )

Returns the numerically lowest value in the collection.

  my $lowest = LINQ( [ 5, 1, 2, 3 ] )->min;   # ==> 1

If a callable is provided, then select will be called and the minimum of the result will be returned.

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my $lowest_age = $people->min( sub { $_->{age} } );   # ==> 31

If you need more flexible comparison (e.g. non-numeric comparison), use order_by followed by first.

max( CALLABLE? )

Like min, but returns the numerically highest value.

sum( CALLABLE? )

Like min, but returns the sum of all values in the collection.

average( CALLABLE? )

Takes sum, and divides by the count of items in the collection.

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my $average_age = $people->average( sub {
    return $_->{age};
  } );   # ==> 32.33333
aggregate( CALLABLE, INITIAL? )

LINQ's version of reduce (from List::Util). We pass $a and $b as the last arguments to CALLABLE, rather than using the package variables like List::Util does.

The CALLABLE must not be a flattened list, but may still be an arrayref. INITIAL is an initial value.

  my $people = LINQ( [
    { name => "Alice", age => 32 },
    { name => "Bob",   age => 31 },
    { name => "Carol", age => 34 },
  ] );
  
  my dotted_names = $people
    ->select( sub { $_->{name} } )
    ->aggregate( sub {
       my ( $a, $b ) = @_;
       return "$a.$b";
    } );
  
  print "$dotted_names\n";  # ==> Alice.Bob.Carol
join( Y, HINT?, X_KEYS, Y_KEYS, JOINER )

This is akin to an SQL join.

  my $people = LINQ( [
    { name => "Alice", dept => 'Marketing' },
    { name => "Bob",   dept => 'IT' },
    { name => "Carol", dept => 'IT' },
  ] );
  
  my $departments = LINQ( [
    { dept_name => 'Accounts',  cost_code => 1 },
    { dept_name => 'IT',        cost_code => 7 },
    { dept_name => 'Marketing', cost_code => 8 },
  ] );
  
  my $BY_HASH_KEY = sub {
    my ($key) = @_;
    return $_->{$key};
  };
  
  my $joined = $people->join(
    $departments,
    -inner,                        # inner join
    [ $BY_HASH_KEY, 'dept' ],      # select from $people 
    [ $BY_HASH_KEY, 'dept_name' ], # select from $departments
    sub {
      my ( $person, $dept ) = @_;
      return {
        name         => $person->{name},
        dept         => $person->{dept},
        expense_code => $dept->{cost_code},
      };
    },
  );

Hints -inner, -left, -right, and -outer are supported, analagous to the joins with the same names in SQL.

X_KEYS and Y_KEYS are non-list callables which return the values to join the two collections by.

JOINER is a callable (which may be a flattened list) which is passed items from each of the two collections and should return a new item. In the case of left/right/outer joins, one of those items may be undef.

group_join( Y, HINT?, X_KEYS, Y_KEYS, JOINER )

Similar to group except that rather than JOINER being called for every X/Y combination, all the Ys for a particular X are put in a collection, and the JOINER is called for each X and passed the collection of Ys.

The only hints supported are -inner and -left.

This is best explained with a full example:

  my $departments = LINQ( [
    { dept_name => 'Accounts',  cost_code => 1 },
    { dept_name => 'IT',        cost_code => 7 },
    { dept_name => 'Marketing', cost_code => 8 },
  ] );
  
  my $people = LINQ( [
    { name => "Alice", dept => 'Marketing' },
    { name => "Bob",   dept => 'IT' },
    { name => "Carol", dept => 'IT' },
  ] );
  
  my $BY_HASH_KEY = sub {
    my ($key) = @_;
    return $_->{$key};
  };
  
  my $joined = $departments->group_join(
    $people,
    -left,                         # left join
    [ $BY_HASH_KEY, 'dept_name' ], # select from $departments
    [ $BY_HASH_KEY, 'dept' ],      # select from $people 
    sub {
      my ( $dept, $people ) = @_;  # $people is a LINQ::Collection 
      my $names = $people->select( $BY_HASH_KEY, 'name' )->to_array;
      return {
        dept      => $dept->{dept_name},
        cost_code => $dept->{cost_code},
        people    => $names,
      };
    },
  );
  
  # [
  #   {
  #     'cost_code' => 1,
  #     'dept' => 'Accounts',
  #     'people' => []
  #   },
  #   {
  #     'cost_code' => 7,
  #     'dept' => 'IT',
  #     'people' => [
  #       'Bob',
  #       'Carol'
  #     ]
  #   },
  #   {
  #     'cost_code' => 8,
  #     'dept' => 'Marketing',
  #     'people' => [
  #       'Alice'
  #     ]
  #   }
take( N )

Takes just the first N items from a collection, returning a new collection.

take_while( CALLABLE )

Takes items from the collection, stopping at the first item where CALLABLE returns false.

If CALLABLE dies, there are some issues on older versions of Perl with the error message getting lost.

skip( N )

Skips the first N items from a collection, and returns the rest as a new collection.

skip_while( CALLABLE )

Skips the first items from a collection while CALLABLE returns true, and returns the rest as a new collection.

concat( COLLECTION )

Returns a new collection by concatenating this collection with another collection.

  my $deck_of_cards = $red_cards->concat( $black_cards );
order_by( HINT?, CALLABLE? )

HINT may be -numeric (the default) or -string.

If CALLABLE is given, it should return a number or string to sort by.

  my $sorted = $people->order_by(
    -string,
    [ $BY_HASH_KEY, 'name' ]   # CALLABLE as an arrayref
  );
then_by( HINT?, CALLABLE )

Not implemented.

order_by_descending( HINT?, CALLABLE )

Like order_by but uses reverse order.

then_by_descending( HINT?, CALLABLE )

Not implemented.

reverse

Reverses the order of the collection.

group_by( CALLABLE )

Groups the items by the key returned by CALLABLE.

The collection of groups is a LINQ::Collection and each grouping is a LINQ::Grouping. LINQ::Grouping provides two accessors: key and values, with values returning a LINQ::Collection of items.

  my $people = LINQ( [
    { name => "Alice", dept => 'Marketing' },
    { name => "Bob",   dept => 'IT' },
    { name => "Carol", dept => 'IT' },
  ] );
  
  my $groups = $people->group_by( sub { $_->{dept} } );
  
  for my $group ( $groups->to_list ) {
    print "Dept: ", $group->key, "\n";
    
    for my $person ( $group->values->to_list ) {
      print " - ", $person->{name};
    }
  }
distinct( CALLABLE? )

Returns a new collection without any duplicates which were in the original.

If CALLABLE is provided, this will be used to determine when two items are considered identical/equivalent. Otherwise, numeric equality is used.

union( COLLECTION, CALLABLE? )

Returns a new collection formed from the union of both collections.

  my $first  = LINQ( [ 1, 2, 3, 4 ] );
  my $second = LINQ( [ 3, 4, 5, 6 ] );
  
  $first->union( $second )->to_array;  # ==> [ 1, 2, 3, 4, 5, 6 ]

If CALLABLE is provided, this will be used to determine when two items are considered identical/equivalent. Otherwise, numeric equality is used.

intersect( COLLECTION, CALLABLE? )

Returns a new collection formed from the union of both collections.

  my $first  = LINQ( [ 1, 2, 3, 4 ] );
  my $second = LINQ( [ 3, 4, 5, 6 ] );
  
  $first->intersect( $second )->to_array;  # ==> [ 3, 4 ]

If CALLABLE is provided, this will be used to determine when two items are considered identical/equivalent. Otherwise, numeric equality is used.

except( COLLECTION, CALLABLE? )

Returns a new collection formed from the asymmetric difference of both collections.

  my $first  = LINQ( [ 1, 2, 3, 4 ] );
  my $second = LINQ( [ 3, 4, 5, 6 ] );
  
  $first->except( $second )->to_array;  # ==> [ 1, 2 ]

If CALLABLE is provided, this will be used to determine when two items are considered identical/equivalent. Otherwise, numeric equality is used.

sequence_equal( COLLECTION, CALLABLE? )

Returns true if and only if each item in the first collection is identical/equivalent to its corresponding item in the second collection, considered in order, according to CALLABLE.

If CALLABLE isn't given, then numeric equality is used.

first( CALLABLE? )

Returns the first item in a collection.

If CALLABLE is provided, returns the first item in the collection where CALLABLE returns true.

If there is no item to return, does not return undef, but throws a LINQ::Exception::NotFound exception.

first_or_default( CALLABLE?, DEFAULT )

Like first, but instead of throwing an exception, will return the DEFAULT.

last( CALLABLE? )

Returns the last item in a collection.

If CALLABLE is provided, returns the last item in the collection where CALLABLE returns true.

If there is no item to return, does not return undef, but throws a LINQ::Exception::NotFound exception.

last_or_default( CALLABLE?, DEFAULT )

Like last, but instead of throwing an exception, will return the DEFAULT.

single( CALLABLE? )

Returns the only item in a collection.

If CALLABLE is provided, returns the only item in the collection where CALLABLE returns true.

If there is no item to return, does not return undef, but throws a LINQ::Exception::NotFound exception.

If there are multiple items in the collection, or multiple items where CALLABLE returns true, throws a LINQ::Exception::MultipleFound exception.

single_or_default( CALLABLE?, DEFAULT )

Like single but rather than throwing an exception, will return DEFAULT.

element_at( N )

Returns element N within the collection. N may be negative to count from the end of the collection. Collections are indexed from zero.

If N exceeds the length of the collection, throws a LINQ::Exception::NotFound exception.

element_at_or_default( N, DEFAULT )

Like element_at but rather than throwing an exception, will return DEFAULT.

any( CALLABLE? )

Returns true if CALLABLE returns true for any item in the collection.

all( CALLABLE? )

Returns true if CALLABLE returns true for every item in the collection.

contains( ITEM, CALLABLE? )

Returns true if the collection contains ITEM. By default, this is checked using numeric equality.

If CALLABLE is given, this is passed two items and should return true if they should be considered identical/equivalent.

  my $SAME_NAME = sub {
    $_[0]{name} eq $_[1]{name};
  };
  
  if ( $people->contains( { name => "Bob" }, $SAME_NAME ) ) {
    print "The collection includes Bob.\n";
  }
count

Returns the size of the collection. (Number of items.)

to_list

Returns the collection as a list.

to_array

Returns an arrayref for the collection. This may be a tied arrayref and you should not assume it will be writable.

to_dictionary( CALLABLE )

The CALLABLE will be called for each item in the collection and is expected to return a string key.

The method will return a hashref mapping the keys to each item in the collection.

to_lookup( CALLABLE )

Alias for to_dictionary.

to_iterator

Returns a coderef which can be used to iterate through the collection.

  my $people = LINQ( [
    { name => "Alice", dept => 'Marketing' },
    { name => "Bob",   dept => 'IT' },
    { name => "Carol", dept => 'IT' },
  ] );
  
  my $next_person = $people->to_iterator;
  
  while ( my $person = $next_person->() ) {
    print $person->{name}, "\n";
  }
cast( TYPE )

Given a type constraint (see Type::Tiny) will attempt to coerce every item in the collection to the type, and will return the collection of coerced values. If any item cannot be coerced, throws a LINQ::Exception::Cast exception.

of_type( TYPE )

Given a type constraint (see Type::Tiny) will attempt to coerce every item in the collection to the type, and will return the collection of coerced values. Any items which cannot be coerced will be skipped.

zip( COLLECTION, CALLABLE )

Will loop through both collections in parallel and pass one item from each collection to CALLABLE as arguments.

If the two collections are of different sizes, will stop after exhausing the shorter collection.

default_if_empty( ITEM )

If this collection contains one or more items, returns itself.

If the collection is empty, returns a new collection containing just a single item, given as a parameter.

  my $collection = $people->default_if_empty( "Bob" );
  
  # Equivalent to:
  my $collection = $people->count ? $people : LINQ( [ "Bob" ] );
foreach( CALLABLE )

This calls CALLABLE on each item in the collection. The following are roughly equivalent:

  for ( $collection->to_list ) {
    say $_;
  }
  
  $collection->foreach( sub {
    say $_;
  } );

The advantage of the latter is that it avoids calling to_list, which would obviously fail on infinite collections.

You can break out of the loop using LINQ::LAST.

  my $counter = 0;
  $collection->foreach( sub {
    say $_;
    LINQ::LAST if ++$counter >= 10;
  } );

Microsoft's official LINQ API doesn't include a ForEach method, but this method is provided by the MoreLINQ extension.

WORKING WITH INFINITE COLLECTIONS

Because LINQ collections can be instantiated from an iterator, they may contain infinite items.

Certain methods aggregate the entire collection, so can go into an infinite loop. This includes: aggregate, min, max, sum, average, and count.

Other methods which will go into an infinite loop on infinite collections: join, group_join, group_by, order_by, order_by_descending, reverse, to_lookup, to_dictionary, and to_list.

The to_array method in general will succeed on infinite collections as it can return a reference to a tied array. However, trying to dereference the entire array to a list will fail.

BUGS

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

SEE ALSO

LINQ, LINQ::Grouping.

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

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2014, 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.