Data::Focus - generic getter/setter/traverser for complex data structures
use Data::Focus qw(focus); my $target = [ "hoge", { foo => "bar", quux => ["x", "y", "z"] } ]; my $z = focus($target)->get(1, "quux", 2); my @xyz = focus($target)->list(1, "quux", [0,1,2]); ## $z: "z" ## @xyz: qw(x y z) focus($target)->set(1, "foo", 10); focus($target)->set(1, "quux", 11); ## $target: ["hoge", {foo => 10, quux => 11}] focus($target)->over(1, ["foo", "quux"], sub { $_[0] * $_[0] }); ## $target: ["hoge", {foo => 100, quux => 121}]
tl;dr: This is a port of Haskell's lens-family-core package.
Data::Focus provides a way to access data elements in a deep, complex and nested data structure. So it's just a complicated version of Data::Diver, but Data::Focus has the following notable features.
It provides a generic way to access any type of objects as long as they have appropriate "lenses". It's like DBI of accessing nested data structures.
It makes it easy to update immutable objects. Strictly speaking, that means creating partially modified copies of immutable objects.
Data::Focus focuses on some parts of a complex data structure. The complex data is called the target. The parts it focuses on are called focal points. With Data::Focus, you can get/set/modify the data at the focal points within the target.
Data::Focus uses objects called lenses to focus on data parts. Lenses are like DBD::* modules in DBI framework. They know how to focus on the data parts in the target. Different lenses are used to focus into different types of targets.
For example, consider the following code.
my $target = ["hoge", { foo => "bar" }]; my $part = $target->[1]{foo}; $target->[1]{foo} = "buzz";
In Perl, we can access the data part ("bar") in the $target by the subscripts ->[1]{foo}. A lens's job is exactly what ->[1]{foo} does here.
"bar"
$target
->[1]{foo}
With Data::Focus we can rewrite the above example to:
use Data::Focus qw(focus); use Data::Focus::Lens::HashArray::Index; my $target = ["hoge", { foo => "bar" }]; my $lens_1 = Data::Focus::Lens::HashArray::Index->new(index => 1); my $lens_foo = Data::Focus::Lens::HashArray::Index->new(index => "foo"); my $part = focus($target)->get($lens_1, $lens_foo); focus($target)->set($lens_1, $lens_foo, "buzz");
(I'm sure you don't wanna write this amount of code just to access an element in the $target. Don't worry. I'll shorten them below.)
Anyway, the point is, focus() function wraps the $target in a Data::Focus object, and methods of the Data::Focus object use lenses to access data parts at the focal points.
focus()
Every lens is a subclass of Data::Focus::Lens class. Lenses included in this distribution are:
Index access to a hash/array. It's like $hash->{$i}, $array->[$i], @{$hash}{$i1, $i2}, @{$array}[$i1, $i2].
$hash->{$i}, $array->[$i], @{$hash}{$i1, $i2}, @{$array}[$i1, $i2]
Access all values in a hash/array. It's like values(%$hash), @$array.
values(%$hash), @$array
Recursively traverse all values in a tree of hashes and arrays.
Call an accessor method of a blessed object. It's like $obj->method.
$obj->method
Composition of multiple lenses.
A front-end lens that dynamically creates an appropriate lens for you.
All Data::Focus::HashArray::* modules optionally support immutable update. See individual documents for detail.
If you pass something that's not a Data::Focus::Lens object to Data::Focus's methods, it is coerced (cast) to a lens.
The passed value is used to create Data::Focus::Lens::Dynamic lens. Then that lens creates an appropriate lens for the given target with the passed value. This means we can rewrite the above example to:
use Data::Focus qw(focus); my $target = ["hoge", { foo => "bar" }]; my $part = focus($target)->get(1, "foo"); focus($target)->set(1, foo => "buzz");
The above is possible because Data::Focus::Lens::Dynamic creates Data::Focus::Lens::HashArray::Index lenses under the hood. See Data::Focus::Lens::Dynamic for detail.
As you might already notice, a lens can have more than one focal points. This is like slices and traversals.
To obtain all elements at the focal points, use list() method.
list()
my $target = ["a", "b", "c"]; my @abc = focus($target)->list([0, 1, 2]);
Sometimes a lens has no focal point. In that case, you cannot set value to the target.
You can compose two lenses to create a composite lens by "." operator.
"."
my $target = ["hoge", { foo => "bar" }]; my $lens_1 = Data::Focus::Lens::HashArray::Index->new(index => 1); my $lens_foo = Data::Focus::Lens::HashArray::Index->new(index => "foo"); my $composite = $lens_1 . $lens_foo; my $part = focus($target)->get($composite); focus($target)->set($composite, "buzz");
To compose two or more lenses at once, use Data::Focus::Lens::Composite.
These functions are exported only by request.
Alias of Data::Focus->new(target => $target, lens => \@lenses). It creates a Data::Focus object. @lenses are optional.
Data::Focus->new(target => $target, lens => \@lenses)
@lenses
The constructor. Fields in %args are:
%args
target
The target object it focuses into.
lens
A lens or an array-ref of lenses used for focusing. If some of the lenses are not Data::Focus::Lens objects, they are coerced. See "Lens Coercion" for detail.
Coerce $maybe_lens to a Data::Focus::Lens object.
$maybe_lens
If $maybe_lens is already a Data::Focus::Lens, it returns $maybe_lens. Otherwise, it creates a lens out of $maybe_lens. See "Lens Coercion" for detail.
Focus more deeply with the given @lenses and return the Data::Focus object.
$deeper_focused is a new Data::Focus object. $focused remains unchanged.
$deeper_focused
$focused
my $result1 = $focused->into("foo", "bar")->get(); my $result2 = $focused->into("foo")->get("bar"); my $result3 = $focused->get("foo", "bar"); ## $result1 == $result2 == $result3
Get the focused $datum.
$datum
The arguments @lenses are optional. If supplied, @lenses are used to focus more deeply into the target to return $datum.
If it focuses on nothing (zero focal point), it returns undef.
undef
If it focuses on more than one values (multiple focal points), it returns the first value.
Get the focused @data.
@data
The arguments @lenses are optional. If supplied, @lenses are used to focus more deeply into the target to return @data.
If it focuses on nothing (zero focal point), it returns an empty list.
If it focuses on more than one values (multiple focal points), it returns all of them.
Set the value of the focused element to $datum, and return the $modified_target.
$modified_target
The arguments @lenses are optional. If supplied, @lenses are used to focus more deeply into the target.
If it focuses on nothing (zero focal point), it modifies nothing. $modified_target is usually the same instance as the target, or its clone (it depends on the lenses used).
If it focuses on more than one values (multiple focal points), it sets all of them to $datum.
Update the value of the focused element by $updater, and return the $modified_target.
$updater
$updater is a code-ref. It is called like
$modified_datum = $updater->($focused_datum)
where $focused_datum is a datum at one of the focal points in the target. $modified_datum replaces the $focused_datum in the $modified_target.
$focused_datum
$modified_datum
If it focuses on nothing (zero focal point), $updater is never called. $modified_target is usually the same instance as the target, or its clone (it depends on the lenses used).
If it focuses on more than one values (multiple focal points), $updater is repeatedly called for each of them.
To create your own lens, you have to write a subclass of Data::Focus::Lens that implements its abstract methods. However, writing your own Lens class from scratch is currently discouraged. Instead we recommend using Data::Focus::LensMaker.
Data::Focus::LensTester provides some common tests for lenses.
You can associate your own class with a specific lens object by implementing Lens() method in your class. See Data::Focus::Lens::Dynamic for detail.
Lens()
Once Lens() method is implemented, you can focus into objects of that class without explicitly creating lens objects for it.
package My::Class; ... sub Lens { my ($self, $param); my $lens = ...; ## create a Data::Focus::Lens object return $lens; } package main; use Data::Focus qw(focus); my $obj = My::Class->new(...); focus($obj)->get("hoge"); focus($obj)->set(foo => "bar");
Data::Focus's API and implementation are based on Haskell packages lens-family-core and lens.
For those familiar with Haskell's lens libraries, here is the Haskell-to-Perl mapping of terminology.
Traversal
The Traversal type corrensponds to Data::Focus::Lens. Currently there's no strict counterpart for Lens, Prism or Iso type.
Lens
Prism
Iso
(^.)
No counterpart in Data::Focus.
(^?)
get() method of Data::Focus.
get()
(^..)
list() method of Data::Focus.
(.~)
set() method of Data::Focus.
set()
(%~)
over() method of Data::Focus.
over()
Applicative
Applicative typeclass corrensponds to Data::Focus::Applicative.
There are tons of modules in CPAN for data access and traversal.
Data::Diver
JSON::Pointer
Data::Path
Data::SPath
Data::DPath
Data::FetchPath
Data::PathSimple
Data::SimplePath
Data::Transformer
Data::Walk
Data::Traverse
https://github.com/debug-ito/Data-Focus
Please report bugs and feature requests to my Github issues https://github.com/debug-ito/Data-Focus/issues.
Although I prefer Github, non-Github users can use CPAN RT https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Focus. Please send email to bug-Data-Focus at rt.cpan.org to report bugs if you do not have CPAN RT account.
bug-Data-Focus at rt.cpan.org
Toshio Ito, <toshioito at cpan.org>
<toshioito at cpan.org>
Copyright 2015 Toshio Ito.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
To install Data::Focus, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Data::Focus
CPAN shell
perl -MCPAN -e shell install Data::Focus
For more information on module installation, please visit the detailed CPAN module installation guide.