| Filename | /mnt/stuff/src/my-cpan/hailo/lib/Hailo.pm |
| Statements | Executed 125145 statements in 599ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 25000 | 1 | 1 | 542ms | 50.4s | Hailo::_learn_one |
| 25000 | 1 | 1 | 39.7ms | 54.7ms | Hailo::_engine (xsub) |
| 25000 | 1 | 1 | 36.0ms | 82.6ms | Hailo::_tokenizer (xsub) |
| 3 | 1 | 1 | 4.29ms | 108ms | Hailo::_new_class |
| 3 | 3 | 1 | 756µs | 6.71ms | Hailo::_is_interactive |
| 1 | 1 | 1 | 418µs | 11.2ms | Hailo::BEGIN@4 |
| 1 | 1 | 1 | 287µs | 306µs | Hailo::BEGIN@178 |
| 3 | 3 | 2 | 164µs | 109ms | Hailo::__ANON__[lib/Hailo.pm:170] |
| 1 | 1 | 1 | 87µs | 51.9s | Hailo::train |
| 1 | 1 | 1 | 66µs | 149µs | Hailo::BEGIN@3 |
| 1 | 1 | 1 | 57µs | 104µs | Hailo::CORE:open (opcode) |
| 13 | 1 | 1 | 53µs | 53µs | Hailo::CORE:regcomp (opcode) |
| 6 | 6 | 2 | 38µs | 47.3ms | Hailo::_storage (xsub) |
| 17 | 3 | 2 | 34µs | 34µs | Hailo::CORE:match (opcode) |
| 1 | 1 | 1 | 26µs | 103µs | Hailo::DEMOLISH |
| 3 | 1 | 1 | 21µs | 21µs | Hailo::CORE:sort (opcode) |
| 1 | 1 | 1 | 18µs | 71µs | Hailo::save |
| 1 | 1 | 1 | 15µs | 39µs | Hailo::BEGIN@123 |
| 1 | 1 | 1 | 14µs | 84µs | Hailo::BEGIN@11 |
| 4 | 1 | 1 | 13µs | 13µs | Hailo::__ANON__[lib/Hailo.pm:111] |
| 1 | 1 | 1 | 13µs | 714µs | Hailo::BEGIN@5 |
| 1 | 1 | 1 | 13µs | 84µs | Hailo::BEGIN@3.8 |
| 1 | 1 | 1 | 12µs | 1.47ms | Hailo::BEGIN@9 |
| 1 | 1 | 1 | 12µs | 63µs | Hailo::BEGIN@7 |
| 1 | 1 | 1 | 12µs | 53µs | Hailo::BEGIN@8 |
| 1 | 1 | 1 | 11µs | 368µs | Hailo::BEGIN@6.10 |
| 3 | 1 | 1 | 5µs | 5µs | Hailo::PLUGINS (xsub) |
| 1 | 1 | 1 | 2µs | 2µs | Hailo::engine_args (xsub) |
| 1 | 1 | 1 | 2µs | 2µs | Hailo::storage_args (xsub) |
| 1 | 1 | 1 | 2µs | 2µs | Hailo::tokenizer_args (xsub) |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:102] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:145] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:146] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:151] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:155] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:182] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:33] |
| 0 | 0 | 0 | 0s | 0s | Hailo::__ANON__[lib/Hailo.pm:65] |
| 0 | 0 | 0 | 0s | 0s | Hailo::_train_fh |
| 0 | 0 | 0 | 0s | 0s | Hailo::learn |
| 0 | 0 | 0 | 0s | 0s | Hailo::learn_reply |
| 0 | 0 | 0 | 0s | 0s | Hailo::reply |
| 0 | 0 | 0 | 0s | 0s | Hailo::stats |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package Hailo; | ||||
| 2 | |||||
| 3 | 4 | 84µs | 3 | 304µs | use 5.010; # spent 149µs making 1 call to Hailo::BEGIN@3
# spent 84µs making 1 call to Hailo::BEGIN@3.8
# spent 71µs making 1 call to feature::import |
| 4 | 2 | 178µs | 2 | 11.2ms | # spent 11.2ms (418µs+10.8) within Hailo::BEGIN@4 which was called:
# once (418µs+10.8ms) by Mouse::Util::_try_load_one_class at line 4 # spent 11.2ms making 1 call to Hailo::BEGIN@4
# spent 7µs making 1 call to autodie::import |
| 5 | 2 | 33µs | 2 | 1.42ms | # spent 714µs (13+701) within Hailo::BEGIN@5 which was called:
# once (13µs+701µs) by Mouse::Util::_try_load_one_class at line 5 # spent 714µs making 1 call to Hailo::BEGIN@5
# spent 701µs making 1 call to Any::Moose::import |
| 6 | 2 | 30µs | 2 | 725µs | # spent 368µs (11+357) within Hailo::BEGIN@6.10 which was called:
# once (11µs+357µs) by Mouse::Util::_try_load_one_class at line 6 # spent 368µs making 1 call to Hailo::BEGIN@6.10
# spent 357µs making 1 call to Any::Moose::import |
| 7 | 2 | 27µs | 2 | 115µs | # spent 63µs (12+52) within Hailo::BEGIN@7 which was called:
# once (12µs+52µs) by Mouse::Util::_try_load_one_class at line 7 # spent 63µs making 1 call to Hailo::BEGIN@7
# spent 51µs making 1 call to Exporter::import |
| 8 | 2 | 29µs | 2 | 95µs | # spent 53µs (12+42) within Hailo::BEGIN@8 which was called:
# once (12µs+42µs) by Mouse::Util::_try_load_one_class at line 8 # spent 53µs making 1 call to Hailo::BEGIN@8
# spent 42µs making 1 call to Exporter::import |
| 9 | 2 | 45µs | 2 | 2.93ms | # spent 1.47ms (12µs+1.46) within Hailo::BEGIN@9 which was called:
# once (12µs+1.46ms) by Mouse::Util::_try_load_one_class at line 9 # spent 1.47ms making 1 call to Hailo::BEGIN@9
# spent 1.46ms making 1 call to namespace::clean::import |
| 10 | |||||
| 11 | 1 | 70µs | # spent 84µs (14+70) within Hailo::BEGIN@11 which was called:
# once (14µs+70µs) by Mouse::Util::_try_load_one_class at line 19 # spent 70µs making 1 call to constant::import | ||
| 12 | Hailo::Engine::Default | ||||
| 13 | Hailo::Storage::MySQL | ||||
| 14 | Hailo::Storage::PostgreSQL | ||||
| 15 | Hailo::Storage::SQLite | ||||
| 16 | Hailo::Tokenizer::Chars | ||||
| 17 | Hailo::Tokenizer::Words | ||||
| 18 | Hailo::UI::ReadLine | ||||
| 19 | 2 | 346µs | 1 | 84µs | ] ]; # spent 84µs making 1 call to Hailo::BEGIN@11 |
| 20 | |||||
| 21 | 1 | 5µs | 1 | 351µs | has brain => ( # spent 351µs making 1 call to Mouse::has |
| 22 | isa => 'Str', | ||||
| 23 | is => 'rw', | ||||
| 24 | ); | ||||
| 25 | |||||
| 26 | has order => ( | ||||
| 27 | isa => 'Int', | ||||
| 28 | is => 'rw', | ||||
| 29 | default => 2, | ||||
| 30 | trigger => sub { | ||||
| 31 | my ($self, $order) = @_; | ||||
| 32 | $self->_custom_order(1); | ||||
| 33 | }, | ||||
| 34 | 1 | 7µs | 1 | 269µs | ); # spent 269µs making 1 call to Mouse::has |
| 35 | |||||
| 36 | 1 | 4µs | 1 | 263µs | has _custom_order => ( # spent 263µs making 1 call to Mouse::has |
| 37 | isa => 'Bool', | ||||
| 38 | is => 'rw', | ||||
| 39 | default => 0, | ||||
| 40 | init_arg => undef, | ||||
| 41 | documentation => "Here so we can differentiate between the default value of order being explictly set and being set by default", | ||||
| 42 | ); | ||||
| 43 | |||||
| 44 | 1 | 4µs | 1 | 262µs | has _custom_tokenizer_class => ( # spent 262µs making 1 call to Mouse::has |
| 45 | isa => 'Bool', | ||||
| 46 | is => 'rw', | ||||
| 47 | default => 0, | ||||
| 48 | init_arg => undef, | ||||
| 49 | documentation => "Here so we can differentiate between the default value of tokenizer_class being explictly set and being set by default", | ||||
| 50 | ); | ||||
| 51 | |||||
| 52 | 1 | 3µs | 1 | 272µs | has save_on_exit => ( # spent 272µs making 1 call to Mouse::has |
| 53 | isa => 'Bool', | ||||
| 54 | is => 'rw', | ||||
| 55 | default => 1, | ||||
| 56 | ); | ||||
| 57 | |||||
| 58 | has brain_resource => ( | ||||
| 59 | documentation => "Alias for `brain' for backwards compatibility", | ||||
| 60 | isa => 'Str', | ||||
| 61 | is => 'rw', | ||||
| 62 | trigger => sub { | ||||
| 63 | my ($self, $brain) = @_; | ||||
| 64 | $self->brain($brain); | ||||
| 65 | }, | ||||
| 66 | 1 | 6µs | 1 | 270µs | ); # spent 270µs making 1 call to Mouse::has |
| 67 | |||||
| 68 | 1 | 10µs | my %has = ( | ||
| 69 | engine => { | ||||
| 70 | name => 'Engine', | ||||
| 71 | default => 'Default', | ||||
| 72 | }, | ||||
| 73 | storage => { | ||||
| 74 | name => 'Storage', | ||||
| 75 | default => 'SQLite', | ||||
| 76 | }, | ||||
| 77 | tokenizer => { | ||||
| 78 | name => 'Tokenizer', | ||||
| 79 | default => 'Words', | ||||
| 80 | }, | ||||
| 81 | ui => { | ||||
| 82 | name => 'UI', | ||||
| 83 | default => 'ReadLine', | ||||
| 84 | }, | ||||
| 85 | ); | ||||
| 86 | |||||
| 87 | 1 | 13µs | for my $k (keys %has) { | ||
| 88 | 4 | 7µs | my $name = $has{$k}->{name}; | ||
| 89 | 4 | 5µs | my $default = $has{$k}->{default}; | ||
| 90 | 4 | 6µs | my $method_class = "${k}_class"; | ||
| 91 | 4 | 5µs | my $method_args = "${k}_args"; | ||
| 92 | |||||
| 93 | # working classes | ||||
| 94 | has "${k}_class" => ( | ||||
| 95 | isa => 'Str', | ||||
| 96 | is => "rw", | ||||
| 97 | default => $default, | ||||
| 98 | ($k ~~ 'tokenizer' | ||||
| 99 | ? (trigger => sub { | ||||
| 100 | my ($self, $class) = @_; | ||||
| 101 | $self->_custom_tokenizer_class(1); | ||||
| 102 | }) | ||||
| 103 | 4 | 30µs | 4 | 1.09ms | : ()) # spent 1.09ms making 4 calls to Mouse::has, avg 273µs/call |
| 104 | ); | ||||
| 105 | |||||
| 106 | # Object arguments | ||||
| 107 | has "${k}_args" => ( | ||||
| 108 | documentation => "Arguments for the $name class", | ||||
| 109 | isa => 'HashRef', | ||||
| 110 | is => "ro", | ||||
| 111 | 4 | 15µs | # spent 13µs within Hailo::__ANON__[lib/Hailo.pm:111] which was called 4 times, avg 3µs/call:
# 4 times (13µs+0s) by Mouse::Object::new at line 78 of MouseX/Getopt/Basic.pm, avg 3µs/call | ||
| 112 | 4 | 23µs | 4 | 1.08ms | ); # spent 1.08ms making 4 calls to Mouse::has, avg 271µs/call |
| 113 | |||||
| 114 | # Working objects | ||||
| 115 | 4 | 17µs | 4 | 2.13ms | has "_${k}" => ( # spent 2.13ms making 4 calls to Mouse::has, avg 534µs/call |
| 116 | does => "Hailo::Role::$name", | ||||
| 117 | lazy_build => 1, | ||||
| 118 | is => 'ro', | ||||
| 119 | init_arg => undef, | ||||
| 120 | ); | ||||
| 121 | |||||
| 122 | # Generate the object itself | ||||
| 123 | 2 | 325µs | 2 | 64µs | # spent 39µs (15+24) within Hailo::BEGIN@123 which was called:
# once (15µs+24µs) by Mouse::Util::_try_load_one_class at line 123 # spent 39µs making 1 call to Hailo::BEGIN@123
# spent 24µs making 1 call to strict::unimport |
| 124 | # spent 109ms (164µs+108) within Hailo::__ANON__[lib/Hailo.pm:170] which was called 3 times, avg 36.2ms/call:
# once (78µs+47.1ms) by Hailo::_storage at line 221 of lib/Hailo/Command.pm
# once (31µs+46.5ms) by Hailo::_tokenizer at line 288
# once (55µs+14.8ms) by Hailo::_engine at line 286 | ||||
| 125 | 13 | 187µs | my ($self) = @_; | ||
| 126 | |||||
| 127 | my $obj = $self->_new_class( | ||||
| 128 | $name, | ||||
| 129 | $self->$method_class, | ||||
| 130 | { | ||||
| 131 | arguments => $self->$method_args, | ||||
| 132 | ($k ~~ [ qw< engine storage > ] | ||||
| 133 | ? (order => $self->order) | ||||
| 134 | : ()), | ||||
| 135 | ($k ~~ [ qw< engine > ] | ||||
| 136 | ? (storage => $self->_storage) | ||||
| 137 | : ()), | ||||
| 138 | (($k ~~ [ qw< storage > ] and defined $self->brain) | ||||
| 139 | ? ( | ||||
| 140 | 15 | 108ms | hailo => do { # spent 108ms making 3 calls to Hailo::_new_class, avg 36.1ms/call
# spent 3µs making 2 calls to Hailo::Command::order, avg 2µs/call
# spent 3µs making 2 calls to Hailo::Command::brain, avg 2µs/call
# spent 3µs making 2 calls to Hailo::Command::tokenizer_class, avg 2µs/call
# spent 2µs making 1 call to Hailo::engine_args
# spent 2µs making 1 call to Hailo::Command::engine_class
# spent 2µs making 1 call to Hailo::_storage
# spent 2µs making 1 call to Hailo::storage_args
# spent 2µs making 1 call to Hailo::tokenizer_args
# spent 1µs making 1 call to Hailo::Command::storage_class | ||
| 141 | require Scalar::Util; | ||||
| 142 | 1 | 2µs | Scalar::Util::weaken(my $s = $self); # spent 2µs making 1 call to Scalar::Util::weaken | ||
| 143 | |||||
| 144 | my %callback = ( | ||||
| 145 | has_custom_order => sub { $s->_custom_order }, | ||||
| 146 | has_custom_tokenizer_class => sub { $s->_custom_tokenizer_class }, | ||||
| 147 | set_order => sub { | ||||
| 148 | my ($db_order) = @_; | ||||
| 149 | $s->order($db_order); | ||||
| 150 | $s->_engine->order($db_order); | ||||
| 151 | }, | ||||
| 152 | set_tokenizer_class => sub { | ||||
| 153 | my ($db_tokenizer_class) = @_; | ||||
| 154 | $s->tokenizer_class($db_tokenizer_class); | ||||
| 155 | }, | ||||
| 156 | ); | ||||
| 157 | |||||
| 158 | \%callback; | ||||
| 159 | }, | ||||
| 160 | brain => $self->brain | ||||
| 161 | ) | ||||
| 162 | : ()), | ||||
| 163 | (($k ~~ [ qw< storage > ] | ||||
| 164 | ? (tokenizer_class => $self->tokenizer_class) | ||||
| 165 | : ())) | ||||
| 166 | }, | ||||
| 167 | ); | ||||
| 168 | |||||
| 169 | return $obj; | ||||
| 170 | 4 | 34µs | }; | ||
| 171 | } | ||||
| 172 | |||||
| 173 | # spent 108ms (4.29+104) within Hailo::_new_class which was called 3 times, avg 36.1ms/call:
# 3 times (4.29ms+104ms) by Hailo::__ANON__[lib/Hailo.pm:170] at line 140, avg 36.1ms/call | ||||
| 174 | 30 | 303µs | my ($self, $type, $class, $args) = @_; | ||
| 175 | |||||
| 176 | my $pkg; | ||||
| 177 | 3 | 6µs | if ($class =~ m[^\+(?<custom_plugin>.+)$]) { # spent 6µs making 3 calls to Hailo::CORE:match, avg 2µs/call | ||
| 178 | 2 | 1.19ms | 1 | 306µs | # spent 306µs (287+20) within Hailo::BEGIN@178 which was called:
# once (287µs+20µs) by Mouse::Util::_try_load_one_class at line 178 # spent 306µs making 1 call to Hailo::BEGIN@178 |
| 179 | } else { | ||||
| 180 | 3 | 5µs | my @plugins = @{ $self->PLUGINS }; # spent 5µs making 3 calls to Hailo::PLUGINS, avg 2µs/call | ||
| 181 | # Be fuzzy about includes, e.g. DBD::SQLite or SQLite or sqlite will go | ||||
| 182 | 13 | 176µs | 26 | 78µs | $pkg = first { / $type : .* : $class /ix } # spent 53µs making 13 calls to Hailo::CORE:regcomp, avg 4µs/call
# spent 25µs making 13 calls to Hailo::CORE:match, avg 2µs/call |
| 183 | 6 | 193µs | sort { length $a <=> length $b } # spent 172µs making 3 calls to List::Util::first, avg 57µs/call
# spent 21µs making 3 calls to Hailo::CORE:sort, avg 7µs/call | ||
| 184 | @plugins; | ||||
| 185 | |||||
| 186 | unless ($pkg) { | ||||
| 187 | local $" = ', '; | ||||
| 188 | my @p = grep { /$type/ } @plugins; | ||||
| 189 | die "Couldn't find a class name matching '$class' in plugins '@p'"; | ||||
| 190 | } | ||||
| 191 | } | ||||
| 192 | |||||
| 193 | 3 | 15µs | if (Any::Moose::moose_is_preferred()) { # spent 15µs making 3 calls to Any::Moose::moose_is_preferred, avg 5µs/call | ||
| 194 | require Class::MOP; | ||||
| 195 | eval { Class::MOP::load_class($pkg) }; | ||||
| 196 | } else { | ||||
| 197 | eval qq[require $pkg]; # spent 100µs executing statements in string eval
# spent 80µs executing statements in string eval
# spent 69µs executing statements in string eval | ||||
| 198 | } | ||||
| 199 | die $@ if $@; | ||||
| 200 | |||||
| 201 | 1 | 204µs | 30 | 1.06ms | return $pkg->new(%$args); # spent 621µs making 3 calls to Mouse::Object::new, avg 207µs/call
# spent 292µs making 3 calls to Mouse::Meta::Class::_calculate_all_attributes, avg 97µs/call
# spent 85µs making 1 call to Hailo::Engine::Default::BUILD
# spent 25µs making 1 call to Hailo::Role::Tokenizer::BUILD
# spent 15µs making 10 calls to Mouse::Meta::TypeConstraint::_compiled_type_constraint, avg 2µs/call
# spent 7µs making 3 calls to Mouse::Meta::Class::strict_constructor, avg 2µs/call
# spent 5µs making 1 call to Hailo::Role::Tokenizer::__ANON__[lib/Hailo/Role/Tokenizer.pm:15]
# spent 5µs making 3 calls to Mouse::Meta::Class::is_immutable, avg 2µs/call
# spent 4µs making 3 calls to Mouse::Meta::Class::is_anon_class, avg 1µs/call
# spent 3µs making 2 calls to Mouse::Meta::Attribute::default, avg 2µs/call |
| 202 | } | ||||
| 203 | |||||
| 204 | # spent 71µs (18+53) within Hailo::save which was called:
# once (18µs+53µs) by Hailo::DEMOLISH at line 334 | ||||
| 205 | 3 | 17µs | my ($self, @args) = @_; | ||
| 206 | 2 | 53µs | $self->_storage->save(@args); # spent 51µs making 1 call to Hailo::Storage::SQLite::save
# spent 2µs making 1 call to Hailo::_storage | ||
| 207 | return; | ||||
| 208 | } | ||||
| 209 | |||||
| 210 | # spent 51.9s (87µs+51.9) within Hailo::train which was called:
# once (87µs+51.9s) by Hailo::Command::run at line 260 of lib/Hailo/Command.pm | ||||
| 211 | 10 | 77µs | my ($self, $input) = @_; | ||
| 212 | |||||
| 213 | 2 | 53.0ms | $self->_storage->start_training(); # spent 53.0ms making 1 call to Hailo::Storage::SQLite::start_training
# spent 1µs making 1 call to Hailo::_storage | ||
| 214 | |||||
| 215 | given ($input) { | ||||
| 216 | # With STDIN | ||||
| 217 | when (not ref and defined and $_ eq '-') { | ||||
| 218 | die "You must provide STDIN when training from '-'" if $self->_is_interactive(*STDIN); | ||||
| 219 | $self->_train_fh(*STDIN); | ||||
| 220 | } | ||||
| 221 | # With a filehandle | ||||
| 222 | when (ref eq 'GLOB') { | ||||
| 223 | $self->_train_fh($input); | ||||
| 224 | } | ||||
| 225 | # With a file | ||||
| 226 | when (not ref) { | ||||
| 227 | 1 | 29µs | open my $fh, '<:encoding(utf8)', $input; # spent 29µs making 1 call to Hailo::__ANON__[(eval 30)[Fatal.pm:1188]:47] | ||
| 228 | 1 | 51.8s | $self->_train_fh($fh, $input); # spent 51.8s making 1 call to Hailo::Command::_train_fh | ||
| 229 | } | ||||
| 230 | # With an Array | ||||
| 231 | when (ref eq 'ARRAY') { | ||||
| 232 | $self->_learn_one($_) for @$input; | ||||
| 233 | } | ||||
| 234 | # With something naughty | ||||
| 235 | default { | ||||
| 236 | die "Unknown input: $input"; | ||||
| 237 | } | ||||
| 238 | } | ||||
| 239 | |||||
| 240 | 2 | 4.47ms | $self->_storage->stop_training(); # spent 4.47ms making 1 call to Hailo::Storage::SQLite::stop_training
# spent 3µs making 1 call to Hailo::_storage | ||
| 241 | |||||
| 242 | return; | ||||
| 243 | } | ||||
| 244 | |||||
| 245 | sub _train_fh { | ||||
| 246 | my ($self, $fh, $filename) = @_; | ||||
| 247 | |||||
| 248 | while (my $line = <$fh>) { | ||||
| 249 | chomp $line; | ||||
| 250 | $self->_learn_one($line); | ||||
| 251 | } | ||||
| 252 | |||||
| 253 | return; | ||||
| 254 | } | ||||
| 255 | |||||
| 256 | sub learn { | ||||
| 257 | my ($self, $input) = @_; | ||||
| 258 | my $inputs; | ||||
| 259 | |||||
| 260 | given ($input) { | ||||
| 261 | when (not defined) { | ||||
| 262 | die "Cannot learn from undef input"; | ||||
| 263 | } | ||||
| 264 | when (not ref) { | ||||
| 265 | $inputs = [$input]; | ||||
| 266 | } | ||||
| 267 | # With an Array | ||||
| 268 | when (ref eq 'ARRAY') { | ||||
| 269 | $inputs = $input | ||||
| 270 | } | ||||
| 271 | default { | ||||
| 272 | die "Unknown input: $input"; | ||||
| 273 | } | ||||
| 274 | } | ||||
| 275 | |||||
| 276 | my $storage = $self->_storage; | ||||
| 277 | |||||
| 278 | $storage->start_learning(); | ||||
| 279 | $self->_learn_one($_) for @$inputs; | ||||
| 280 | $storage->stop_learning(); | ||||
| 281 | return; | ||||
| 282 | } | ||||
| 283 | |||||
| 284 | # spent 50.4s (542ms+49.9) within Hailo::_learn_one which was called 25000 times, avg 2.02ms/call:
# 25000 times (542ms+49.9s) by Hailo::Command::train_progress at line 334 of lib/Hailo/Command.pm, avg 2.02ms/call | ||||
| 285 | 125000 | 595ms | my ($self, $input) = @_; | ||
| 286 | 1 | 19µs | 25004 | 69.7ms | my $engine = $self->_engine; # spent 54.7ms making 25000 calls to Hailo::_engine, avg 2µs/call
# spent 14.9ms making 1 call to Hailo::__ANON__[lib/Hailo.pm:170]
# spent 119µs making 1 call to Mouse::Util::TypeConstraints::__ANON__[Mouse/Util/TypeConstraints.pm:217]
# spent 2µs making 1 call to Mouse::Meta::Attribute::builder
# spent 2µs making 1 call to Mouse::Meta::TypeConstraint::_compiled_type_constraint |
| 287 | |||||
| 288 | 1 | 22µs | 50004 | 11.5s | my $tokens = $self->_tokenizer->make_tokens($input); # spent 11.4s making 25000 calls to Hailo::Tokenizer::Words::make_tokens, avg 454µs/call
# spent 82.6ms making 25000 calls to Hailo::_tokenizer, avg 3µs/call
# spent 46.5ms making 1 call to Hailo::__ANON__[lib/Hailo.pm:170]
# spent 119µs making 1 call to Mouse::Util::TypeConstraints::__ANON__[Mouse/Util/TypeConstraints.pm:217]
# spent 2µs making 1 call to Mouse::Meta::TypeConstraint::_compiled_type_constraint
# spent 2µs making 1 call to Mouse::Meta::Attribute::builder |
| 289 | 25000 | 38.4s | $engine->learn($tokens); # spent 38.4s making 25000 calls to Hailo::Engine::Default::learn, avg 1.54ms/call | ||
| 290 | |||||
| 291 | return; | ||||
| 292 | } | ||||
| 293 | |||||
| 294 | sub learn_reply { | ||||
| 295 | my ($self, $input) = @_; | ||||
| 296 | $self->learn($input); | ||||
| 297 | return $self->reply($input); | ||||
| 298 | } | ||||
| 299 | |||||
| 300 | sub reply { | ||||
| 301 | my ($self, $input) = @_; | ||||
| 302 | |||||
| 303 | my $storage = $self->_storage; | ||||
| 304 | # start_training() hasn't been called so we can't guarentee that | ||||
| 305 | # the storage has been engaged at this point. This must be called | ||||
| 306 | # before ->_engine() is called anywhere to ensure that the | ||||
| 307 | # lazy-loading in the engine works. | ||||
| 308 | $storage->_engage() unless $storage->_engaged; | ||||
| 309 | |||||
| 310 | my $engine = $self->_engine; | ||||
| 311 | my $tokenizer = $self->_tokenizer; | ||||
| 312 | |||||
| 313 | my $reply; | ||||
| 314 | if (defined $input) { | ||||
| 315 | my $tokens = $tokenizer->make_tokens($input); | ||||
| 316 | $reply = $engine->reply($tokens); | ||||
| 317 | } | ||||
| 318 | else { | ||||
| 319 | $reply = $engine->reply(); | ||||
| 320 | } | ||||
| 321 | |||||
| 322 | return unless defined $reply; | ||||
| 323 | return $tokenizer->make_output($reply); | ||||
| 324 | } | ||||
| 325 | |||||
| 326 | sub stats { | ||||
| 327 | my ($self) = @_; | ||||
| 328 | |||||
| 329 | return $self->_storage->totals(); | ||||
| 330 | } | ||||
| 331 | |||||
| 332 | # spent 103µs (26+77) within Hailo::DEMOLISH which was called:
# once (26µs+77µs) by Mouse::Object::DESTROY at line 0 of bin/hailo | ||||
| 333 | 3 | 31µs | my ($self) = @_; | ||
| 334 | 3 | 77µs | $self->save() if blessed $self->{_storage} and $self->save_on_exit; # spent 71µs making 1 call to Hailo::save
# spent 4µs making 1 call to Scalar::Util::blessed
# spent 2µs making 1 call to Hailo::Command::save_on_exit | ||
| 335 | return; | ||||
| 336 | } | ||||
| 337 | |||||
| 338 | # spent 6.71ms (756µs+5.95) within Hailo::_is_interactive which was called 3 times, avg 2.24ms/call:
# once (738µs+5.84ms) by Hailo::Command::__ANON__[lib/Hailo/Command.pm:59] at line 58 of lib/Hailo/Command.pm
# once (9µs+59µs) by Hailo::Command::__ANON__[lib/Hailo/Command.pm:298] at line 293 of lib/Hailo/Command.pm
# once (10µs+57µs) by Hailo::Command::run at line 249 of lib/Hailo/Command.pm | ||||
| 339 | 6 | 197µs | require IO::Interactive; | ||
| 340 | 3 | 184µs | return IO::Interactive::is_interactive(); # spent 184µs making 3 calls to IO::Interactive::is_interactive, avg 61µs/call | ||
| 341 | } | ||||
| 342 | |||||
| 343 | 1 | 35µs | 2 | 158µs | __PACKAGE__->meta->make_immutable; # spent 142µs making 1 call to Mouse::Meta::Class::make_immutable
# spent 15µs making 1 call to Hailo::meta |
| 344 | |||||
| 345 | =encoding utf8 | ||||
| 346 | |||||
| 347 | =head1 NAME | ||||
| 348 | |||||
| 349 | Hailo - A pluggable Markov engine analogous to MegaHAL | ||||
| 350 | |||||
| 351 | =head1 SYNOPSIS | ||||
| 352 | |||||
| 353 | This is the synopsis for using Hailo as a module. See L<hailo> for | ||||
| 354 | command-line invocation. | ||||
| 355 | |||||
| 356 | # Hailo requires Perl 5.10 | ||||
| 357 | use 5.010; | ||||
| 358 | use Any::Moose; | ||||
| 359 | use Hailo; | ||||
| 360 | |||||
| 361 | # Construct a new in-memory Hailo using the SQLite backend. See | ||||
| 362 | # backend documentation for other options. | ||||
| 363 | my $hailo = Hailo->new; | ||||
| 364 | |||||
| 365 | # Various ways to learn | ||||
| 366 | my @train_this = ("I like big butts", "and I can not lie"); | ||||
| 367 | $hailo->learn(\@train_this); | ||||
| 368 | $hailo->learn($_) for @train_this; | ||||
| 369 | |||||
| 370 | # Heavy-duty training interface. Backends may drop some safety | ||||
| 371 | # features like journals or synchronous IO to train faster using | ||||
| 372 | # this mode. | ||||
| 373 | $hailo->train("megahal.trn"); | ||||
| 374 | $hailo->train($filehandle); | ||||
| 375 | |||||
| 376 | # Make the brain babble | ||||
| 377 | say $hailo->reply("hello good sir."); | ||||
| 378 | # Just say something at random | ||||
| 379 | say $hailo->reply(); | ||||
| 380 | |||||
| 381 | =head1 DESCRIPTION | ||||
| 382 | |||||
| 383 | Hailo is a fast and lightweight markov engine intended to replace | ||||
| 384 | L<AI::MegaHAL|AI::MegaHAL>. It has a L<Mouse|Mouse> (or | ||||
| 385 | L<Moose|Moose>) based core with pluggable | ||||
| 386 | L<storage|Hailo::Role::Storage>, L<tokenizer|Hailo::Role::Tokenizer> | ||||
| 387 | and L<engine|Hailo::Role::Engine> backends. | ||||
| 388 | |||||
| 389 | It is similar to MegaHAL in functionality, the main differences (with the | ||||
| 390 | default backends) being better scalability, drastically less memory usage, | ||||
| 391 | an improved tokenizer, and tidier output. | ||||
| 392 | |||||
| 393 | With this distribution, you can create, modify, and query Hailo brains. To | ||||
| 394 | use Hailo in event-driven POE applications, you can use the | ||||
| 395 | L<POE::Component::Hailo|POE::Component::Hailo> wrapper. One example is | ||||
| 396 | L<POE::Component::IRC::Plugin::Hailo|POE::Component::IRC::Plugin::Hailo>, | ||||
| 397 | which implements an IRC chat bot. | ||||
| 398 | |||||
| 399 | =head2 Etymology | ||||
| 400 | |||||
| 401 | I<Hailo> is a portmanteau of I<HAL> (as in MegaHAL) and | ||||
| 402 | L<failo|http://identi.ca/failo>. | ||||
| 403 | |||||
| 404 | =head1 Backends | ||||
| 405 | |||||
| 406 | Hailo supports pluggable L<storage|Hailo::Role::Storage> and | ||||
| 407 | L<tokenizer|Hailo::Role::Tokenizer> backends, it also supports a | ||||
| 408 | pluggable L<UI|Hailo::Role::UI> backend which is used by the L<hailo> | ||||
| 409 | command-line utility. | ||||
| 410 | |||||
| 411 | =head2 Storage | ||||
| 412 | |||||
| 413 | Hailo can currently store its data in either a | ||||
| 414 | L<SQLite|Hailo::Storage::SQLite>, | ||||
| 415 | L<PostgreSQL|Hailo::Storage::PostgreSQL> or | ||||
| 416 | L<MySQL|Hailo::Storage::MySQL> database. Some NoSQL backends were | ||||
| 417 | supported in earlier versions, but they were removed as they had no | ||||
| 418 | redeeming quality. | ||||
| 419 | |||||
| 420 | SQLite is the primary target for Hailo. It's much faster and uses less | ||||
| 421 | resources than the other two. It's highly recommended that you use it. | ||||
| 422 | |||||
| 423 | See L<Hailo::Storage/"Comparison of backends"> for benchmarks showing | ||||
| 424 | how the various backends compare under different workloads, and how | ||||
| 425 | you can create your own. | ||||
| 426 | |||||
| 427 | =head2 Tokenizer | ||||
| 428 | |||||
| 429 | By default Hailo will use L<the word | ||||
| 430 | tokenizer|Hailo::Tokenizer::Words> to split up input by whitespace, | ||||
| 431 | taking into account things like quotes, sentence terminators and more. | ||||
| 432 | |||||
| 433 | There's also a L<the character | ||||
| 434 | tokenizer|Hailo::Tokenizer::Chars>. It's not generally useful for a | ||||
| 435 | conversation bot but can be used to e.g. generate new words given a | ||||
| 436 | list of existing words. | ||||
| 437 | |||||
| 438 | =head1 UPGRADING | ||||
| 439 | |||||
| 440 | Hailo makes no promises about brains generated with earlier versions | ||||
| 441 | being compatable with future version and due to the way Hailo works | ||||
| 442 | there's no practical way to make that promise. Learning in Hailo is | ||||
| 443 | lossy so an accurate conversion is impossible. | ||||
| 444 | |||||
| 445 | If you're maintaining a Hailo brain that you want to keep using you | ||||
| 446 | should save the input you trained it on and re-train when you upgrade. | ||||
| 447 | |||||
| 448 | Hailo is always going to lose information present in the input you | ||||
| 449 | give it. How input tokens get split up and saved to the storage | ||||
| 450 | backend depends on the version of the tokenizer being used and how | ||||
| 451 | that input gets saved to the database. | ||||
| 452 | |||||
| 453 | For instance if an earlier version of Hailo tokenized C<"foo+bar"> | ||||
| 454 | simply as C<"foo+bar"> but a later version split that up into | ||||
| 455 | C<"foo", "+", "bar">, then an input of C<"foo+bar are my favorite | ||||
| 456 | metasyntactic variables"> wouldn't take into account the existing | ||||
| 457 | C<"foo+bar"> string in the database. | ||||
| 458 | |||||
| 459 | Tokenizer changes like this would cause the brains to accumulate | ||||
| 460 | garbage and would leave other parts in a state they wouldn't otherwise | ||||
| 461 | have gotten into. | ||||
| 462 | |||||
| 463 | There have been more drastic changes to the database format itself in | ||||
| 464 | the past. | ||||
| 465 | |||||
| 466 | Having said all that the database format and the tokenizer are | ||||
| 467 | relatively stable. At the time of writing 0.33 is the latest release | ||||
| 468 | and it's compatable with brains down to at least 0.17. If you're | ||||
| 469 | upgrading and there isn't a big notice about the storage format being | ||||
| 470 | incompatable in the F<Changes> file your old brains will probably work | ||||
| 471 | just fine. | ||||
| 472 | |||||
| 473 | =head1 ATTRIBUTES | ||||
| 474 | |||||
| 475 | =head2 C<brain> | ||||
| 476 | |||||
| 477 | The name of the brain (file name, database name) to use as storage. | ||||
| 478 | There is no default. Whether this gets used at all depends on the | ||||
| 479 | storage backend, currently only SQLite uses it. | ||||
| 480 | |||||
| 481 | =head2 C<save_on_exit> | ||||
| 482 | |||||
| 483 | A boolean value indicating whether Hailo should save its state before | ||||
| 484 | its object gets destroyed. This defaults to true and will simply call | ||||
| 485 | L<save|/save> at C<DEMOLISH> time. | ||||
| 486 | |||||
| 487 | See L<Hailo::Storage::SQLite/"in_memory"> for how the SQLite backend | ||||
| 488 | uses this option in conjunction with its C<in_memory> option. | ||||
| 489 | |||||
| 490 | =head2 C<order> | ||||
| 491 | |||||
| 492 | The Markov order (chain length) you want to use for an empty brain. | ||||
| 493 | The default is 2. | ||||
| 494 | |||||
| 495 | =head2 C<engine_class> | ||||
| 496 | |||||
| 497 | =head2 C<storage_class> | ||||
| 498 | |||||
| 499 | =head2 C<tokenizer_class> | ||||
| 500 | |||||
| 501 | =head2 C<ui_class> | ||||
| 502 | |||||
| 503 | A a short name name of the class we use for the engine, storage, | ||||
| 504 | tokenizer or ui backends. | ||||
| 505 | |||||
| 506 | By default this is B<Default> for the engine, B<SQLite> for storage, | ||||
| 507 | B<Words> for the tokenizer and B<ReadLine> for the UI. The UI backend | ||||
| 508 | is only used by the L<hailo> command-line interface. | ||||
| 509 | |||||
| 510 | You can only specify the short name of one of the packages Hailo | ||||
| 511 | itself ships with. If you need another class then just prefix the | ||||
| 512 | package with a plus (Catalyst style), e.g. C<+My::Foreign::Tokenizer>. | ||||
| 513 | |||||
| 514 | =head2 C<engine_args> | ||||
| 515 | |||||
| 516 | =head2 C<storage_args> | ||||
| 517 | |||||
| 518 | =head2 C<tokenizer_args> | ||||
| 519 | |||||
| 520 | =head2 C<ui_args> | ||||
| 521 | |||||
| 522 | A C<HashRef> of arguments for engine/storage/tokenizer/ui | ||||
| 523 | backends. See the documentation for the backends for what sort of | ||||
| 524 | arguments they accept. | ||||
| 525 | |||||
| 526 | =head1 METHODS | ||||
| 527 | |||||
| 528 | =head2 C<new> | ||||
| 529 | |||||
| 530 | This is the constructor. It accepts the attributes specified in | ||||
| 531 | L</ATTRIBUTES>. | ||||
| 532 | |||||
| 533 | =head2 C<learn> | ||||
| 534 | |||||
| 535 | Takes a string or an array reference of strings and learns from them. | ||||
| 536 | |||||
| 537 | =head2 C<train> | ||||
| 538 | |||||
| 539 | Takes a filename, filehandle or array reference and learns from all its | ||||
| 540 | lines. If a filename is passed, the file is assumed to be UTF-8 encoded. | ||||
| 541 | Unlike L<C<learn>|/learn>, this method sacrifices some safety (disables | ||||
| 542 | the database journal, fsyncs, etc) for speed while learning. | ||||
| 543 | |||||
| 544 | =head2 C<reply> | ||||
| 545 | |||||
| 546 | Takes an optional line of text and generates a reply that might be relevant. | ||||
| 547 | |||||
| 548 | =head2 C<learn_reply> | ||||
| 549 | |||||
| 550 | Takes a string argument, learns from it, and generates a reply that | ||||
| 551 | might be relevant. This is equivalent to calling L<learn|/learn> | ||||
| 552 | followed by L<reply|/reply>. | ||||
| 553 | |||||
| 554 | =head2 C<save> | ||||
| 555 | |||||
| 556 | Tells the underlying storage backend to L<save its | ||||
| 557 | state|Hailo::Role::Storage/"save">, any arguments to this method will | ||||
| 558 | be passed as-is to the backend. | ||||
| 559 | |||||
| 560 | =head2 C<stats> | ||||
| 561 | |||||
| 562 | Takes no arguments. Returns the number of tokens, expressions, previous | ||||
| 563 | token links and next token links. | ||||
| 564 | |||||
| 565 | =head1 SUPPORT | ||||
| 566 | |||||
| 567 | You can join the IRC channel I<#hailo> on FreeNode if you have questions. | ||||
| 568 | |||||
| 569 | =head1 BUGS | ||||
| 570 | |||||
| 571 | Bugs, feature requests and other issues are tracked in L<Hailo's RT on | ||||
| 572 | rt.cpan.org|https://rt.cpan.org/Dist/Display.html?Name=Hailo> | ||||
| 573 | |||||
| 574 | =head1 SEE ALSO | ||||
| 575 | |||||
| 576 | =over | ||||
| 577 | |||||
| 578 | =item * L<POE::Component::Hailo> - A non-blocking POE wrapper around Hailo | ||||
| 579 | |||||
| 580 | =item * L<POE::Component::IRC::Plugin::Hailo> - A Hailo IRC bot plugin | ||||
| 581 | |||||
| 582 | =item * L<http://github.com/hinrik/failo> - Failo, an IRC bot that uses Hailo | ||||
| 583 | |||||
| 584 | =item * L<http://github.com/bingos/gumbybrain> - GumbyBRAIN, a more famous IRC bot that uses Hailo | ||||
| 585 | |||||
| 586 | =item * L<Hailo::UI::Web> - A L<Catalyst> and jQuery powered web | ||||
| 587 | interface to Hailo available at L<hailo.nix.is|http://hailo.nix.is> | ||||
| 588 | and as L<hailo-ui-web|http://github.com/avar/hailo-ui-web> on | ||||
| 589 | L<GitHub|http://github.com> | ||||
| 590 | |||||
| 591 | =item * L<HALBot> - Another L<Catalyst> Dojo powered web interface to | ||||
| 592 | Hailo available at L<bifurcat.es|http://bifurcat.es/> and as | ||||
| 593 | L<halbot-on-the-web|http://gitorious.org/halbot-on-the-web/halbot-on-the-web> | ||||
| 594 | at L<gitorious|http://gitorious.org> | ||||
| 595 | |||||
| 596 | =item * L<http://github.com/pteichman/cobe> - cobe, a Python port of MegaHAL "inspired by the success of Hailo" | ||||
| 597 | |||||
| 598 | =back | ||||
| 599 | |||||
| 600 | =head1 LINKS | ||||
| 601 | |||||
| 602 | =over | ||||
| 603 | |||||
| 604 | =item * L<hailo.org|http://hailo.org> - Hailo's website | ||||
| 605 | |||||
| 606 | =item * L<http://bit.ly/hailo_rewrite_of_megahal> - Hailo: A Perl rewrite of | ||||
| 607 | MegaHAL, A blog posting about the motivation behind Hailo | ||||
| 608 | |||||
| 609 | =item * L<http://blogs.perl.org/users/aevar_arnfjor_bjarmason/hailo/> - | ||||
| 610 | More blog posts about Hailo on E<AElig>var ArnfjE<ouml>rE<eth> | ||||
| 611 | Bjarmason's L<blogs.perl.org|http://blogs.perl.org> blog | ||||
| 612 | |||||
| 613 | =item * Hailo on L<freshmeat|http://freshmeat.net/projects/hailo> and | ||||
| 614 | L<ohloh|https://www.ohloh.net/p/hailo> | ||||
| 615 | |||||
| 616 | =back | ||||
| 617 | |||||
| 618 | =head1 AUTHORS | ||||
| 619 | |||||
| 620 | Hinrik E<Ouml>rn SigurE<eth>sson, hinrik.sig@gmail.com | ||||
| 621 | |||||
| 622 | E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org> | ||||
| 623 | |||||
| 624 | =head1 LICENSE AND COPYRIGHT | ||||
| 625 | |||||
| 626 | Copyright 2010 Hinrik E<Ouml>rn SigurE<eth>sson and | ||||
| 627 | E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org> | ||||
| 628 | |||||
| 629 | This program is free software, you can redistribute it and/or modify | ||||
| 630 | it under the same terms as Perl itself. | ||||
| 631 | |||||
| 632 | 1 | 29µs | 1 | 4.62ms | =cut # spent 4.62ms making 1 call to B::Hooks::EndOfScope::__ANON__[B/Hooks/EndOfScope.pm:26] |
# spent 34µs within Hailo::CORE:match which was called 17 times, avg 2µs/call:
# 13 times (25µs+0s) by List::Util::first at line 182, avg 2µs/call
# 3 times (6µs+0s) by Hailo::_new_class at line 177, avg 2µs/call
# once (3µs+0s) by Hailo::__ANON__[(eval 30)[/home/hinrik/perl5/perlbrew/perls/perl-5.13.5/lib/5.13.5/Fatal.pm:1188]:47] at line 14 of (eval 30)[Fatal.pm:1188] | |||||
# spent 104µs (57+47) within Hailo::CORE:open which was called:
# once (57µs+47µs) by Hailo::__ANON__[(eval 29)[/home/hinrik/perl5/perlbrew/perls/perl-5.13.5/lib/5.13.5/Fatal.pm:1102]:127] at line 109 of (eval 29)[Fatal.pm:1102] | |||||
# spent 53µs within Hailo::CORE:regcomp which was called 13 times, avg 4µs/call:
# 13 times (53µs+0s) by List::Util::first at line 182, avg 4µs/call | |||||
# spent 21µs within Hailo::CORE:sort which was called 3 times, avg 7µs/call:
# 3 times (21µs+0s) by Hailo::_new_class at line 183, avg 7µs/call | |||||
# spent 5µs within Hailo::PLUGINS which was called 3 times, avg 2µs/call:
# 3 times (5µs+0s) by Hailo::_new_class at line 180, avg 2µs/call | |||||
# spent 54.7ms (39.7+15.0) within Hailo::_engine which was called 25000 times, avg 2µs/call:
# 25000 times (39.7ms+15.0ms) by Hailo::_learn_one at line 286, avg 2µs/call | |||||
# spent 47.3ms (38µs+47.3) within Hailo::_storage which was called 6 times, avg 7.88ms/call:
# once (29µs+47.3ms) by Hailo::Command::__ANON__[lib/Hailo/Command.pm:234] at line 221 of lib/Hailo/Command.pm
# once (3µs+0s) by Hailo::train at line 240
# once (2µs+0s) by Hailo::Command::run at line 249 of lib/Hailo/Command.pm
# once (2µs+0s) by Hailo::save at line 206
# once (2µs+0s) by Hailo::__ANON__[lib/Hailo.pm:170] at line 140
# once (1µs+0s) by Hailo::train at line 213 | |||||
# spent 82.6ms (36.0+46.7) within Hailo::_tokenizer which was called 25000 times, avg 3µs/call:
# 25000 times (36.0ms+46.7ms) by Hailo::_learn_one at line 288, avg 3µs/call | |||||
# spent 2µs within Hailo::engine_args which was called:
# once (2µs+0s) by Hailo::__ANON__[lib/Hailo.pm:170] at line 140 | |||||
# spent 2µs within Hailo::storage_args which was called:
# once (2µs+0s) by Hailo::__ANON__[lib/Hailo.pm:170] at line 140 | |||||
# spent 2µs within Hailo::tokenizer_args which was called:
# once (2µs+0s) by Hailo::__ANON__[lib/Hailo.pm:170] at line 140 |