NAME
Data::Focus - generic getter/setter/traverser for complex data structures
SYNOPSIS
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}]
DESCRIPTION
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.
Concept
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.
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.
Lenses
Every lens is a subclass of Data::Focus::Lens class. Lenses included in this distribution are:
- Data::Focus::Lens::HashArray::Index
-
Index access to a hash/array. It's like
$hash->{$i}, $array->[$i], @{$hash}{$i1, $i2}, @{$array}[$i1, $i2]
. - Data::Focus::Lens::HashArray::All
-
Access all values in a hash/array. It's like
values(%$hash), @$array
. - Data::Focus::Lens::HashArray::Recurse
-
Recursively traverse all values in a tree of hashes and arrays.
- Data::Focus::Lens::Composite
-
Composition of multiple lenses.
All Data::Focus::HashArray::* modules optionally support immutable update. See individual documents for detail.
Lens Coercion
If you pass something that's not a Data::Focus::Lens object to Data::Focus's methods, it is coerced (cast) to a lens.
Currently, the passed value is treated as the index
argument of Data::Focus::Lens::HashArray::Index's constructor. 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");
We are planning a more dynamic mechanism of lens coercion in future releases. However, as long as the target is a hash-ref or array-ref, Data::Focus::Lens::HashArray::Index will always be used for coercion, i.e., the above example is guaranteed to work.
Traversals
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.
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.
Lens Composition
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.
EXPORTABLE FUNCTIONS
These functions are exported only by request.
$focused = focus($target, @lenses)
Alias of Data::Focus->new(target => $target, lens => \@lenses)
. It creates a Data::Focus object. @lenses
are optional.
CLASS METHODS
$focused = Data::Focus->new(%args)
The constructor. Fields in %args
are:
target
=> SCALAR (mandatory)-
The target object it focuses into.
lens
=> LENS or ARRAYREF_OF_LENSES (optional)-
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.
$lens = Data::Focus->coerce_to_lens($maybe_lens)
Coerce $maybe_lens
to a Data::Focus::Lens object.
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.
OBJECT METHODS
$deeper_focused = $focused->into(@lenses)
Focus more deeply with the given @lenses
and return the Data::Focus object.
$deeper_focused
is a new Data::Focus object. $focused
remains unchanged.
my $result1 = $focused->into("foo", "bar")->get();
my $result2 = $focused->into("foo")->get("bar");
my $result3 = $focused->get("foo", "bar");
## $result1 == $result2 == $result3
$datum = $focused->get(@lenses)
Get the focused $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
.
If it focuses on more than one values (multiple focal points), it returns the first value.
@data = $focused->list(@lenses)
Get the focused @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.
$modified_target = $focused->set(@lenses, $datum)
Set the value of the focused element to $datum
, and return the $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
.
$modified_target = $focused->over(@lenses, $updater)
Update the value of the focused element by $updater
, and return the $modified_target
.
The arguments @lenses
are optional. If supplied, @lenses
are used to focus more deeply into the target.
$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
.
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.
HOW TO CREATE A LENS
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.
RELATIONSHIP TO HASKELL
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 forLens
,Prism
orIso
type. (^.)
-
No counterpart in Data::Focus.
(^?)
-
get()
method of Data::Focus. (^..)
-
list()
method of Data::Focus. (.~)
-
set()
method of Data::Focus. (%~)
-
over()
method of Data::Focus. Applicative
-
Applicative
typeclass corrensponds to Data::Focus::Applicative.
SEE ALSO
There are tons of modules in CPAN for data access and traversal.
REPOSITORY
https://github.com/debug-ito/Data-Focus
BUGS AND FEATURE REQUESTS
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.
AUTHOR
Toshio Ito, <toshioito at cpan.org>
LICENSE AND COPYRIGHT
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.