Perl 5 Porters Mailing List Summary: November 13th-20th
Hey everyone,
Following is the p5p (Perl 5 Porters) mailing list summary for the past week.
Enjoy!
Hey everyone,
Following is the p5p (Perl 5 Porters) mailing list summary for the past week.
Enjoy!
This document is the May, 2017 progress report for TPF Standardization, Test Coverage, and Documentation of Perl 6 I/O Routines grant. I believe I reasonably satisfied the goals of the grant and consider it completed. This is the final report and may reference some of the work/commits previously mentioned in monthly reports.
I'd like to thank all the donors that support The Perl Foundation who made this grant possible. It was a wonderful learning experience for me, and it brings me joy to look back and see Perl 6 improved due to this grant.
Thank You!
Here are the original completeness criteria (in bold) that are listed on the original grant proposal and my comments on their status:
IO::Notification type (and IO::Path.watch method).
While it has full coverage for
OSX operating system, it lacks it for other OSes. I tried writing some tests for it, but
it looks like the behaviour of the nqp op handling these is broken
on Linux and the class needs more work.I produced these extra deliverables while working on the grant:
Trait::IO module.
Provides does auto-close pseudo-trait to simplify closing of IO handles.IO::Path::ChildSecure module. Due to
large ecosystem usage, IO::Path.child was left as is until 6.d language,
at which point it will be made secure (as outlined in the IO Plan). This
module provides the secure version in the mean time.IO::Dir module. Provides
IO::Path.dir-like functionality, with ability to close open directory
without needing to fully exhaust the returned Seq.Die module. Implements
Perl-5-like behaviour for &die routine.In addition, I plan to complete these modules some time in the future; the
ideas for them were birthed while working on the grant:
- NL module. Targeted for use in one liners, the module will provide
$*NL dynvar that behaves like Perl 5's $. variable (providing current
$*ARGFILES's file's line number). Its implementation became possible
thanks to newly-implemented
IO::CatHandle type
- FastIO module. A re-imagination of core IO, the biggest part of which
will be the removal of (user-exposed) use of IO::Spec::* types
and $*SPEC
variable, which—it is believed—will provide improved performance over
core IO. The module is a prototype for some of the proposals that were
made during the IO grant and if it offers significant improvements over
core IO, its ideas will be used by core IO in future language versions.
For the work done in May, many of my commits went into going through the IO routine list, and adding missing tests and documentation, along with fixing bugs (and reporting new ones I found).
The major work was implementation of the
IO::CatHandle class that fixed
all of the bugs and NYIs with the
$*ARGFILES. This work
saw the addition of 372 lines of code,
800 lines of tests and 793 lines of documentation.
jnthn++ completed the handle encoding refactor that will eventually let us get rid of using libuv for syncronous IO and, more importantly, allow us to support user-defined encoders/decoders.
Along with fixing a bunch of bugs, this work altered the performance landscape for IO operations (i.e. some operations may now be a bit faster, others a bit slower), though overall the performance appeared to stay the same.
$*HOME blows up if HOME isn't setbad .perl for paths with pipe characters (was already fixed; just added an extra test)During this grant, I've made 417 commits, that are: 134 Rakudo commits + 23 performance-enchancing Rakudo commits + 114 Perl 6 Specification commits + 146 documentation commits,
I've made 23 performance enchancing commits to Rakudo's repository:
Make IO::Handle.open 75% fasterMake IO::Spec::Unix.rel2abs 35% fasterIO::Path.slurp: make 12%-35% faster; propagate FailuresMake IO::Spec::Win32!canon-cat 2.3x fasterMake IO::Spec::Win32.is-absolute about 63x fasterMake IO::Spec::Win32.split about 82% fasterMake IO::Spec::Unix.rel2abs 2.9x fasterMake IO::Path.is-absolute about 80% fasterMake IO::Path.is-relative about 2.1x fasterMake IO::Spec::Unix.join about 40% fasterMake IO::Handle.put($x) about 5%-35% fasterMake &say(**@args) 70%− fasterMake &put(**@args) up to 70% fasterMake 1-arg IO::Handle.say up to 2x fasterRemove dir's :absolute and :Str; make up to 23% fasterMake IO::Spec::Cygwin.is-absolute 21x fasterFix combiners on SPEC::Win32.rel2abs; make 6% fasterMake IO::Spec::Unix.path consistent and 4.6x fasterFix IO::Spec::Win32.path and make 26x fasterMake IO::Spec::Win32.catpath 47x fasterMake IO::Spec::Win32.join 26x fasterMake IO::Spec::Unix.splitdir 7.7x fasterMake IO::Spec::Win32.splitdir 25x fasterOther than perf commits, I've also made 134 commits to the Rakudo's repository:
Fix crash in IO::Special .WHICH/.StrDo not cache IO::Path.e resultsRemove IO::Path.BridgeRemove IO::Path.pipeImprove IO::Path.child perf on *nixMake IO::Spec::Unix.split 36x FasterMake IO::Spec::Unix.catdir 3.9x FasterMake R::I::SET_LINE_ENDING_ON_HANDLE 4.1x FasterFix smartmatch of Cool ~~ IO::PathDo not capture args in .IO methodLog all changes to plan made during review periodRemoverole IOand its .umask methodRemove 15 methods from IO::HandleImplement IO::Handle.spurtRemove &tmpdir and &homedirImprove &chdir, &indir, and IO::Path.chdirFix race in &indir(IO::Path …)Fix regression in &chdir's failure modeImprove &*chdirAdd S32-io/chdir-process.t to list of test files to runClean up &open and IO::Path.openClean up and improve all spurt routinesGive $*TMPDIR a containerImplement IO::Path.extension 2.0Fix ambiguity in empty extension vs no extensionRestore IO::Handle.IOImplement IO::Path.concat-withClean up IO::Spec::Unix.abs2rel a bitRemove IO::Path.abspath (part 2)Fix return value of IO::Special methodsRun S32-io/io-special.t test fileMake IO::Path::* actually instantiate a subclassImplement :parent in IO::Spec::Cygwin.canonpathRemove type constraint in IO::Spec::Cygwin.canonpathDelete code for IO::Spec::Win32.catfileMake IO::Path throw when path contains NUL byteImplement :completely param in IO::Path.resolveRemove .f check in .zMake IO::Handle.Supply respect handle's modeImplement IO::Handle.slurpRework read methods in IO::Path/IO::HandleFix symlink and link routinesFix &symlink and &linkMake IO::Path.new-from-absolute-path privateStraighten up rename, move, and copyRemove multi-dir &mkdirCoerce mode in IO::Path.mkdir to IntAdd IO::Pipe .path and .IO methodsMake IO::Path.mkdir return invocant on successFix up IO::Handle.StrDo not use self.Str in IO::Path errorsSwap .child to .concat-with in all the gutsRevert "Removerole IOand its .umask method"Make IO::Path/IO::Special do IO roleImplement proper args for IO::Handle.lockMove Bool return value to signatureAmend rules for last part in IO::Path.resolveRewordmethod childfor cleaner codeImplement IO::Path.child-secureFix IO::Path.resolve with combiners; timotimo++Rename IO::Path.concat-with to .addRemove IO::Path.child-secureImplement IO::Path.siblingAdd :D on invocant for file testsFix $*CWD inside IO::Path.dir's :test CallableStraighten out &slurp/&spurt/&get/&getc/&closeStraighten out &lines/&wordsMake dir take any IO(), not just CoolMake $*HOME default to Nil, not AnyFix display of backslashes in IO::Path.gistRevert "Fix display of backslashes in IO::Path.gist"Fix .perl for IO::Path and subclassesFix .IO on :U of IO::Path subclassesMake IO::Handle.iterator a private lines iteratorFix IO::Path.copy/move when source/target are sameFix IO::Handle.comb/.split; make them .slurpMake IO::Handle.flush fail with typed exceptionsRemove .tell info in IO::Handle.gistFix IO::Spec::Unix.is-absolute for combiners on /Fix crash when setting .nl-in ...Make IO::Handle.encoding settable via .newMake IO::Handle.open respect attribute valuesRemove:directoryfrom IO::Spec::*.splitMake IO::Path.parts a Map instead of HashFix IO::Handle.perl.EVAL roundtrippageMake IO::Path.resolve set CWD to $!SPEC.dir-sepImplement $limit arg for IO::Handle.wordsMake IO::Handle.print/.put sig consistentAllow no-arg &promptImplement IO::CatHandle.closeImplement IO::CatHandle.getImplement IO::CatHandle.getcImplement IO::CatHandle.wordsImplement IO::CatHandle.slurpImplement IO::CatHandle.comb/.splitImplement IO::CatHandle.readImplement IO::CatHandle.readcharsImplement IO::CatHandle.SupplyImplement IO::CatHandle.encodingImplement IO::CatHandle.eofImplement IO::CatHandle.t/.path/.IO/.native-descriptorImplement IO::CatHandle.gist/.Str/.opened/.openImplement IO::CatHandle.lock/.unlock/.seek/.tellImplement IO::CatHandle.chomp/.nl-inImplement IO::CatHandle.on-switchSwap IO::ArgFiles to IO::CatHandle implImplement IO::CatHandle.perl methodRemove IO::Path.watchRevert "Remove IO::Path.watch"Remove useless :SPEC/:CWD on some IO subsThrow out IO::Path.intI've made 114 commits to the Perl 6 Specification (roast) repository:
Test IO::Special .WHICH/.Str do not crashTest IO::Path.lines(*) does not crashExpand &open testsCover IO::Path.ACCEPTSUse Numeric instead of IO role in dispatch testExpand IO::Spec::*.tmpdir testsTest &indirAmend &indir race testsTest &indir fails with non-existent paths by defaultRemove two fudged &chdir testsExpand &chdir testsTest &*chdirDelete qp{} testsTest IO::Path.Str works as advertisedMerge S32-io/path.t and S32-io/io-path.tExpand &spurt and IO::Path.spurt testsTest $*TMPDIR can betempedTest IO::Path.extensionFix incorrect testTest empty-string extensions in IO::Path.extensionTest IO::Path.concat-withExpand IO::Path.accessed testsCover methods of IO::SpecialTest IO::Path::* instantiate a subclassCover IO::Spec::Unix.basenameCover IO::Spec::Win32.basenameCover IO::Spec::QNX.canonpathCover :parent arg in IO::Spec::Cygwin.canonpathChange \0 roundtrip test to \t roundtrip testAdd tests to check nul byte is rejectedMove is-path sub to top so it can be reusedExpand IO::Path.resolve testsExpand file testsUse bin IO::Handle to test its .SupplySwap .slurp-rest to .slurpRewrite .l on broken symlinks testTest symlink routinesTestlinkroutinesSpec IO::Pipe.path/.IO returns IO::Path type objectCover IO::Path/IO::Pipe's .Str/.path/.IOTest IO::Handle.lock/.unlockAmend rules for last part in IO::Path.resolveTest IO::Path.child-secureTest IO::Path.child-secure with combinersIO::Path.concat-with got renamed to .addFudge .child-secure testsTest IO::Path.siblingTest $*CWD in IO::Path.dir(:test) CallableCover IO::Handle.spurtTest &words with IO::ArgFilesCover IO::Handle.tellAdd $*HOME testsTest IO::Path.gist does escapes of backslashesRevert "Test IO::Path.gist does escapes of backslashes"Test IO::Handle.close can be...Test IO::Pipe.close returns pipe's ProcTest IO::Handle.DESTROY closes the handleAdd test for .perl.EVAL roundtrip with combinersTest we can roundtrip IO::Path.perlTest .IO on :U of IO::Path subclassesTest for IO::Handle:D { ... } loops over handleTest IO::Path.copy/move when source/target are sameTest IO::Path.dir's absoluteness behaviourTest IO::Spec::Unix.extensionTest IO::Handle.flushTest IO::Handle.t when handle is a TTYTest IO::Path*.gistTest .is-absolute method for / with combinersTest IO::Spec::Win32.rel2abs with combinersTest IO::Handle.nl-in can be setTest IO::Handle.open respects attributesTest IO::Handle.nl-in attributeTest IO::Handle.encoding can be setTest no-arg candidate of ¬eTest IO::Path.parts attributeTest return type of IO::Spec::Unix.pathTest IO::Spec::Win32.pathTest IO::Handle.perl.EVAL roundtripsTest IO::Path.resolve sets CWD to $!SPEC.dir-sepTest &words, IO::Handle.words, and IO::Path.wordsTest $limit arg with &lines/IO::*.linesAdd test for handle leak in IO::Path.linesAdd &put/IO::Handle.put testsAdd &prompt testsTest IO::CatHandle.closeTest IO::CatHandle.getTest IO::CatHandle.getcTest IO::CatHandle.wordsAdd &put/IO::Handle.put testsAdd &prompt testsTest IO::CatHandle.slurpTest IO::CatHandle.comb/.splitTest IO::CatHandle.readTest IO::CatHandle.readcharsTest IO::CatHandle.SupplyTest IO::CatHandle.encodingTest IO::CatHandle.eofTest IO::CatHandle.t/.path/.IO/.native-descriptorTest IO::CatHandle.gist/.Str/.opened/.openTest IO::CatHandle.lock/.unlock/.seek/.tellTest IO::CatHandle.chomp/.nl-inTest IO::CatHandle.DESTROYTest IO::CatHandle.on-switchTest IO::CatHandle.next-handleTest IO::CatHandle.perl methodTest IO::Path.watchTest IO::Handle.sayTest IO::Handle.print-nlTest IO::Pipe.proc attributeTest IO::Path.SPEC attributeTest IO::Path.CWD/.path attributesTest IO::Path.Numeric and other .numeric methodsTest 0-arg &say/&put/&printTest &slurp() and &slurp(IO::Handle)I've made 146 commits to the Perl 6 Documentation repository:
Improve code exampleNo need for.ends-with``Remove IO::Handle.zRemove IO::Handle.rw and .rwxFix incorrect information for IO::Path.absoluteExpand IO::Path.relativeRemove mention of IO.umaskRemove mention ofrole IO``Remove 8 methods from IO::HandleDocument IO::Spec::*.tmpdirRemove tip to use $*SPEC to detect OSExpand docs for $*HOME and $*TMPDIRRemove IO::Path.chdir proseDocument &chdirDocument &*chdirReword "defined as" for &*chdirFix URL to &*chdirDocument &indirImprove suggestion for Perl 5's opendirClarify value of IO::Path.pathFix desc of IO::Path.StrInclude type names in links to methodsPoint out my $*CWD = chdir … is an errorWrite docs for all spurt routinesDocument new IO::Path.extensionDocument IO::Path.concat-withToss all of the TODO methods in IO::Spec*Document IO::Spec*.abs2relKill IO::Path.abspathDocument IO::Path.ACCEPTSExpand/fix up IO::Path.accessedFix up type graphMinor formatting improvements in IO::SpecialDocument IO::Special.whatDissuade readers from using IO::Spec*Remove unrelated related classesDocument IO::Path's $.SPEC and $.CWDDocument IO::Path::* subclassesFix up IO::Path.basenameDocument IO::Spec::Unix.basenameDocument IO::Spec::Win32.basenameDocument IO::Spec::*.canonpathDocument IO::Spec::*.catdir and .catfileDocument IO::Spec::*.catpathReword/expand IO::Path intro proseMove IO::Path.path to attributesRemove DateTime tutorial from IO::Path docsDocument IO::Path.chdirDocument IO::Spec::* don't do any validationImprove chmod docsDocument :completely arg to IO::Path.resolveStraighten up file test docsAvoid potential confusion with use of word "object"Document new behaviour of IO::Handle.SupplyDocument IO::Handle.slurpImprove docs for IO::Path.slurpList Rakudo-supported encodings in open()List utf-* alias examples too since they're commonUse idiomatic Perl 6 in exampleFix docs for symlink/link routinesStraighten up copy, move, renameStraighten up mkdir docsExplicitly spell out caveats of IO::Path.StrChange return value formkdir``Expand IO::Handle/IO::Pipe.path docsDocument IO::Pipe.pathDocument IO::Handle/IO::Pipe.IODocument IO::Handle.StrDocumentrole IO's new purposeDocument IO::Handle.lock/.unlockDocument IO::Path.child-secureRename IO::Path.concat-with to .addAmend IO::Path.resolve: :completelyStart sketching out Definitive IO Guide™Toss IO::Path.child-secureDocument IO::Path.siblingFix typegraphDocument IO::Path.cleanupRe-write IO::Handle.close docsAmend IO::Handle.close docsDocument IO::Spec::Unix.curupdirDocument IO::Spec::Unix.curdirDocument IO::Spec::Unix.updirDocument IO::Handle.DESTROYAdd warning to dir about...Document copy/move behaviour for same target/sourceDocument IO::Path/IO::Handle.combInclude exception used in IO::Path.resolveDocument IO::Spec::*.devnullList IO::Dir as one of the means...Finish up IO::Path.dir docsDocument IO::Spec::*.dir-sepFinish up IO::Path.dirnameDocument IO::Handle.encodingFinish off IO::Handle.eofDocument IO::Spec::*.extensionDocument IO::Handle.flushDocument IO::Path.succImprove IO::Handle.t docsBe explicit what IO::Handle.opened returnsDocument IO::Path.predRemove entirely-invented "File test operators"Document IO::Path.Numeric/.IntImprove IO::Handle.get docsFinish off IO::Handle.getc/&getc docsDocument IO::Handle.gistDocument IO::Path.gistDocument IO::Spec::*.is-absoluteFinish up IO::Path.is-absoluteFinish off IO::Path.is-relativeDocument IO::Handle.nl-inFinish up ¬eFinish off IO::Path.parentFinish off IO::Path.partsFinish off IO::Path.path/.IODocument IO::Spec::*.pathDocument IO::Path*.perlAdd "The Basics" section to TDIOGAdd "What's an IO::Path Anyway?" section to TDIOGAdd "Writing into files" Section to TDIOGDocument IO::Handle.words/&wordsDocument IO::Spec::*.joinDocument IO::Handle.linesDocument IO::Path.linesDocument IO::Path.wordsFix incorrect suggested routineFix up IO::Handle.printFix up IO::Handle.print-nlFix &promptFix up IO::Handle.splitFix up IO::Handle.combDocument IO::CatHandleDocument IO::Path.splitDocument IO::Spec::*.splitDocument IO::Spec::*.splitdirDocument IO::Spec::*.splitpathFix rmdir docsDocument IO::Spec::*.rel2absDocument IO::Spec::*.rootdirDocument IO::Handle.putPolish IO::Handle.sayPolish &put/&print/&sayDocument IO::Handle.nl-out attributeDocument IO::Handle.chomp attributeImprove &open/IO::Handle.open docsAdd Reading From Files section to TDIOGThis document is the April, 2017 progress report for TPF Standardization, Test Coverage, and Documentation of Perl 6 I/O Routines grant
As proposed to and approved by the Grant Manager, I've extended the due date for this grant by 1 extra month, in exchange for doing some extra optimization work on IO routines at no extra cost. The new completion date is May 22nd; right after the next Rakudo compiler release.
I've created and published three notices as part of this grant, informing the users on what is changing and how to best upgrade their code, where needed:
Most of the IO Action Plan has been implemented and got shipped in Rakudo's 2017.04.2 release. The remaining items are:
stat calls for multiple file tests (entirely internal that requires no changes to users' code)In my spreadsheet with all the IO routines and their candidates, the totals show that 40% have been documented and tested. Some of the remaining 60% may already have tests/docs added when implementing IO Action Plan or ealier and merely need checking and verification.
Some of the optimizations I promised to deliver in exchange for grant deadline extension were already done on IO::Spec::Unix and IO::Path routines and have made it into the 2017.04.2 release. Most of the optimizations that will be done in the upcoming month will be done in IO::Spec::Win32 and will largely affect Windows users.
The following tickets have been resolved as part of the grant:
:dPossibly more tickets were addressed by the IO Action Plan implementation, but they still need further review.
IO::Path.resolve with combiners tucked on the path
separator. Fix in
rakudo/9d8e391f3b;
tests in
roast/92217f75ce. The bug was identified by Timo Paulssen while testing secure implementation of IO::Path.childis native NativeCall trait rakudo/99840804temp $*TMPDIR variable rakudo/1b9d53So far, I've commited 192 IO grant commits to rakudo/roast/doc repos.
69 IO grant commits:
c6fd736 Make IO::Spec::Win32.is-absolute about 63x faster7112a08 Add :D on invocant for file tests8bacad8 Implement IO::Path.siblinga98b285 Remove IO::Path.child-secure0b5a41b Rename IO::Path.concat-with to .add9d8e391 Fix IO::Path.resolve with combiners; timotimo++1887114 Implement IO::Path.child-secureb8458d3 Reword method child for cleaner code51e4629 Amend rules for last part in IO::Path.resolve9a2446c Move Bool return value to signature214198b Implement proper args for IO::Handle.lockc95c4a7 Make IO::Path/IO::Special do IO rolefd503f8 grant] Remove role IO and its .umask method"0e36bb2 Make IO::Spec::Win32!canon-cat 2.3x faster40217ed Swap .child to .concat-with in all the guts490ffd1 Do not use self.Str in IO::Path errors1f689a9 Fix up IO::Handle.Strc01ebea Make IO::Path.mkdir return invocant on successd46e8df Add IO::Pipe .path and .IO methods6ee71c2 Coerce mode in IO::Path.mkdir to Int0d9ecae Remove multi-dir &mkdirff97083 Straighten up rename, move, and copy7f73f92 Make IO::Path.new-from-absolute-path privateda1dea2 Fix &symlink and &link8c09c84 Fix symlink and link routines90da80f Rework read methods in IO::Path/IO::Handlec13480c IO::Path.slurp: make 12%-35% faster; propagate Failuresf1b4af7 Implement IO::Handle.slurp184d499 Make IO::Handle.Supply respect handle's modeb6838ee Remove .f check in .z6a8d63d Implement :completely param in IO::Path.resolvee681498 Make IO::Path throw when path contains NUL byteb4358af Delete code for IO::Spec::Win32.catfile0a442ce Remove type constraint in IO::Spec::Cygwin.canonpath0c8bef5 Implement :parent in IO::Spec::Cygwin.canonpatha0b82ed Make IO::Path::* actually instantiate a subclass67f06b2 Run S32-io/io-special.t test file954e69e Fix return value of IO::Special methodsa432b3d Remove IO::Path.abspath (part 2)94a6909 Clean up IO::Spec::Unix.abs2rel a bit966a7e3 Implement IO::Path.concat-with50aea2b Restore IO::Handle.IO15a25da Fix ambiguity in empty extension vs no extensionb1e7a01 Implement IO::Path.extension 2.0b62d1a7 Give $*TMPDIR a container099512b Clean up and improve all spurt routinescb27bce Clean up &open and IO::Path.open4c31903 Add S32-io/chdir-process.t to list of test files to run5464b82 Improve &*chdir2483d68 Fix regression in &chdir's failure modeca1acb7 Fix race in &indir(IO::Path …)a0ef2ed Improve &chdir, &indir, and IO::Path.chdiraa62cd5 Remove &tmpdir and &homedira5800a1 Implement IO::Handle.spurt36ad92a Remove 15 methods from IO::Handle87987c2 Remove role IO and its .umask method9d8d7b2 Log all changes to plan made during review period0c7e4a0 Do not capture args in .IO methodc360ac2 Fix smartmatch of Cool ~~ IO::Pathfa9aa47 Make R::I::SET_LINE_ENDING_ON_HANDLE 4.1x Faster0111f10 Make IO::Spec::Unix.catdir 3.9x Faster4fdebc9 Make IO::Spec::Unix.split 36x Fasterdcf1bb2 Make IO::Spec::Unix.rel2abs 35% faster55abc6d Improve IO::Path.child perf on *nix4032953 Make IO::Handle.open 75% fastera01d679 Remove IO::Path.pipe212cc8a Remove IO::Path.Bridge76f7187 Do not cache IO::Path.e resultsdd4dfb1 Fix crash in IO::Special .WHICH/.Str47 IO grant commits:
3b36d4d Test IO::Path.sibling7a063b5 Fudge .child-secure tests39677c4 IO::Path.concat-with got renamed to .add92217f7 Test IO::Path.child-secure with combinersf3c5dae Test IO::Path.child-securea716962 Amend rules for last part in IO::Path.resolve4194755 Test IO::Handle.lock/.unlock64ff572 Cover IO::Path/IO::Pipe's .Str/.path/.IO637500d Spec IO::Pipe.path/.IO returns IO::Path type object8fa49e1 Test link routines416b746 Test symlink routinesd4353b6 Rewrite .l on broken symlinks test7e4a2ae Swap .slurp-rest to .slurpa4c53b0 Use bin IO::Handle to test its .Supplyfeecaf0 Expand file testsa809f0f Expand IO::Path.resolve testsee7f05b Move is-path sub to top so it can be reusedb16fbd3 Add tests to check nul byte is rejected8f73ad8 Change \0 roundtrip test to \t roundtrip test7c7fbb4 Cover :parent arg in IO::Spec::Cygwin.canonpath896033a Cover IO::Spec::QNX.canonpathc3c51ed Cover IO::Spec::Win32.basenamed8707e7 Cover IO::Spec::Unix.basenamebd8d167 Test IO::Path::* instantiate a subclass43ec543 Cover methods of IO::Speciale5dc376 Expand IO::Path.accessed tests0e47f25 Test IO::Path.concat-with305f206 Test empty-string extensions in IO::Path.extension2f09f18 Fix incorrect testb23e53e Test IO::Path.extension1d4e881 Test $*TMPDIR can be temped79ff022 Expand &spurt and IO::Path.spurt testsba3e7be Merge S32-io/path.t and S32-io/io-path.t3c4e81b Test IO::Path.Str works as advertised86c5f9c Delete qp{} tests430ab89 Test &*chdir86f79ce Expand &chdir tests73a5448 Remove two fudged &chdir tests04333b3 Test &indir fails with non-existent paths by defaultbd46836 Amend &indir race testsf48198f Test &indir5a7a365 Expand IO::Spec::*.tmpdir tests14b6844 Use Numeric instead of IO role in dispatch test8d6ca7a Cover IO::Path.ACCEPTS091931a Expand &open tests465795c Test IO::Path.lines(*) does not crash63370fe Test IO::Special .WHICH/.Str do not crash76 IO grant commits:
61cb776 Document IO::Path.siblingb9c9117 Toss IO::Path.child-secure6ca67e4 Start sketching out Definitive IO Guide™81a5806 Amend IO::Path.resolve: :completelyc5524ef Rename IO::Path.concat-with to .add3145979 Document IO::Path.child-secure160c6a2 Document IO::Handle.lock/.unlock53f2b99 Document role IO's new purpose2aaf12a Document IO::Handle.Strbd4fa68 Document IO::Handle/IO::Pipe.IOfd8a5ed Document IO::Pipe.path8d95371 Expand IO::Handle/IO::Pipe.path docs60b9227 Change return value for mkdir47b0526 Explicitly spell out caveats of IO::Path.Str923ea05 Straighten up mkdir docsaeeec94 Straighten up copy, move, renamefff866f Fix docs for symlink/link routinesf83f78c Use idiomatic Perl 6 in examplee60da5c List utf-* alias examples too since they're common0f49bb5 List Rakudo-supported encodings in open()017acd4 Improve docs for IO::Path.slurp56b50fe Document IO::Handle.slurp2aa3c9f Document new behaviour of IO::Handle.Supplya30fae6 Avoid potential confusion with use of word "object"372545c Straighten up file test docs1527d32 Document :completely arg to IO::Path.resolve4090446 Improve chmod docsd436f3c Document IO::Spec::* don't do any validation69b2082 Document IO::Path.chdirb9de84f Remove DateTime tutorial from IO::Path docs45e84ad Move IO::Path.path to attributes0ca2295 Reword/expand IO::Path intro prosedbdc995 Document IO::Spec::*.catpath50e5565 Document IO::Spec::*.catdir and .catfile28b6283 Document IO::Spec::*.canonpatha1cb80b Document IO::Spec::Win32.basename5c1d3b6 Document IO::Spec::Unix.basename9102b51 Fix up IO::Path.basenamee9b6809 Document IO::Path::* subclassesa43ecb9 Document IO::Path's $.SPEC and $.CWD7afd9c4 Remove unrelated related classes6bd0f98 Dissuade readers from using IO::Spec*184342c Document IO::Special.what56256d0 Minor formatting improvements in IO::Special1cd7de0 Fix up type graphb3a9324 Expand/fix up IO::Path.accessed1973010 Document IO::Path.ACCEPTScc62dd2 Kill IO::Path.abspath1f75ddc Document IO::Spec*.abs2rel24a6ea9 Toss all of the TODO methods in IO::Spec*65cc372 Document IO::Path.concat-withb9e692e Document new IO::Path.extensiond5abceb Write docs for all spurt routinesb8fba97 Point out my $*CWD = chdir … is an errorb78d4fd Include type names in links to methodsbdd18f1 Fix desc of IO::Path.Stra53015a Clarify value of IO::Path.path5aa614f Improve suggestion for Perl 5's opendirbf377c7 Document &indire5225be Fix URL to &*chdire1a299c Reword "defined as" for &*chdir3fdc6dc Document &*chdir1d0e433 Document &chdird050d4b Remove IO::Path.chdir prose839a6b3 Expand docs for $*HOME and $*TMPDIRdb36655 Remove tip to use $*SPEC to detect OS0511e07 Document IO::Spec::*.tmpdircc6539b Remove 8 methods from IO::Handle335a98d Remove mention of role IOcc496eb Remove mention of IO.umask3cf943d Expand IO::Path.relativeccae74a Fix incorrect information for IO::Path.absoluted02ae7d Remove IO::Handle.rw and .rwx69d32da Remove IO::Handle.z110efb4 No need for .ends-withfd7a41b Improve code exampleHey everyone,
Following is the p5p (Perl 5 Porters) mailing list summary for the past week. Enjoy!
Read this article on Perl6.Party
While testing a fix for one of the Less Than Awesome behaviours in standalone
Signature objects, I came across
a bugglet. Smartmatching two Signatures throws, while spilling a bit of the
guts:
<Zoffix> m: my $m = method ($a: $b) { }; say $m.signature ~~ :($a, $b);
<camelia> rakudo-moar 46838d: OUTPUT«Method 'type' not found for invocant of class 'Any' in blockat line 1»
So I figured I'll write about fixing it, 'cause hacking on internals is lots of fun. Let's roll!
The less code there is to reproduces the bug, the fewer places there are for that bug to hide. We have a detached method and then we smartmatch its signature against something else. Let's try to golf it down a bit and smartmatch two Signatures, without involving a method:
<Zoffix> m: :($a, $b) ~~ :($a, $b);
<camelia> rakudo-moar 46838d: ( no output )
The bug disappeared, so perhaps out Signature on the left doesn't contain the stuff that triggers the bug. Let's dump the signature of the method to see what we should match against:
<Zoffix> m: my $m = method ($a: $b) { }; say $m.signature <camelia> rakudo-moar 46838d: OUTPUT«($a: $b, *%_)»
Aha! It has a slurpy hash: *%_. Let's try matching a Signature with a
slurpy in it:
<Zoffix> m:
:(*%) ~~ :();
<camelia> rakudo-moar 46838d: OUTPUT«Method 'type' not found for invocant of class 'Any' in blockat line 1»
And there we go: hole in three. Let's proceed.
There's an official Perl 6 test suite that Rakudo must pass to be called a Perl 6 compiler. Since we got a bug on our hands, we should add a test for it to the test suite to ensure it doesn't rear its ugly head again.
The copy of the repo gets automatically cloned into t/spec when you
run make spectest in Rakudo's checkout.
If you don't have a commit bit, you can just change the remote/branch of that
checkout to your fork:
cd t/spec
git remote rm origin
git remote add origin https://github.com/YOURUSERNAME/roast
git checkout your-branch
cd ../..
It may be tricky to figure out which file to put the test in, if you're new.
You can always ask the good folks on
irc.freenode.net/#perl6
for advice. In this case, I'll place the test
into S06-signature/outside-subroutine.t
While not required, I find it helpful to open a ticket for the bug. This way I can reference it in my fix in the compiler repo, I can reference it in the commit to the test repo, and people get a place where to tell me why I'm being stupid when I am. I opened this bug as RT#128795.
Now, for the code of the test itself.
I'll adjust the plan at the top of the file to
include however many tests I'm writing—in this case one. I'll use the
lives-ok test sub and stick our buggy golfed code
into it. Here's the diff of the changes to the file; note the reference
to the ticket number in the comment before the test:
@@ -1,7 +1,7 @@
use v6;
use Test;
-plan 3;
+plan 4;
# RT #82946
subtest 'signature binding outside of routine calls' => {
@@ -25,4 +25,7 @@ subtest 'smartmatch on signatures with literal strings' => {
# RT #128783
lives-ok { EVAL ’:($:)‘ }, ’signature marker is allowed in bare signature‘;
+# RT #128795
+lives-ok { :(*%)~~ :() }, 'smartmatch with no slurpy on right side';
+
# vim: ft=perl6
Run the file now to ensure the test fails. Hint: some files have fudging; explaining it is out of the scope of this article, but if you notice failures you're not expecting, look it up.
$ make t/spec/S06-signature/outside-subroutine.t
...
Test Summary Report
-------------------
t/spec/S06-signature/outside-subroutine.t (Wstat: 256 Tests: 4 Failed: 1)
Failed test: 4
Non-zero exit status: 1
With the test in place, it's time to look at some source code. Let the bug hunt begin!
Our bug involves a Smartmatch operator, which aliases the left side to the topic
variable $_ and calls .ACCEPTS method on the right side with it. Both
of our sides are Signature objects, so let's pop open Rakudo's sauce code
for that class.
In the Rakudo's repo,
directory src/core/ contains most of the built in types in separate
files named after those types, so we'll just pop open
src/core/Signature.pm in the editor and locate the definition of method ACCEPTS.
There are actually four multis for ACCEPTS. Here's the full code. Don't
try to understand all of it, just note its size.
``` multi method ACCEPTS(Signature:D: Capture $topic) { nqp::p6bool(nqp::p6isbindable(self, nqp::decont($topic))); }
multi method ACCEPTS(Signature:D: @topic) {
self.ACCEPTS(@topic.Capture)
}
multi method ACCEPTS(Signature:D: %topic) {
self.ACCEPTS(%topic.Capture)
}
multi method ACCEPTS(Signature:D: Signature:D $topic) {
my $sclass = self.params.classify({.named});
my $tclass = $topic.params.classify({.named});
my @spos := $sclass{False} // ();
my @tpos := $tclass{False} // ();
while @spos {
my $s;
my $t;
last unless @tpos && ($t = @tpos.shift);
$s=@spos.shift;
if $s.slurpy or $s.capture {
@spos=();
@tpos=();
last;
}
if $t.slurpy or $t.capture {
return False unless any(@spos) ~~ {.slurpy or .capture};
@spos=();
@tpos=();
last;
}
if not $s.optional {
return False if $t.optional
}
return False unless $t ~~ $s;
}
return False if @tpos;
if @spos {
return False unless @spos[0].optional or @spos[0].slurpy or @spos[0].capture;
}
for flat ($sclass{True} // ()).grep({!.optional and !.slurpy}) -> $this {
my $other;
return False unless $other=($tclass{True} // ()).grep(
{!.optional and $_ ~~ $this });
return False unless +$other == 1;
}
my $here=($sclass{True}:v).SetHash;
my $hasslurpy=($sclass{True} // ()).grep({.slurpy});
$here{@$hasslurpy} :delete;
$hasslurpy .= Bool;
for flat @($tclass{True} // ()) -> $other {
my $this;
if $other.slurpy {
return False if any($here.keys) ~~ -> Any $_ { !(.type =:= Mu) };
return $hasslurpy;
}
if $this=$here.keys.grep( -> $t { $other ~~ $t }) {
$here{$this[0]} :delete;
}
else {
return False unless $hasslurpy;
}
}
return False unless self.returns =:= $topic.returns;
True;
}
```
The error we get from the bug mentions .type method call and there is one
such method call in the code above (close to the end of it). In this case,
there's quite a bit of code to sort through. It would be nice to be able
to play around
with it, stick a couple of dd or say calls to dump out variables, right?
That approach, however, is somewhat annoying because after each change we have to recompile the entire Rakudo. On the meatiest box I got, it takes about 60 seconds. Not the end of the world, but there's a way to make things lightning fast!
We need to fix a bug in a method of a class. Another way to think of it is:
we need to replace a broken method with a working one. Signature class
is just like any other class, so if we want to replace one of its methods, we
can just mix in a role!
The broken ACCEPTS will continue to live in the compiler, and we'll pop
open a separate playground file and define a role—let's calls it
FixedSignature—in it.
To get our new-and-improved ACCEPTS method in standalone signature objects,
we'll use the but operator to mix the FixedSignature in.
Here's the role, the mixing in, and the code that triggers the bug. I'll leave out method bodies for brieviety, but there's they are the same as in the code above.
role FixedSignature {
multi method ACCEPTS(Signature:D: Capture $topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: @topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: %topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: Signature:D $topic) { #`(redacted for brevity) }
}
my $a = :(*%) but FixedSignature;
my $b = :() but FixedSignature;
say $a ~~ $b;
There are two more things we need to do for our role to work properly.
First, we're dealing with multis and right now the multis in our role
are creating ambiguities with the multis in the original Signature class.
To avoid that, we'll define a proto:
proto method ACCEPTS (|) { * }
Since the code is using some NQP, we also need to bring in those features into our playground file with the role. Just add the appropriate pragma at the top of the file:
use MONKEY-GUTS;
With these modifications, our final test file becomes the following:
use MONKEY-GUTS;
role FixedSignature {
proto method ACCEPTS (|) { * }
multi method ACCEPTS(Signature:D: Capture $topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: @topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: %topic) { #`(redacted for brevity) }
multi method ACCEPTS(Signature:D: Signature:D $topic) { #`(redacted for brevity) }
}
my $a = :(*%) but FixedSignature;
my $b = :() but FixedSignature;
say $a ~~ $b;
And with this trick in place, we now have a rapid-fire weapon to hunt down the bug with—the changes we make compile instantly.
Now, we can debug the code just like any other. I prefer applying liberal
amounts of dd (or say) calls and dumping out the variables to ensure
their contents match expectations.
The .type method call our error message mentions is in this line:
return False if any($here.keys) ~~ -> Any $_ { !(.type =:= Mu) };
It calls it on the keys of $here, so let's dump the $here before that
statement:
...
dd $here
return False if any($here.keys) ~~ -> Any $_ { !(.type =:= Mu) };
...
# OUTPUT:
# SetHash $here = SetHash.new(Any)
Here's our offending Any, let's go up a bit and dump the $here right where
it's defined:
...
my $here=$sclass{True}.SetHash;
dd $here;
...
# OUTPUT:
# SetHash $here = SetHash.new(Any)
It's still there, and for a good reason. If we trace the creation of
$sclass, we'll see it's this:
my $sclass = self.params.classify({.named});
The params of the Signature on the right of the smartmatch get classified
based on whether they are named or not. The named parameters will be inside
a list under the True key of $sclass. Since we do not have any named
params, there won't be such a key, and we can verify that with this bit of
code:
:().params.classify(*.named).say
# OUTPUT:
# {}
When we go to define $here, we get an Any from $sclass{True}, since
that key doesn't exist, and when we call .SetHash on it, we get our
problematic Sethash object with an Any in it. And so, we have our fix for the bug: ensure the True key in $sclass is actually there before creating
a SetHash out of its value:
my $here=($sclass{True}:v).SetHash;
Add that to our playground file with the FixedSignature role in it, run it,
and verify the fix works. Now, simply transplant the fix back into
src/core/Signature.pm and then compile the
compiler.
perl Configure.pl --gen-moar --gen-nqp --backends=moar
make
make test
make install
Verify our fix worked before we proceed onto the final stages:
$ make t/spec/S06-signature/outside-subroutine.t
...
All tests successful.
Files=1, Tests=4, 1 wallclock secs ( 0.03 usr 0.00 sys + 0.32 cusr 0.02 csys = 0.37 CPU)
Result: PASS
So far, all we know is the bug we found was fixed and the tests we wrote for it pass. However, before we ship our fix, we must ensure we didn't break anything else. There are other devs working from the same repo and you'll be interfering with their work if you break stuff.
Run the full Roast test suite with make spectest command.
You can use the TEST_JOBS environmental variable to specify the number of
simultaneous tests. Generally a value slightly higher than the available cores
works the fastest... and cores make all the difference. On my 24-core VM I
cut releases on, the spectest completes in about 1 minute and 15 seconds. On
my 2-core web server, it takes about 25 minutes. You get the idea.
TEST_JOBS=28 make spectest
...
All tests successful.
Files=1111, Tests=52510, 82 wallclock secs (13.09 usr 2.44 sys + 1517.34 cusr 97.67 csys = 1630.54 CPU)
Result: PASS
Once the spectest completes and we have the clean bill of health, we're ready
to ship our fix. Commit the Rakudo fix, then go into t/spec and commit
the Roast fix:
git commit -m 'Fix Smartmatch with two signatures, only one of which has slurpy hash' \
-m 'Fixes RT#128795' src/core/Signature.pm
git push
cd t/spec
git commit -m 'smartmatch on signature with no slurpy on right side does not crash' \
-m 'RT#128795' S06-signature/outside-subroutine.t
git push
If you're pushing to your fork of these projects, you have to go the extra step and submit a Pull Request (just go to your fork and GitHub should display a button just for that).
And we're done! Celebrate with the appropriate amount of fun.
Rakudo bugs can be easy to fix, requiring not much more than knowledge of Perl 6. To fix them, you don't need to re-compile the entire compiler, but can instead define a small role with a method you're trying to fix and modify and recompile just that.
It's important to add tests for the bug into the official test suite and it's also important to run the full spectest after you fix the bug. But most important of all, is to have fun fixing it.
-Ofun
Read this article on Perl6.Party
I wrote my first Perl 6 program—a New Years IRC Party bot—around Christmas, 2015. The work included releasing the IRC::Client module, and given my virginity with the language and blood alcohol level appropriate for the Holiday Season, the module ended up sufficiently craptastic.
Recently, I needed a tool for some Perl 6 bug queue work, so I decided to lock myself up for a weekend and re-design and re-write the module from scratch. Multiple people bugged me to do so over the past months, so I figured I'd also write a tutorial for how to use the module—as an apology for being a master procrastinator. And should IRC be of no interest to you, I hope the tutorial will prove useful as a general example of async, non-blocking interfaces in Perl 6.
To create an IRC bot, instantiate an IRC::Client object, giving it some basic
info, and call the .run method. Implement all of the functionality you need as
classes with method names matching the events you want to listen to
and hand those in via the .plugins attribute. When an IRC event
occurs, it's passed to all of the plugins, in the order you specify them,
stopping if a plugin claims it handled the event.
Here's a simple IRC bot that responds to being addressed in-channel, notices, and private messages sent to it. The response is the uppercased original message the bot received:
use IRC::Client;
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(class { method irc-to-me ($_) { .text.uc } })
And here's what the bot looks like when running:
<Zoffix> MahBot, I ♥ you!
<MahBot> Zoffix, I ♥ YOU!
The :nick, :host, and :channels are the nick for your bot, the
server it should connect to, and channels it should join. The :debug
controls how much debugging output to display. We'll set it to value 1 here,
for sparse debug output, just to see what's happening. Tip: install the
optional
Terminal::ANSIColor
module to make debug output purty:

For the .plugins attribute, we hand in an anonymous class. If you have
multiple plugins, just shove them all in in the order you want them to receive
events in:
:plugins(PlugFirst.new, PlugSecond.new(:conf), class { ... })
The plugin class of our uppercasing bot has a single method that listens to
irc-to-me event, triggered whenever the bot is addressed in-channel or is sent
a private message or notice. It receives a single argument: one of the objects
that does the IRC::Client::Message role. We stick it into the $_ topical
variable to save a bit of typing.
We reply to the event by returning a value from the method. The original text is
contained inside the .text attribute of the message object, so we'll call
.uc method on it to uppercase the content and that's what our reply will be.
As awesome as our uppercasing bot is, it's as useful as an air conditioner on a polar expedition. Let's teach it some tricks.
We'll call our new plugin Trickster and it'll respond to commands time—that
will give the local time and date—and temp—that will convert temperature
between Fahrenheit and Celsius. Here's the code:
use IRC::Client;
class Trickster {
method irc-to-me ($_) {
given .text {
when /time/ { DateTime.now }
when /temp \s+ $<temp>=\d+ $<unit>=[F|C]/ {
when $<unit> eq 'F' { "That's {($<temp> - 32) × .5556}°C" }
default { "That's { $<temp> × 1.8 + 32 }°F" }
}
'huh?'
}
}
}
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(Trickster)
<Zoffix> MahBot, time
<MahBot> Zoffix, 2016-07-23T19:00:15.795551-04:00
<Zoffix> MahBot, temp 42F
<MahBot> Zoffix, That's 5.556°C
<Zoffix> MahBot, temp 42C
<MahBot> Zoffix, That's 107.6°F
<Zoffix> MahBot, I ♥ you!
<MahBot> Zoffix, huh?
The code is trivial: we pass the given text over a couple of regexes. If
it contains word time, we return the current time. If it contains word
temp we do the appropriate math, based on whether the given number is
postfixed by an F or a C. And if no matches happen, we end up returning
the inquisitive huh?.
There's an obvious problem with this new and improved plugin: the bot no longer
loves me! And while I'll survive the heartache, I doubt any other plugin will
teach the bot to love again, as Trickster consumes all irc-to-me events,
even if it doesn't recognize any of the commands it can handle. Let's fix that!
There's a special value that can be returned by the event handler to signal
that it did not handle the event and that it should be propagated to
further plugins and event handlers. That value is provided by the
.NEXT attribute offered by the IRC::Client::Plugin role, which a plugin
does to obtain that attribute. The role is automatically exported when
you use IRC::Client.
Let's look at some code utilizing that special value. Note that since
.NEXT is an attribute and we can't look up attributes on type objects,
you need to go the extra step and instantiate your plugin classes when giving
them to :plugins.
use IRC::Client;
class Trickster does IRC::Client::Plugin {
method irc-to-me ($_) {
given .text {
when /time/ { DateTime.now }
when /temp \s+ $<temp>=\d+ $<unit>=[F|C]/ {
when $<unit> eq 'F' { "That's {($<temp> - 32) × .5556}°C" }
default { "That's { $<temp> × 1.8 + 32 }°F" }
}
$.NEXT;
}
}
}
class BFF does IRC::Client::Plugin {
method irc-to-me ($_) {
when .text ~~ /'♥'/ { 'I ♥ YOU!' };
$.NEXT;
}
}
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(Trickster.new, BFF.new)
<Zoffix> MahBot, time
<MahBot> Zoffix, 2016-07-23T19:37:45.788272-04:00
<Zoffix> MahBot, temp 42F
<MahBot> Zoffix, That's 5.556°C
<Zoffix> MahBot, temp 42C
<MahBot> Zoffix, That's 107.6°F
<Zoffix> MahBot, I ♥ you!
<MahBot> Zoffix, I ♥ YOU!
We now have two plugins that both subscribe to irc-to-me event. The
:plugins attribute receives Trickster plugin first, so its
event handler will be run first. If the received text does not match either
of the Trickster's regexes, it returns $.NEXT from the method.
That signals the Client Object to go hunting for other handlers, so it gets
to BFF's irc-to-me handler. There, we reply if the input contains a heart,
if not, we pre-emptively return $.NEXT here too.
While the bot got its sunny disposition back, it did so at the cost of quite a bit of extra typing. What can we do about that?
Perl 6 supports multi-dispatch as well as type constraints in signatures. On
top of that, smartmatch against IRC::Client's message objects that have
a .text attribute uses the value of that attribute. Combine all three
of those features and you end up with ridiculously concise code:
use IRC::Client;
class Trickster {
multi method irc-to-me ($ where /time/) { DateTime.now }
multi method irc-to-me ($ where /temp \s+ $<temp>=\d+ $<unit>=[F|C]/) {
$<unit> eq 'F' ?? "That's {($<temp> - 32) × .5556}°C"
!! "That's { $<temp> × 1.8 + 32 }°F"
}
}
class BFF { method irc-to-me ($ where /'♥'/) { 'I ♥ YOU!' } }
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(Trickster, BFF)
<Zoffix> MahBot, time
<MahBot> Zoffix, 2016-07-23T19:59:44.481553-04:00
<Zoffix> MahBot, temp 42F
<MahBot> Zoffix, That's 5.556°C
<Zoffix> MahBot, temp 42C
<MahBot> Zoffix, That's 107.6°F
<Zoffix> MahBot, I ♥ you!
<MahBot> Zoffix, I ♥ YOU!
Outside of the signature, we no longer have any need for the message object,
so we use the anonymous $ parameter in its place. We then
type-constrain
that parameter with a regex match, and so the method will be called only if the
text of the message matches that regex. Since no methods will be called
on failed matches, we no longer have to mess around with the whole $.NEXT
business or compose any roles into our plugins.
The bodies of our methods each have a single statement that produces the
response value for the event. In the temperature converter, we use the ternary
operator to select which formula to use for the conversion, depending on the
unit requested, and yes, the $<unit> and $<temp> captures created in the
signature type constraint match are available in the method's body.
Along with standard named and numerical IRC protocol events, IRC::Client
offers convenience events. One of them we've already seen: the irc-to-me
event. Such events are layered, so one IRC event can trigger several
IRC::Client's events. For example, if someone addresses our bot in a channel,
the following chain of events will be fired:
irc-addressed ▶ irc-to-me ▶ irc-privmsg-channel ▶ irc-privmsg ▶ irc-all
The events are ordered from "narrowest" to "widest": irc-addressed can be
triggered only in-channel, when our bot is addressed; irc-to-me can also
be triggered via notice and private message, so it's wider;
irc-privmsg-channel includes all channel messages, so it's wider still;
and irc-privmsg also includes private messages to our bot. The chain ends
by the widest event of them all: irc-all.
If a plugin's event handler returns any value other than $.NEXT, later
events in the event chain won't be fired, just as plugins later in the
plugin chain won't be tried for the same reason. Each event is tried on all
of the plugins, before attempting to handle a wider event.
By setting the :debug attribute to level 3 or higher, you'll get emitted
events in the debug output. Here's our bot attempting to handle unknown command
blarg and then processing command time handled by irc-to-me event handler
we defined:

All of IRC::Client's events have irc- prefix, so you can freely define
auxiliary methods in your plugin, without worrying about conflicting with event
handlers. Speaking of emitting things...
Responding to commands is sweet and all, but many bots will likely want to generate some output out of their own volition. As an example, let's write a bot that will annoy us whenever we have unread GitHub notifications!
use IRC::Client;
use HTTP::Tinyish;
use JSON::Fast;
class GitHub::Notifications does IRC::Client::Plugin {
has Str $.token = %*ENV<GITHUB_TOKEN>;
has $!ua = HTTP::Tinyish.new;
constant $API_URL = 'https://api.github.com/notifications';
method irc-connected ($) {
start react {
whenever self!notification.grep(* > 0) -> $num {
$.irc.send: :where<Zoffix>
:text("You have $num unread notifications!")
:notice;
}
}
}
method !notification {
supply {
loop {
my $res = $!ua.get: $API_URL, :headers{ :Authorization("token $!token") };
$res<success> and emit +grep *.<unread>, |from-json $res<content>;
sleep $res<headers><X-Poll-Interval> || 60;
}
}
}
}
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(GitHub::Notifications.new)
-MahBot- Zoffix, You have 20 unread notifications!
-MahBot- Zoffix, You have 19 unread notifications!
We create GitHub::Notifications class that does the
IRC::Client::Plugin role. That role gives us the $.irc attribute, which
is the IRC::Client object we'll use to send messages to us on IRC.
Aside from irc-connected method, the class is just like any other:
a public $.token attribute for our GitHub API token, a private $!ua
attribute that keeps our HTTP User Agent object around, and a private
notification method, where all the action happens.
Inside notification, we create a
Supply
that will emit the number of unread notifications we have. It does so
by using an HTTP::Tinyish
object to access a GitHub API endpoint. On line 24, it parses the JSON
returned by successful requests, and greps the message list for any items with
unread property set to true. The prefix + operator converts the list
to an Int that is total items found, which is what we emit from our supply.
The irc-connected event handler gets triggered when we successfully connect
to an IRC server. In it, we start an event loop that reacts whenever
we receive the current unread messages count from our supply given by
notifications method. Since we're only interested in cases where we do
have unread messages, we also pop a grep on the supply to filter out the
cases without any messages (yes, we could avoid emitting those in the
first place, but I'm showing off Perl 6 here 😸). And once we do
have unread messages, we simply call IRC::Client's .send method, asking
it to send us an IRC notice with the total number of unread messages. Pure
awesomeness!
We've covered the cases where we either have an asynchronous supply of values we sent to IRC or where we reply to a command right away. It's not uncommon for a bot command to take some time to execute. In those cases, we don't want the bot to lock up while the command is doing its thing.
Thanks to Perl 6's excellent concurrency primitives, it doesn't have to! If
an event handler returns a Promise,
the Client Object will use its .result as the reply when it is kept. This
means that in order to make our blocking event handler non-blocking, all we have
to do is wrap its body in a start { ... } block. What could be simpler?
As an example, let's write a bot that will respond to bash command. The bot
will fetch bash.org/?random1, parse out the
quotes from the HTML, and keep them in the cache. When the command is triggered,
the bot will hand out one of the quotes, repeating the fetching when the cache
runs out. In particular, we don't want the bot to block while retrieving
and parsing the web page. Here's the full code:
use IRC::Client;
use Mojo::UserAgent:from<Perl5>;
class Bash {
constant $BASH_URL = 'http://bash.org/?random1';
constant $cache = Channel.new;
has $!ua = Mojo::UserAgent.new;
multi method irc-to-me ($ where /bash/) {
start $cache.poll or do { self!fetch-quotes; $cache.poll };
}
method !fetch-quotes {
$cache.send: $_
for $!ua.get($BASH_URL).res.dom.find('.qt').each».all_text.lines.join: ' ';
}
}
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#perl6>
:debug
:plugins(Bash.new)
<Zoffix> MahBot, bash
<MahBot> Zoffix, <Time> that reminds me of when Manning and I installed OS/2 Warp4 on a box and during the install routine it said something to the likes of 'join the hundreds of people on the internet'
For page fetching needs, I chose Perl 5's
Mojo::UserAgent, since it has
an HTML parser built-in. The :from<Perl5> adverb indicates to the compiler
that we want to load a Perl 5, not Perl 6, module.
Since we're multi-threading, we'll use a
Channel as a thread-safe queue
for our caching purposes. We
subscribe to the irc-to-me event where text contains word bash. When the
event handler is triggered, we pop out to a new thread using the start
keyword. Then we .poll our cache and use the cached value if we have one,
otherwise, the logic will move onto the do block that
that calls the fetch-quotes private method and when that completes,
polls the cache once more, getting a fresh quote. All said and done, a quote
will be the result of the Promise we return from the event handler.
The fetch-quotes method fires up our Mojo::UserAgent object that fetches
the random quotes page from the website, finds all
HTML elements that have class="qt" on them—those are paragraphs with
quotes. Then, we use a hyper method call to convert those paragraphs to just
text and that final list is fed to our $cache Channel via a for loop.
And there you go, we non-blockingly connected our bot to the cesspit of the IRC
world. And speaking of things you may want to filter...
Our bot would get banned rather quickly if it spewed enormous amounts of
output into channels. An obvious solution is to include logic in our
plugins that would use a pastebin if the output is too large. However,
it's pretty impractical to add such a thing to every plugin we write. Luckily,
IRC::Client has support for filters!
For any method that issues a NOTICE or PRIVMSG IRC command,
IRC::Client will pass the output through classes given to it via :filters
attribute. This means we can set up a filter that will automatically pastebin
large output, regardless of what plugin it comes from.
We'll re-use our bash.org quote bot, except this time it will pastebin large quotes to Shadowcat pastebin. Let's look at some code!
use IRC::Client;
use Pastebin::Shadowcat;
use Mojo::UserAgent:from<Perl5>;
class Bash {
constant $BASH_URL = 'http://bash.org/?random1';
constant $cache = Channel.new;
has $!ua = Mojo::UserAgent.new;
multi method irc-to-me ($ where /bash/) {
start $cache.poll or do { self!fetch-quotes; $cache.poll };
}
method !fetch-quotes {
$cache.send: $_
for $!ua.get($BASH_URL).res.dom.find('.qt').each».all_text;
}
}
.run with IRC::Client.new:
:nick<MahBot>
:host<irc.freenode.net>
:channels<#zofbot>
:debug
:plugins(Bash.new)
:filters(
-> $text where .lines > 1 || .chars > 300 {
Pastebin::Shadowcat.new.paste: $text.lines.join: "\n";
}
)
<Zoffix> MahBot, bash
<MahBot> Zoffix, <intuit> hmm maybe sumtime next week i will go outside'
<Zoffix> MahBot, bash
<MahBot> Zoffix, http://fpaste.scsys.co.uk/528741
The code that does all the filtering work is small enough that it's easy to
miss—it's the last 5 lines in the program above. The :filters attribute
takes a list of Callables, and here
we're passing a pointy block. In its signature we constraint the text
to be more than 1 line or more than 300 characters long, so our filter will
be run only when those criteria are met. Inside the block, we simply use the
Pastebin::Shadowcat module
to throw the output onto the pastebin. Its .paste method returns the
URL of the newly-created paste, which is what our filter will replace the
original content with. Pretty awesome!
In the past, when I used other IRC client tools, whenever someone asked me to place my bots on other servers, the procedure was simple: copy over the code to another directory, change config, and you're done. It almost made sense that a new server would mean a "new" bot: different channels, different nicknames, and so on.
In Perl 6's IRC::Client, I tried to re-imagine things a bit:
a server is merely another
identifier for a message, along with a channel or nickname. This means
connecting your bot to multiple servers is as simple as adding new server
configuration via :servers attribute:
use IRC::Client;
class BFF {
method irc-to-me ($ where /'♥'/) { 'I ♥ YOU!' }
}
.run with IRC::Client.new:
:debug
:plugins(BFF)
:nick<MahBot>
:channels<#zofbot>
:servers(
freenode => %(
:host<irc.freenode.net>,
),
local => %(
:nick<P6Bot>,
:channels<#zofbot #perl6>,
:host<localhost>,
)
)
<ZoffixW> MahBot, I ♥ you
<MahBot> ZoffixW, I ♥ YOU!
<ZoffixW> P6Bot, I ♥ you
<P6Bot> ZoffixW, I ♥ YOU!
First, our plugin remains oblivious that it's being run on multiple servers.
Its replies get redirected to the correct server and IRC::Client still
executes its method handler in a thread-safe way.
In the IRC::Client's constructor we added :servers attribute that takes
a Hash. The keys of this Hash are servers' labels and values are
server-specific configurations that override global settings. So freenode
server gets its :nick and :channels from the :nick and :channels
attributes we give to IRC::Client, while the local server overrides those
with its own values.
The debug output now has server lables printed, to indicate to which server the event applies:

And so, but simply telling the bot to connect to another server, we made it multi-server, without making any changes to our plugins. But what do we do when we do want to talk to a specific server?
When the bot is .run, the Client Object changes the values of :servers
attribute to be IRC::Client::Server objects. Those stringify to the label
for the server they represent and we can get them either from the .server
attribute of the Message Object or .servers hash attribute of the
Client Object. Client Object methods such as .send or .join take
an optional server attribute that controls which server the message will
be sent to and defaults to value *, which means send to every server.
Here's a bot that connects to two servers and joins several channels. Whenever
it sees a channel message, it forwards it to all other channels and sends a
private message to user Zoffix on server designated by label local.
use IRC::Client;
class Messenger does IRC::Client::Plugin {
method irc-privmsg-channel ($e) {
for $.irc.servers.values -> $server {
for $server.channels -> $channel {
next if $server eq $e.server and $channel eq $e.channel;
$.irc.send: :$server, :where($channel), :text(
"$e.nick() over at $e.server.host()/$e.channel() says $e.text()"
);
}
}
$.irc.send: :where<Zoffix>
:text('I spread the messages!')
:server<local>;
}
}
.run with IRC::Client.new:
:debug
:plugins[Messenger.new]
:nick<MahBot>
:channels<#zofbot>
:servers{
freenode => %(
:host<irc.freenode.net>,
),
local => %(
:nick<P6Bot>,
:channels<#zofbot #perl6>,
:host<localhost>,
)
}
<ZoffixW> Yey!
<P6Bot> ZoffixW over at irc.freenode.net/#zofbot says Yey!
<P6Bot> ZoffixW over at irc.freenode.net/#zofbot says Yey!
<P6Bot> I spread the messages!
We subscribe to the irc-privmsg-channel event and when it's triggered,
we loop over all the servers. For each server, we loop over all of the
connected channels and use $.irc.send method to send a message to that
particular channel and server, unless the server and channel are the same
as where the message originated.
The message itself calls .nick, .channel, and .server.host methods
on the Message Object to identify the sender and origin of the message.
Perl 6 offers powerful concurrency primitives, dispatch methods, and
introspection that lets you build awesome non-blocking, event-based interfaces.
One of them is IRC::Client that lets you use IRC networks. It's here.
It's ready. Use it!
Coming from a Perl 5 background, my first experience with Perl 6's non-destructive substitution operator S/// looked something like this:
(artist's impression)
You'll fare better, I'm sure. Not only have the error messages improved, but I'll also explain everything right here and now.
The reason I had issues is because, seeing familiar-looking operators, I
simply translated Perl 5's binding operator (=~) to Perl 6's
smartmatch operator (~~) and expected things to work. The S/// was not documented and, combined with the confusing (at the time) warning message, this was the source of my pain:
my $orig = 'meowmix';
my $new = $orig ~~ S/me/c/;
say $new;
# OUTPUT warning:
# Smartmatch with S/// can never succeed
The old warning suggests the ~~ operator is the wrong choice here and it is.
The ~~ isn't the equivalent of Perl 5's =~. It aliases the left hand side
to $_, evaluates the right hand side, and then calls .ACCEPTS($_) on it. That is all there is to its magic.
So what's actually happening in the example above:
S///, $orig is aliased to $_S/// non-destructively executes substitution on $_ and returns the resulting string. This is what the smartmatch will operate onStr against Str, will give True or False depending on whether
substitution happened (True, confusingly, meaning it didn't)At the end of it all, we aren't getting what we actually want: the version of the string with substitution applied.
Now that we know that S/// always works on $_ and returns the result, it's
easy to come up with a whole bunch of ways that set $_ to our original
string and gather back the return value of S///, but let's look
at just a couple of them:
my $orig = 'meowmix';
my $new = S/me/c/ given $orig;
say $orig;
say $new;
my @orig = <meow cow sow vow>;
my @new = do for @orig { S/\w+ <?before 'ow'>/w/ };
say @orig;
say @new;
# OUTPUT:
# meowmix
# cowmix
# [meow cow sow vow]
# [wow wow wow wow]
The first one operates on a single value. We use the postfix form of the
given block, which lets us avoid the curlies (you can use with in place of given with the same results). From the output, you can see the original string remained intact.
The second example operates on a whole bunch of strings from an Array and we
use the do keyword to execute a regular for loop (that aliases to $_ in this case) and assign the result to the @new array. Again, the output shows
the originals were not touched.
The S/// operator—just like s/// and some methods—lets you use regex adverbs:
given 'Lörem Ipsum Dolor Sit Amet' {
say S:g /m/g/; # Löreg Ipsug Dolor Sit Aget
say S:i /l/b/; # börem Ipsum Dolor Sit Amet
say S:ii /l/b/; # Börem Ipsum Dolor Sit Amet
say S:mm /o/u/; # Lürem Ipsum Dolor Sit Amet
say S:nth(2) /m /g/; # Lörem Ipsug Dolor Sit Amet
say S:x(2) /m /g/; # Löreg Ipsug Dolor Sit Amet
say S:ss/Ipsum Dolor/Gipsum\nColor/; # Lörem Gipsum Color Sit Amet
say S:g:ii:nth(2) /m/g/; # Lörem Ipsug Dolor Sit Amet
}
As you can see, they are in the form of :foo that is added after the S part of the operator. You can
use whitespace liberally and several adverbs can be used at the same time. Here are their
meanings:
:g—(long alternative: :global) global match: replace all occurances:i—case insentive match:ii—(long alternative: :samecase) preserve case: regardless of the case of letter used as a substitute, the original case of the letter being replaced will be used:mm—(long alternative: :samemark) preserve mark: in the example above, the diaeresis that was on letter o was preserved and applied to the replacement letter u:nth(n)—replace only nth occurance:x(n)—replace at most n occurances (mnemonic: "x as in times"):ss—(long alternative: :samespace) preserve space type: the type of whitespace character is preserved, regardless of whitespace characters used in the replacement string. In the example above, we replaced with a new line, but the original space was keptOperator S/// is nice, but using it is somewhat awkward at times. Don't fear, Perl 6 provides
.subst method for all your substitution needs and delightful .subst/.substr confusion. Here's
what its use looks like:
say 'meowmix'.subst: 'me', 'c';
say 'meowmix'.subst: /m./, 'c';
# OUTPUT:
# cowmix
# cowmix
The method takes either a regex or a plain string as the first positional argument, which is the thing it'll look for in its invocant. The second argument is the replacement string.
You can use the adverbs as well, by simply listing them as named Bool arguments,
with a slight caveat. In S/// form, adverbs :ss and :ii imply
the presence of :s (make whitepsace significant) and :i (case-insensitive match) adverbs respectively.
In method form, you have to apply those to the regex itself:
given 'Lorem Ipsum Dolor Sit Amet' {
say .subst: /:i l/, 'b', :ii;
say .subst: /:s Ipsum Dolor/, "Gipsum\nColor", :ss;
}
# OUTPUT:
# Borem Ipsum Dolor Sit Amet
# Lorem Gipsum Color Sit Amet
Captures aren't alien to substitution operations, so let's try one out with the method call form of substitution:
say 'meowmix'.subst: /me (.+)/, "c$0";
# OUTPUT:
# Use of Nil in string context in block <unit> at test.p6 line 1
# c
Not quite what we were looking for. Our replacement string is constructed even before it reaches the .subst method and the $0 variable inside of it actually refers to whatever it is before the
method call, not the capture in the .subst regex. So how do we fix this?
The second argument to .subst can also take a Callable. Inside
of it, you can use the $0, $1, ... $n variables the way they were meant to and get correct values from
captures:
say 'meowmix'.subst: /me (.+)/, -> { "c$0" };
# OUTPUT:
# cowmix
Here, we've used a pointy block for our Callable, but WhateverCode and subs will work too. They will
be called for each substitution, with the Match object passed
as the first positional argument, if you need to access it.
The S/// operator in Perl 6 is the brother of s/// operator that instead of modifying the original
string, copies it, modifies, and returns the modified version. The way to use this operator differs from
the way non-destructive substitution operator works in Perl 5. As an alternative, a method version .subst is available as well. Both method and operator form of substitution can take a number of adverbs that
modify the behaviour of it, to suit your needs.
In my first college programming course, I was taught that Pascal language
has Integer, Boolean, and String types among others. I learned the types
were there because computers were stupid. While dabbling in C, I learned more
about what int, char, and other vermin looked like inside the warm,
buzzing metal box under my desk.
Perl 5 didn’t have types, and I felt free as a kid on a bike, rushing through the wind, going down a slope. No longer did I have to cram my mind into the narrow slits computer hardware dictated me to. I had data and I could do whatever I wanted with it, as long as I didn’t get the wrong kind of data. And when I did get it, I fell off my bike and skinned my knees.
With Perl 6, you can have the cake and eat it too. You can use types or avoid them. You can have broad types that accept many kinds of values or narrow ones. And you can enjoy the speed of types that represent the mind of the machine, or you can enjoy the precision of your own custom types that represent your mind, the types made for humans.
my $a = 'whatever';
my Str $b = 'strings only';
my Str:D $c = 'defined strings only';
my int $d = 16; # native int
sub foo ($x) { $x + 2 }
sub bar (Int:D $x) returns Int { $x + 2 }
Perl 6 has gradual typing, which means you can either use types or avoid them. So why bother with them at all?
First, types restrict the range of values that can be contained in your variable, accepted by your method or sub or returned by them. This functions both as data validation and as a safety net for garbage data generated by incorrect code.
Also, you can get better performance and reduced memory usage when using native, machine-mind types, providing they’re the appropriate tool for your data.
There’s a veritable smörgåsbord of built-in types in Perl 6. If the thing your subroutine does makes
sense to be done only on integers, use an Int for your
parameters.
If negatives don’t make sense either, limit the range of values even further
and use a UInt—an unsigned Int. On the other hand, if you want to handle
a broader range, Numeric type may
be more appropriate.
If you want to drive closer to the metal, Perl 6 also offers a range of
native types that map into what you’d normally find with, say, C. Using these
may offer performance improvements or lower memory usage. The available types
are: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, num, num32, and num64. The number in the type name
signifies the available bits, with the numberless types being
platform-dependent.
Sub-byte types such as int1, int2, and int4 are planned to be implemented
in the future as well.
multi foo (Int:U $x) { 'Y U NO define $x?' }
multi foo (Int:D $x) { "The square of $x is {$x²}" }
my Int $x;
say foo $x;
$x = 42;
say foo $x;
# OUTPUT:
# Y U NO define $x?
# The square of 42 is 1764
Smileys are :U, :D, or :_ appended to the type name. The :_ is the
default you get when you don’t specify a smiley. The :U specifies
undefined values only, while :D specifies defined values only.
This can be useful to detect whether a method is called on the class or on the
instance by having two multies with :U and :D on the invocant. And if you
work at a nuclear powerplant, ensuring your rod insertion subroutine never
tries to insert by an undefined amount is also a fine thing, I imagine.
Built-in types are cool and all, but most of the data programmers work with doesn’t match them precisely. That’s where Perl 6 subsets come into play:
subset Prime of Int where *.is-prime;
my Prime $x = 3;
$x = 11; # works
$x = 4; # Fails with type mismatch
Using the subset keyword, we created a type called Prime on the fly. It’s
a subset of Int, so anything that’s non-Int doesn’t fit the type. We
also specify an additional restriction with the where keyword; that
restriction being that .is-prime method called on the given value must
return a true value.
With that single line of code, we created a special type and can use it as if it were built-in! Not only can we use it to specify the type of variables, sub/method parameters and return values, but we can test arbitrary values against it with the smartmatch operator, just as we can with built-in types:
subset Prime of Int where *.is-prime;
say "It's an Int" if 'foo' ~~ Int; # false, it's a Str
say "It's a prime" if 31337 ~~ Prime; # true, it's a prime number
Is your “type” a one-off thing you just want to apply to a single variable?
You don’t need to declare a separate subset at all! Just use the where
keyword after the variable and you’re good to go:
multi is-a-prime (Int $ where *.is-prime --> 'Yup' ) {}
multi is-a-prime (Any --> 'Nope') {}
say is-a-prime 3; # Yup
say is-a-prime 4; # Nope
say is-a-prime 'foo'; # Nope
The --> in the signature above is just another way to indicate the return
type, or in this case, a concrete returned value. So we have two multies with different
signatures. First one takes an Int that is a prime number and the second
one takes everything else. With exactly zero code in the bodies of our multies
we wrote a subroutine that can tell you whether a number is prime!!
What we’ve learned so far is pretty sweet, but sweet ain’t awesome! You may end up using some of your custom types quite frequently. Working at a company where product numbers can be at most 20 characters, following some format? Perfect! Let’s create a subtype just for that:
subset ProductNumber of Str where { .chars <= 20 and m/^ \d**3 <[-#]>/ };
my ProductNumber $num = '333-FOOBAR';
This is great, but we don’t want to repeat this subset stuff all over the place.
Let’s shove it into a separate module we can use.
I’ll create /opt/local/Perl6/Company/Types.pm6 because /opt/local/Perl6
is the path included in module search path for all the apps I write for
this fictional company. Inside this file, we’ll have this code:
unit module Company::Types;
my package EXPORT::DEFAULT {
subset ProductNumber of Str where { .chars <= 20 and m/^ \d**3 <[-#]>/ };
}
We name our module and let our shiny subsets be exported by default. What will our code look like now? It’ll look pretty sweet—no, wait, AWESOME—this time:
use Company::Types;
my ProductNumber $num1 = '333-FOOBAR'; # succeeds
my ProductNumber $num2 = 'meow'; # fails
And so, with a single use statement, we extended Perl 6 to provide
custom-tailored types for us that match perfectly what we want our data to be
like.
If you’ve been actually trying out all these examples, you may have noticed a minor flaw. The error messages you get are Less Than Awesome:
Type check failed in assignment to $num2;
expected Company::Types::EXPORT::DEFAULT::ProductNumber but got Str ("meow")
in block <unit> at test.p6 line 3
When awesome is the goal, you certainly have a way to improve those messages.
Pop open our Company::Types file again, and extend the where clause
of our ProductNumber type to include an awesome error message:
subset ProductNumber of Str where {
.chars <= 20 and m/^ \d**3 <[-#]>/
or warn 'ProductNumber type expects a string at most 20 chars long'
~ ' with the first 4 characters in the format of \d\d\d[-|#]'
};
Now, whenever the thing doesn’t match our type, the message will be included
before the Type check... message and the stack trace, providing more info on
what sort of stuff was expected. You can also call fail instead of warn
here, if you wish, in which case the Type check... message won’t be printed,
giving you more control over the error the user of your code receives.
Perl 6 was made for humans to tell computers what to do, not for computers to restrict what is possible. Using types catches programming errors and does data validation, but you can abstain from using types when you don’t want to or when the type of data you get is uncertain.
You have the freedom to refine the built-in types to represent exactly the data you’re working with and you can create a module for common subsets. Importing such a module lets you write code as if those custom types were part of Perl 6 itself.
The Perl 6 technology lets you create types that are made for Humans. And it’s about time we started telling computers what to do.
Perl 6 will actually evaluate your where expression when checking types even for optional parameters. This can be quite annoying, due to “uninitialized” values being compared. I wrote Subset::Helper to make it easier to create subsets that solves that issue, and it provides an easy
way to add awesome error messages too.
Last night I gave a "Wow, Perl 6!" talk at the Toronto Perl Mongers, whom I thank for letting me speak, even after I got lost for 15 minutes in the building the event was hosted at and was subsequently late.
The talk is an overview of some of the cool features Perl 6 offers. If you didn't get a chance to be at the talk or to watch it via a Google Hangout, you can still get a recording of it.
You can view the slides at http://tpm2016.zoffix.com/ and the recording of the talk is on YouTube:
Couch Potato:
Molding Your Own:
Hyperspace: Multi-core processing at a touch of a button
How's Your Spellin'?
Whatever, man!:
Polyglot:
Not Really Advanced Things:
Bonus Slides:
say is for humans put is for computersDuring the talk a couple of questions were asked and I didn't know the answer at the time. I do now:
The code in the where can be anything you want, so you can warn or fail inside the check to get a better error message. Once caveat: the argument given to callframe might be different depending on where you're performing the check. Try adjusting it:
subset Foo of Int where {
$_ > 10_000
or fail "You need a number more than 10,000 on "
~ "line {(callframe 4).line}, but you passed $_";
};
my Foo $x = 1000;
# OUTPUT:
# You need a number more than 10,000 on line 7, but you passed 1000
# in block <unit> at test.p6 line 2
Yes, just smartmatch against the type/subset:
subset Even where * %% 2;
say 3 ~~ Even;
say 42 ~~ Even
# OUTPUT:
# False
# True
Set?No, it tries to actually create one. Makes sense, since a Set cares about the elements in it. Sure, it's possible to special-case some forms of sequences to figure out whether an element is part of the sequence or not, but it's probably not worth it. In a more general case, you are faced with the Halting Problem. Speaking of which, here is a gotcha with the sequence operator and the upper limit:
my @seq = 0, 2 ... * == 1001;
Here, I'm using the sequence operator to create a sequence of even numbers, and I'm limiting the upper bound by when it'd be equal to 1001. But it won't ever be equal to that. To human brain, it might seem obvious that once you're over 1001, you should stop here, but to a computer it's a Halting Problem and it'll keep trying to find the end point (so it'll never complete here).
Not possible. If you need that kind of thing, you'll have to use processes, or you'll have to build the code inside the Promise so that it exposes some kind of a "should I continue working?" flag.
Along with http://perl6intro.com/ that I mentioned during the talk, there's also Learn X in Y Minues Perl 6 page, which I personally found very useful when just starting out with Perl 6.
The Ecosystem is at http://modules.perl6.org/ you should have panda program installed, and you can install modules from the Ecosystem by typing panda install Foo::Bar
Hey everyone,
Following is the p5p (Perl 5 Porters) mailing list summary for the past week. Enjoy!