Jonathan McDowell: Christmas Movies

Courtesy of my CRANberries, there is also a diffstat report for this release. For questions, suggestions, or issues please use the issue tracker at the GitHub repo.Changes in ttdo version 0.0.10 (2025-01-21)
- Regular packaging updates to badges and continuous integration setup
- Add support for a 'visual difference' taking advantage of a tinysnapshot feature returning a
ttvd
-typed result- Added (versioned) dependency on tinysnapshot (to also get the plot 'style' enhancement in the most recent version) and base64enc
- Return
ttdo
-typed result if diffobj-printed result is returned- Ensure all package-documentation links in manual page have anchors to the package
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.
english/index.wml -> /index.en.html (with a symlink from index.html to index.en.html)
and french/index.wml -> /index.fr.html
. In contrast, debianhugo uses en/_index.md -> /index.html
and fr/_index.md -> /fr/index.html
.
Apache's multilingual content negotiation checks for index.<user preferred lang code>.html
in the current directory, which works well with webwml since all related translations are generated in the same directory. However, with debianhugo using subdirectories for languages other than English, we had to set up aliases for every other language page to be generated in the frontmatter. For example, in fr/_index.md
, we added this to the front matter:...
aliases:
- /index.fr.html
...
/index.en.html
. If it doesn t find it, it defaults to any other language-suffixed file, which can lead to unexpected behavior. For example, if English is set as the preferred language, accessing the site may serve /index.fr.html
, which then redirects to /fr/index.html
. This was a significant challenge, and you can see a demo of this hosted here.
If I were to start the project over, I would document every decision as I make them in the wiki, no matter how rough the documentation turns out. Waiting until the midpoint of the project to document was not a good idea.
As I move into the second half of my internship, the goals we ve set include improving our project wiki documentation and continuing the migration process while enhancing the user experience of complicated sections. I m looking forward to making even more progress and sharing my journey with you all. Happy coding!
fast_float
library
version to the current version 7.0.0, and updates a few packaging
aspects.
Courtesy of my CRANberries, there is also a diffstat report for this release. For questions, suggestions, or issues please use the [issue tracker][issue tickets] at the GitHub repo.Changes in version 0.0.5 (2025-01-15)
- No longer set a compilation standard
- Updates to continuous integration, badges, URLs, DESCRIPTION
- Update to fast_float 7.0.0
- Per CRAN Policy comment-out compiler 'diagnostic ignore' instances
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.
checkbashism
script complained about.
The following section from the NEWS.Rd file has full details.
Thanks to my CRANberries, there is a diff to the previous release. The RProtoBuf page has copies of the (older) package vignette, the quick overview vignette, and the pre-print of our JSS paper. Questions, comments etc should go to the GitHub issue tracker off the GitHub repo.Changes in RProtoBuf version 0.4.23 (2022-12-13)
- More robust tests using
toTextFormat()
(Xufei Tan in #99 addressing #98)- Various standard packaging updates to CI and badges (Dirk)
- Improvements to string construction in error messages (Michael Chirico in #102 and #103)
- Accommodate ProtoBuf 26.x and later (Matteo Gianella in #104)
- Accommodate ProtoBuf 6.30.9 and later (Lev Kandel in #106)
- Correct
bashism
issues inconfigure.ac
(Dirk)
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
.Call(symbol)
as well
as for the url to the Rcpp book (which
has remained unchanged for years) failing . My email reply was promptly
dealt with under European morning hours and by the time I got up the
submission was in state waiting over a single reverse-dependency
failure which is also spurious, appears on some systems and not
others, and also not new. Imagine that: nearly 3000 reverse dependencies
and only one (spurious) change to worse. Solid testing seems to help. My
thanks as always to the CRAN
for responding promptly.
This release continues with the six-months January-July cycle started
with release
1.0.5 in July 2020. This time we also need a one-off hotfix
release 1.0.13-1: we had (accidentally) conditioned an upcoming R
change on 4.5.0, but it already came with 4.4.2 so we needed to adjust
our code. As a reminder, we do of course make interim snapshot dev or
rc releases available via the Rcpp drat repo as well as
the r-universe page and
repo and strongly encourage their use and testing I run my systems
with these versions which tend to work just as well, and are also fully
tested against all reverse-dependencies.
Rcpp has long established itself
as the most popular way of enhancing R with C or C++ code. Right now,
2977 packages on CRAN depend on
Rcpp for making analytical code go
faster and further. On CRAN, 13.6% of all packages depend (directly) on
Rcpp, and 60.8% of all compiled
packages do. From the cloud mirror of CRAN (which is but a subset of all
CRAN downloads), Rcpp has been
downloaded 93.7 million times. The two published papers (also included
in the package as preprint vignettes) have, respectively, 1947 (JSS, 2011) and 354 (TAS, 2018)
citations, while the the book (Springer useR!,
2013) has another 676.
This release is primarily incremental as usual, generally preserving
existing capabilities faithfully while smoothing our corners and / or
extending slightly, sometimes in response to changing and tightened
demands from CRAN or R standards. The move towards a
more standardized approach for the C API of R once again to a few
changes; Kevin did once again did most of these PRs. Other contributed
PRs include G bor permitting builds on yet another BSD variant, Simon
Guest correcting sourceCpp()
to work on read-only files,
Marco Colombo correcting a (surprisingly large) number of vignette
typos, I aki rebuilding some documentation files that tickled (false)
alerts, and I took care of a number of other maintenance items along the
way.
The full list below details all changes, their respective PRs and, if
applicable, issue tickets. Big thanks from all of us to all
contributors!
Thanks to my CRANberries, you can also look at a diff to the previous release Questions, comments etc should go to the rcpp-devel mailing list off the R-Forge page. Bugs reports are welcome at the GitHub issue tracker as well (where one can also search among open or closed issues).Changes in Rcpp release version 1.0.14 (2025-01-11)
- Changes in Rcpp API:
- Support for user-defined databases has been removed (Kevin in #1314 fixing #1313)
- The
SET_TYPEOF
function and macro is no longer used (Kevin in #1315 fixing #1312)- An errorneous cast to
int
affecting large return object has been removed (Dirk in #1335 fixing #1334)- Compilation on DragonFlyBSD is now supported (G bor Cs rdi in #1338)
- Use read-only
VECTOR_PTR
andSTRING_PTR
only with with R 4.5.0 or later (Kevin in #1342 fixing #1341)- Changes in Rcpp Attributes:
- Changes in Rcpp Deployment:
- One unit tests for arm64 macOS has been adjusted; a macOS continuous integration runner was added (Dirk in #1324)
- Authors@R is now used in DESCRIPTION as mandated by CRAN, the
Rcpp.package.skeleton()
function also creates it (Dirk in #1325 and #1327)- A single datetime format test has been adjusted to match a change in R-devel (Dirk in #1348 fixing #1347)
- Changes in Rcpp Documentation:
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
pyproject.toml
files over time. This post is the update for 2024.
Analysis
Like last year, I m using Tom Forbes fantastic dataset
containing information about every file within every release uploaded to
PyPI. To get the current dataset, I followed the same
process as in last year s analysis, so I won t repeat
all the details here. Instead, I ll highlight the main steps:
pyproject.toml
file, and its hash for each uploadpyproject.toml
file and extract the build backend. To avoid
redundant downloads, I stored a mapping of the file hash and their respective
build backendpyproject.toml
files in just 20 minutes(!).
However, I couldn t find sufficient documentation that would help me to
implement this method, so this will have to wait until next year s analysis.
Once all the data is downloaded, I perform some preprocessing:
pyproject.toml
file given the repository
URL and its hash, I d love to hear from you!
amd64
architecture, naturally, but it also is building Debian packages that are marked with the no architecture label, all
. The second builder is, however, only rebuilding the i386
architecture.
Both of these services were also switched to reproduce the Debian trixie distribution instead of unstable, which started with 43% of the archive rebuild with 79.3% reproduced successfully. This is very much a work in progress, and we ll start reproducing Debian unstable soon.
Our i386
hosts are very kindly sponsored by Infomaniak whilst the amd64
node is sponsored by OSUOSL thank you! Indeed, we are looking for more workers for more Debian architectures; please contact us if you are able to help.
apt install debian-repro-status
.
glibc
within openSUSE.
kpcyrd
followed-up to a post from September 2024 which mentioned their desire for someone to implement a hashset of allowed module hashes that is generated during the kernel build and then embedded in the kernel image , thus enabling a deterministic and reproducible build. However, they are now reporting that somebody implemented the hash-based allow list feature and submitted it to the Linux kernel mailing list . Like kpcyrd
, we hope it gets merged.
First, we propose an automated approach for library reproducibility to enhance library security during the deployment phase. We then develop a scalable call graph generation technique to support various use cases, such as method-level vulnerability analysis and change impact analysis, which help mitigate security challenges within the ecosystem. Utilizing the generated call graphs, we explore the impact of libraries on their users. Finally, through empirical research and mining techniques, we investigate the current state of the Maven ecosystem, identify harmful practices, and propose recommendations to address them.A PDF of Mehdi s entire thesis is available to download.
283
and 284
to Debian:
tests_quines.py::test_ differences,differences_deb
to simply use assert_diff and not mangle the test fixture. [ ]0.11+nmu4
of the dh-buildinfo
package. In this release, the dh_buildinfo
becomes a no-op ie. it no longer does anything beyond warning the developer that the dh-buildinfo
package is now obsolete. In his upload, Santiago wrote that We still want packages to drop their [dependency] on dh-buildinfo
, but now they will immediately benefit from this change after a simple rebuild.
dpkg
.
debian-devel
development mailing list on the topic of Supporting alternative zlib implementations . In particular, Fay wrote about her results experimenting whether zlib-ng
produces identical results or not.
rust-rebuilderd-worker
, rust-derp
, rust-in-toto
and debian-repro-status
to Debian, which passed successfully through the so-called NEW queue.
debrebuild
component/script of the devscripts
package, including:
dh-r
package to report that the Recommends
and Suggests
fields are missing from rebuilt R packages. At the time of writing, this bug has no patch and needs some help to make over 350 binary packages reproducible.
-fobject-determinism
, which enables deterministic object code generation .
tests.reproducible-builds.org/archlinux
now redirect to reproducible.archlinux.org instead. In fact, everything Arch-related has now been removed from the jenkins.debian.net.git
repository, as those continuous integration tests have been disabled for some time.
0.7.29
was uploaded to Debian unstable by Vagrant Cascadian. It included contributions already covered in previous months as well as new ones from Rebecca N. Palmer, such as:
cargo-packaging/rusty_v8
, cockpit
, collectd
, deepin-daemon
, deepin-file-manager
, esbuild
, grpc
, hyperkitty
, icedtea-web
, java-atk-wrapper
, kdenetwork-filesharing
, kicad
, kompare
, librespeed-cli
, lincity-ng
, mraa
, ollama
, opa-fmgui
, opencryptoki
, opencryptoki
, openmpi4:gnu-hpc
, openwsman
, patterns-microos
, portmidi
, presage
, procps
, sad
, scons/nst
, sendmail
, static-initrd
, suse-hpc
, swtpm
, tiny
, vtk
, xdg-desktop-portal
and yast
.
pyorbital
.python-pbcore
.dictionaries-common
.i386.reproduce.debian.net
rebuilder. [ ][ ][ ][ ][ ][ ]i386.reproduce.debian.net
run on a public port to allow external workers. [ ]/api/v0/pkgs/list
endpoint. [ ]arch:any
and arch:all
on the amd64
architecture, but only arch:any
on i386
. [ ]dstat
on Jenkins nodes anymore as its been removed from Debian trixie. [ ]infom08-i386
node to become another rebuilder. [ ]reproducible_pool_buildinfos.sh
script. [ ]installation-birthday
everywhere. [ ]Recommends
by default on Jenkins nodes. [ ]rebuilder_stats.py
to rebuilderd_stats.py
. [ ]/etc/cron.d/
with the correct permissions. [ ].buildinfo
on buildinfos.debian.net files. [ ][ ][ ]rebuilder_stats.py
scripts. [ ]dpkg.selections
file [ ], Roland Clobus updated the Jenkins log parser to parse warnings from diffoscope [ ] and Mattia Rizzolo banned a number of bots and crawlers from the service [ ][ ].
#reproducible-builds
on irc.oftc.net
.
rb-general@lists.reproducible-builds.org
python3-django-jsonfield
in the code (it was
superseded by a Django-native field). Thanks to Philipp Kern from the Debian
System Administrators team, the upgrade happened on December 23rd.
Rapha l also improved distro-tracker to better deal with invalid Maintainer
fields which recently caused multiples issues in the regular data updates
(#1089985,
MR 105).
While working on this, he filed
#1089648 asking
dpkg tools to error out early when maintainers make such mistakes.
Finally he provided feedback to multiple issues and merge requests
(MR 106,
issues #21,
#76,
#77), there seems to
be a surge of interest in distro-tracker lately. It would be nice if those new
contributors could stick around and help out with the significant backlog of
issues (in the Debian BTS, in
Salsa).
build-essential
, by Helmut Grohne
Building on the gcc-for-host
work of last December,
a notable patch turning build-essential
Multi-Arch: same
became feasible. Whilst the change is small, its implications
and foundations are not. We still install crossbuild-essential-$ARCH
for cross
building and due to a britney2
limitation, we cannot have it depend on the
host s C library. As a result, there are workarounds in place for
sbuild
and pbuilder.
In turning build-essential
Multi-Arch: same
, we may actually express these
dependencies directly as we install build-essential:$ARCH
instead.
The crossbuild-essential-$ARCH
packages will continue to be available as
transitional dummy packages.
bubblewrap
,
e2fsprogs
, libvpd-2.2-3
, and pam-tmpdir
and corresponding on related
issues such as kexec-tools
and live-build
. The removal of the usrmerge
package unfortunately broke debootstrap
and was quickly reverted. Continued
fallout is expected and will continue until trixie
is released.gnu-efi
in the process.nproc
is not a good place for this functionality.etckeeper
, mdformat
and
python-internetarchive
python-midiutil
, antimony
, python-pyo
, rakarrack
, python-pyknon
,
soundcraft-utils
, cecilia
, nasty
, gnome-icon-theme-nuovo
,
gnome-extra-iconsg
, nome-subtitles
, timgm6mb-soundfont
)Andreas.
Restrictions:
needs-sudo
in autopkgtest.
I fixed broken aptly
images
in the Salsa CI pipeline.
Python team
Last month, I mentioned some progress on
sorting out the multipart vs. python-multipart name conflict in Debian
(#1085728), and said that I thought we d
be able to finish it soon. I was right! We got it all done this month:
intersphinx_mapping
syntax which turned out to
still be in use by many packages in Debian. The fixes for this were
individually trivial, but there were a lot of them:
twisted.internet.defer.returnValue
, realized it
was still used in many places in Debian, and went on a PR-filing spree
informed by codesearch to try to reduce
the future impact of such a change on Debian:
make
--shuffle
(also see its
author s
explanation).
I fixed associated bugs in cccc (contributed
upstream), groff, and spectemu.
I backported an upstream patch to putty to fix undefined behaviour that
affected use of the small keypad .
I removed groff s Recommends: libpaper1
(#1091375,
#1091376), since it isn t currently all
that useful and was getting in the way of a transition to libpaper2. I
filed an upstream bug suggesting
better integration in this area.
<return>
(MR)Recommends:
isn't enough for existing installations.--export-dir
regression (MR)--toggle
(MRvibrate()
API to allow e.g.games more haptic control (MR).
This could also be used in browser to implement the vibration API in e.g. Firefox.uscan --download-vesion
(MRPublisher: | Erewhon |
Copyright: | November 2024 |
ISBN: | 1-64566-099-0 |
Format: | Kindle |
Pages: | 443 |
Know I adore you. Look out over the glow. The cities sundered, their machines inverted, mountains split and prairies blazing, that long foreseen Hereafter crowning fast. This calamity is a promise made to you. A prayer to you, and to your shadow which has become my second self, tucked behind my eye and growing in tandem with me, pressing outwards through the pupil, the smarter, truer, almost bursting reason for our wrath. Do not doubt me. Just look. Watch us rise as the sun comes up over the beauty. The future stains the bleakness so pink. When my violence subsides, we will have nothing, and be champions.Marney Honeycutt is twelve years old, a factory worker, and lustertouched. She works in the Yann I. Chauncey Ichorite Foundry in Ignavia City, alongside her family and her best friend, shaping the magical metal ichorite into the valuable industrial products of a new age of commerce and industry. She is the oldest of the lustertouched, the children born to factory workers and poisoned by the metal. It has made her allergic, prone to fits at any contact with ichorite, but also able to exert a strange control over the metal if she's willing to pay the price of spasms and hallucinations for hours afterwards. As Metal from Heaven opens, the workers have declared a strike. Her older sister is the spokesperson, demanding shorter hours, safer working conditions, and an investigation into the health of the lustertouched children. Chauncey's response is to send enforcer snipers to kill the workers, including the entirety of her family.
The girl sang, "Unalone toward dawn we go, toward the glory of the new morning." An enforcer shot her in the belly, and when she did not fall, her head.Marney survives, fleeing into the city, swearing an impossible personal revenge against Yann Chauncey. An act of charity gets her a ticket on a train into the countryside. The woman who bought her ticket is a bandit who is on the train to rob it. Marney's ability to control ichorite allows her to help the bandits in return, winning her a place with the Highwayman's Choir who have been preying on the shipments of the rich and powerful and then disappearing into the hills. The Choir's secret is that the agoraphobic and paranoid Baron of the Fingerbluffs is dead and has been for years. He was killed by his staff, Hereafterist idealists, who have turned his remote territory into an anarchist commune and haven for pirates and bandits. This becomes Marney's home and the Choir becomes her family, but she never forgets her oath of revenge or the childhood friend she left behind in the piles of bodies and to whom this story is narrated. First, Clarke's writing is absolutely gorgeous.
We scaled the viny mountain jags at Montrose Barony's legal edge, the place where land was and wasn't Ignavia, Royston, and Drustland alike. There was a border but it was diffuse and hallucinatory, even more so than most. On legal papers and state maps there were harsh lines that squashed topography and sanded down the mountains into even hills in planter's rows, but here among the jutting rocks and craggy heather, the ground was lineless.The rhythm of it, the grasp of contrast and metaphor, the word choice! That climactic word "lineless," with its echo of limitless. So good. Second, this is the rarest of books: a political fantasy that takes class and religion seriously and uses them for more than plot drivers. This is not at all our world, and the technology level is somewhat ambiguous, but the parallels to the Gilded Age and Progressive Era are unmistakable. The Hereafterists that Marney joins are political anarchists, not in the sense of alternative governance structures and political theory sanitized for middle-class liberals, but in the sense of Emma Goldman and Peter Kropotkin. The society they have built in the Fingerbluffs is temporary, threatened, and contingent, but it is sincere and wildly popular among the people who already lived there. Even beyond politics, class is a tangible force in this book. Marney is a factory worker and the child of factory workers. She barely knows how to read and doesn't magically learn over the course of the book. She has friends who are clever in the sense rewarded by politics and nobility, who navigate bureaucracies and political nuance, but that is not Marney's world. When, towards the end of the book, she has to deal with a gathering of high-class women, the contrast is stark, and she navigates that gathering only by being entirely unexpected. Perhaps the best illustration of the subtlety of this is the terminology in the book for lesbian. Marney is a crawly, which is a slur thrown at people like her (and one of the rare fictional slurs that work exactly as the author intended) but is also simply what she calls herself. Whether or not it functions as a slur depends on context, and the context is never hard to understand. The high-class lesbians she meets later are Lunarists, and react to crawly as a vile and insulting word. They use language to separate themselves from both the insult and from the social class that uses it. Language is an indication of culture and manners and therefore of morality, unlike deeds, which admit endless justifications.
Conversation was fleeting. Perdita managed with whomever stood near her, chipper about every prettiness she saw, the flitting butterflies, the dappled light between the leaves, the lushness and the fragrance of untamed land, and her walking companions took turns sharing in her delight. It was infectious, how happy she was. She was going to slaughter millions. She was going to skip like this all the while.The handling of religion is perhaps even better. Marney was raised a Tullian, which sits alongside two other fleshed-out fictional religions and sketches of several more. Tullians tend to be conservative and patriarchal, and Marney has a realistically complicated relationship with faith: sticking with some Tullian worship practices and gestures because they're part of who she is, feeling a kinship to other Tullians, discarding beliefs that don't fit her, and revising others. Every major religion has a Hereafterist spin or reinterpretation that upends or reverses the parts of the religion that were used to prop up the existing social order and brings it more in line with Hereafterist ideals. We see the Tullian Hereafterist variation in detail, and as someone who has studied a lot of methods of reinterpreting Christianity, I was impressed by how well Clarke invents both a belief system and its revisionist rewrite. This is exactly how religions work in human history, but one almost never sees this subtlety in fantasy novels. Marney's allergy to ichorite causes her internal dialogue to dissolve into hallucinatory synesthesia when she's manipulating or exposed to it. Since that's most of the book, substantial portions read like drug trips with growing body horror. I normally hate this type of narration, so it's a sign of just how good Clarke's writing is that I tolerated it and even enjoyed parts. It helps that the descriptions are irreverent and often surprising, full of unexpected metaphors and sudden turns. It's very hard not to quote paragraph after paragraph of this book. Clarke is also doing a lot with gender that I don't feel qualified to comment in detail on, but it would not surprise me to see this book in the Otherwise Award recommendation list. I can think of three significant male characters, all of whom are well-done, but every other major character is female by at least some gender definition. Within that group, though, is huge gender diversity of the complicated and personal type that doesn't force people into defined boxes. Marney's sexuality is similarly unclassified and sometimes surprising. My one complaint is that I thought the sex scenes (which, to warn, are often graphic) fell into the literary fiction trap of being described so closely and physically that it didn't feel like anyone involved was actually enjoying themselves. (This is almost certainly a matter of personal taste.) I had absolutely no idea how Clarke was going to end this book, and the last couple of chapters caught me by surprise. I'm still not sure what I think about the climax. It's not the ending that I wanted, but one of the merits of this book is that it never did what I thought I wanted and yet made me enjoy the journey anyway. It is, at least, a genre ending, not a literary ending: The reader gets a full explanation of what is going on, and the setting is not static the way that it so often is in literary fiction. The characters can change the world, for good or for ill. The story felt frustrating and incomplete when I first finished it, but I haven't stopped thinking about this book and I think I like the shape of it a bit more now. It was certainly unexpected, at least by me. Clarke names Dhalgren as one of their influences in the acknowledgments, and yes, Metal from Heaven is that kind of book. This is the first 2024 novel I've read that felt like the kind of book that should be on award shortlists. I'm not sure it was entirely successful, and there are parts of it that I didn't like or that weren't for me, but it's trying to do something different and challenging and uncomfortable, and I think it mostly worked. And the writing is so good.
She looked like a mythic princess from the old woodcuts, who ruled nature by force of goodness and faith and had no legal power.Metal from Heaven is not going to be everyone's taste. If you do not like literary fantasy, there is a real chance that you will hate this. I am very glad that I read it, and also am going to take a significant break from difficult books before I tackle another one. But then I'm probably going to try the Scapegracers series, because Clarke is an author I want to follow. Content notes: Explicit sex, including sadomasochistic sex. Political violence, mostly by authorities. Murdered children, some body horror, and a lot of serious injuries and death. Rating: 8 out of 10
Series: | Uncertain Sanctuary #2 |
Publisher: | Jenny Schwartz |
Copyright: | October 2020 |
Printing: | September 2024 |
ASIN: | B0DBX6GP8Z |
Format: | Kindle |
Pages: | 196 |
Series: | The Echo Archives #1 |
Publisher: | Orbit |
Copyright: | November 2024 |
ISBN: | 0-316-30364-X |
Format: | Kindle |
Pages: | 388 |
Rika frowned. "Resolve it? How?" "I have no idea." I couldn't keep my frustration from leaking through. "Might be that we have to delve deep into our own hearts to confront the unhealed wounds we've carried with us in secret. Might be that we have to say their names backward, or just close our eyes and they'll go away. Echoes never make any damned sense." Rika made a face. "We'd better not have to confront our unhealed wounds, or I'm leaving you to die."All of The Last Hour Between Worlds is told in the first person from Kem's perspective, but Rika is the best character in this book. Kem is a rather straightforward, dogged, stubborn protector; Rika is complicated, selfish, conflicted, and considerably more dynamic. The first obvious twist in her background I spotted so long before Kem found out that it was a bit frustrating, but there were multiple satisfying twists after that. As advertised in the blurb, there's a sapphic romance angle here, but it's the sort that comes from a complicated friendship and a lot of mutual respect rather than love at first sight. Some of their relationship conflict is driven by misunderstanding, but the misunderstanding happens before the novel begins, which means the reader doesn't have to sit through the bit where one yells at the characters for being stupid. It helps that the characters have something concrete to do, and that driving plot problem is multi-layered and satisfying. Each time the party falls through a layer of reality, it's mostly reset to the start of the book, but the word "mostly" is hiding a lot of subtlety. Given the clock at the start of each chapter and the blurb (if one read it), the reader can make a good guess that the plot problem will not be fully resolved until the characters fall quite deep into the Echoes, but the story never felt repetitive the way that some time loop stories can. As the characters gain more understanding, the problems change, the players change, and they have to make several excursions into the surrounding world. This is the sort of fantasy that feels a bit like science fiction. You're thrown into a world with a different culture and different rules that are foreign to the reader and natural to the characters. Part of the fun of reading is figuring out the rules, history, and backstory while watching the characters try to solve the puzzles they're faced with. The writing is good but not great. Characterization was good enough for a story primarily focused on action and puzzle-solving, but it was a bit lacking in subtlety. I think Caruso's strengths showed most in the world design, particularly the magic system and the rules followed by the Echo creatures. The excursions outside of the somewhat-protected house struck a balance between eeriness and comprehensibility that reminded me of T. Kingfisher or Sandman. The human politics were unfortunately less successful and rested on some tired centrist cliches. Thankfully, this was not the main point of the story. I should also warn that there is a lot of talk about babies. Kem's entire identity at the start of the novel, to the point of incessant monologue, is "new mother." This is not a perspective we get very often in fantasy, and Kem eventually finds a steadier balance between her bond with her daughter and the other parts of her life. I think some readers will feel very seen. But Caruso leans hard into maternal bonding. So hard. If you don't want to read about someone who is deliriously obsessed with their new child, you may want to skip this one. Right after I finished this book, I thought it was amazing. Now that I've had a few days to think about it, the lack of subtlety and the facile human politics brought it down a notch. I'm a science fiction reader at heart, so I loved the slow revelation of mechanics; the reader starts the story by knowing that Kem can "blink step" but not knowing what that means, and by the end of the story one not only knows but has opinions about its limitations, political implications, and interactions with other forms of magic. The Echo worlds are treated similarly, and this type of world-building is my jam. But the cost is that the human characters, particularly the supporting cast, don't get the same focus and therefore are a bit straightforward and obvious. The subplot with Dona Vandelle was particularly annoying. Ah well. Kem and Rika's relationship did work, and it's the center of the book. If you like fantasy mechanics but are a bit leery of fae stories because they feel too symbolic or arbitrary, give this a try. It's the most satisfyingly constructed fae story that I've read in a long time. It's not great literary fiction, but it's also not trying to be; it's a puzzle adventure, and a well-executed one. Recommended, and I will definitely be reading the sequel. Content notes: Lots of violent death and other physical damage, creepy dream worlds with implied but not explicit horror, and rather a lot of blood. Followed by The Last Soul Among Wolves, not yet published at the time I wrote this review. Rating: 8 out of 10
package Foo;
use v5.40;
use Moose;
has 'attribute' => (
is => 'ro',
isa => 'Str',
required => 1
);
sub say_something
my $self = shift;
say "Hello there, our attribute is " . $self->attribute;
The above is a class that has a single attribute called attribute
.
To create an object, you use the Moose constructor on the class, and
pass it the attributes you want:
use v5.40;
use Foo;
my $foo = Foo->new(attribute => "foo");
$foo->say_something;
(output: Hello there, our attribute is foo
)
This creates a new object with the attribute attribute
set to bar
.
The attribute
accessor is a method generated by Moose, which functions
both as a getter and a setter (though in this particular case we made
the attribute "ro", meaning read-only, so while it can be set at object
creation time it cannot be changed by the setter anymore). So yay, an
object.
And it has methods, things that we set ourselves. Basic OO, all that.
One of the peculiarities of perl is its concept of "lists". Not to be
confused with the lists of python -- a concept that is called "arrays"
in perl and is somewhat different -- in perl, lists are enumerations of
values. They can be used as initializers for arrays or hashes, and they
are used as arguments to subroutines. Lists cannot be nested; whenever a
hash or array is passed in a list, the list is "flattened", that is, it
becomes one big list.
This means that the below script is functionally equivalent to the above
script that uses our "Foo" object:
use v5.40;
use Foo;
my %args;
$args attribute = "foo";
my $foo = Foo->new(%args);
$foo->say_something;
(output: Hello there, our attribute is foo
)
This creates a hash %args
wherein we set the attributes that we want
to pass to our constructor. We set one attribute in %args
, the one
called attribute
, and then use %args
and rely on list flattening to
create the object with the same attribute set (list flattening turns a
hash into a list of key-value pairs).
Perl also has a concept of "references". These are scalar values that
point to other values; the other value can be a hash, a list, or another
scalar. There is syntax to create a non-scalar value at assignment time,
called anonymous references, which is useful when one wants to remember
non-scoped values. By default, references are not flattened, and this
is what allows you to create multidimensional values in perl; however,
it is possible to request list flattening by dereferencing the
reference. The below example, again functionally equivalent to the
previous two examples, demonstrates this:
use v5.40;
use Foo;
my $args = ;
$args-> attribute = "foo";
my $foo = Foo->new(%$args);
$foo->say_something;
(output: Hello there, our attribute is foo
)
This creates a scalar $args
, which is a reference to an anonymous
hash. Then, we set the key attribute
of that anonymous hash to bar
(note the use arrow operator here, which is used to indicate that we
want to dereference a reference to a hash), and create the object using
that reference, requesting hash dereferencing and flattening by using a
double sigil, %$
.
As a side note, objects in perl are references too, hence the fact that
we have to use the dereferencing arrow to access the attributes and
methods of Moose objects.
Moose attributes don't have to be strings or even simple scalars. They
can also be references to hashes or arrays, or even other objects:
package Bar;
use v5.40;
use Moose;
extends 'Foo';
has 'hash_attribute' => (
is => 'ro',
isa => 'HashRef[Str]',
predicate => 'has_hash_attribute',
);
has 'object_attribute' => (
is => 'ro',
isa => 'Foo',
predicate => 'has_object_attribute',
);
sub say_something
my $self = shift;
if($self->has_object_attribute)
$self->object_attribute->say_something;
$self->SUPER::say_something unless $self->has_hash_attribute;
say "We have a hash attribute!"
This creates a subclass of Foo
called Bar
that has a hash
attribute called hash_attribute
, and an object attribute called
object_attribute
. Both of them are references; one to a hash, the
other to an object. The hash ref is further limited in that it requires
that each value in the hash must be a string (this is optional but can
occasionally be useful), and the object ref in that it must refer to an
object of the class Foo
, or any of its subclasses.
The predicates
used here are extra subroutines that Moose provides if
you ask for them, and which allow you to see if an object's attribute
has a value or not.
The example script would use an object like this:
use v5.40;
use Bar;
my $foo = Foo->new(attribute => "foo");
my $bar = Bar->new(object_attribute => $foo, attribute => "bar");
$bar->say_something;
(output: Hello there, our attribute is foo
)
This example also shows object inheritance, and methods implemented in
child classes.
Okay, that's it for perl and Moose basics. On to...
Bar
package, we could use coercion to
eliminate one object creation step from the creation of a Bar
object:
package "Bar";
use v5.40;
use Moose;
use Moose::Util::TypeConstraints;
extends "Foo";
coerce "Foo",
from "HashRef",
via Foo->new(%$_) ;
has 'hash_attribute' => (
is => 'ro',
isa => 'HashRef',
predicate => 'has_hash_attribute',
);
has 'object_attribute' => (
is => 'ro',
isa => 'Foo',
coerce => 1,
predicate => 'has_object_attribute',
);
sub say_something
my $self = shift;
if($self->has_object_attribute)
$self->object_attribute->say_something;
$self->SUPER::say_something unless $self->has_hash_attribute;
say "We have a hash attribute!"
Okay, let's unpack that a bit.
First, we add the Moose::Util::TypeConstraints
module to our package.
This is required to declare coercions.
Then, we declare a coercion to tell Moose how to convert a HashRef
to
a Foo
object: by using the Foo
constructor on a flattened list
created from the hashref that it is given.
Then, we update the definition of the object_attribute
to say that it
should use coercions. This is not the default, because going through the
list of coercions to find the right one has a performance penalty, so if
the coercion is not requested then we do not do it.
This allows us to simplify declarations. With the updated Bar
class,
we can simplify our example script to this:
use v5.40;
use Bar;
my $bar = Bar->new(attribute => "bar", object_attribute => attribute => "foo" );
$bar->say_something
(output: Hello there, our attribute is foo
)
Here, the coercion kicks in because the value object_attribute
, which
is supposed to be an object of class Foo
, is instead a hash ref.
Without the coercion, this would produce an error message saying that
the type of the object_attribute
attribute is not a Foo
object. With
the coercion, however, the value that we pass to object_attribute
is
passed to a Foo constructor using list flattening, and then the
resulting Foo
object is assigned to the object_attribute
attribute.
Coercion works for more complicated things, too; for instance, you can
use coercion to coerce an array of hashes into an array of objects, by
creating a subtype first:
package MyCoercions;
use v5.40;
use Moose;
use Moose::Util::TypeConstraints;
use Foo;
subtype "ArrayOfFoo", as "ArrayRef[Foo]";
subtype "ArrayOfHashes", as "ArrayRef[HashRef]";
coerce "ArrayOfFoo", from "ArrayOfHashes", via [ map Foo->create(%$_) @ $_ ] ;
Ick. That's a bit more complex.
What happens here is that we use the map
function to iterate over a
list of values.
The given list of values is @ $_
, which is perl for "dereference the
default value as an array reference, and flatten the list of values in
that array reference".
So the ArrayRef
of HashRef
s is dereferenced and flattened, and each
HashRef
in the ArrayRef is passed to the map
function.
The map function then takes each hash ref in turn and passes it to the
block of code that it is also given. In this case, that block is
Foo->create(%$_)
. In other words, we invoke the create
factory
method with the flattened hashref as an argument. This returns an object
of the correct implementation (assuming our hash ref has a type
attribute set), and with all attributes of their object set to the
correct value. That value is then returned from the block (this could be
made more explicit with a return
call, but that is optional, perl
defaults a return value to the rvalue of the last expression in a
block).
The map
function then returns a list of all the created objects, which
we capture in an anonymous array ref (the []
square brackets), i.e.,
an ArrayRef of Foo object, passing the Moose requirement of
ArrayRef[Foo]
.
Usually, I tend to put my coercions in a special-purpose package.
Although it is not strictly required by Moose, I find that it is
useful to do this, because Moose does not allow a coercion to be defined
if a coercion for the same type had already been done in a different
package. And while it is theoretically possible to make sure you only
ever declare a coercion once in your entire codebase, I find that doing
so is easier to remember if you put all your coercions in a specific
package.
Okay, now you understand Moose object coercion! On to...
my $module = "Foo";
eval "require $module";
This loads "Foo" at runtime. Obviously, the $module string could be a
computed value, it does not have to be hardcoded.
There are some obvious downsides to doing things this way, mostly in the
fact that a computed value can basically be anything and so without
proper checks this can quickly become an arbitrary code vulnerability.
As such, there are a number of distributions on
CPAN to help you with the low-level stuff of
figuring out what the possible modules are, and how to load them.
For the purposes of my script, I used
Module::Pluggable. Its API
is fairly simple and straightforward:
package Foo;
use v5.40;
use Moose;
use Module::Pluggable require => 1;
has 'attribute' => (
is => 'ro',
isa => 'Str',
);
has 'type' => (
is => 'ro',
isa => 'Str',
required => 1,
);
sub handles_type
return 0;
sub create
my $class = shift;
my %data = @_;
foreach my $impl($class->plugins)
if($impl->can("handles_type") && $impl->handles_type($data type ))
return $impl->new(%data);
die "could not find a plugin for type " . $data type ;
sub say_something
my $self = shift;
say "Hello there, I am a " . $self->type;
The new concept here is the plugins
class method, which is added by
Module::Pluggable
, and which searches perl's library paths for all
modules that are in our namespace. The namespace is configurable, but by
default it is the name of our module; so in the above example, if there
were a package "Foo::Bar" which
handles_type
type
key in
a hash that is passed to the create
subroutine,create
subroutine creates a new object with the passed
key/value pairs used as attribute initializers.Foo::Bar
package:
package Foo::Bar;
use v5.40;
use Moose;
extends 'Foo';
has 'type' => (
is => 'ro',
isa => 'Str',
required => 1,
);
has 'serves_drinks' => (
is => 'ro',
isa => 'Bool',
default => 0,
);
sub handles_type
my $class = shift;
my $type = shift;
return $type eq "bar";
sub say_something
my $self = shift;
$self->SUPER::say_something;
say "I serve drinks!" if $self->serves_drinks;
We can now indirectly use the Foo::Bar
package in our script:
use v5.40;
use Foo;
my $obj = Foo->create(type => bar, serves_drinks => 1);
$obj->say_something;
output:
Hello there, I am a bar.
I serve drinks!
Okay, now you understand all the bits and pieces that are needed to
understand how I created the DSL engine. On to...
create
factory method in the
last version of our Foo
package allows us to decide at run time which
module to instantiate an object of, and to load that module at run time.
We can use coercion and list flattening to turn a reference to a hash
into an object of the correct type.
We haven't looked yet at how to turn a JSON data structure into a hash,
but that bit is actually ridiculously trivial:
use JSON::MaybeXS;
my $data = decode_json($json_string);
Tada, now $data is a reference to a deserialized version of the JSON
string: if the JSON string contained an object, $data is a hashref; if
the JSON string contained an array, $data is an arrayref, etc.
So, in other words, to create an extensible JSON-based DSL that is
implemented by Moose objects, all we need to do is create a system that
Module::Pluggable
to find the available object classes, andtype
attribute to figure out which object class to use
to create the object
"description": "do stuff",
"actions": [
"type": "bar",
"serves_drinks": true,
,
"type": "bar",
"serves_drinks": false,
]
... and then we could have a Moose object definition like this:
package MyDSL;
use v5.40;
use Moose;
use MyCoercions;
has "description" => (
is => 'ro',
isa => 'Str',
);
has 'actions' => (
is => 'ro',
isa => 'ArrayOfFoo'
coerce => 1,
required => 1,
);
sub say_something
say "Hello there, I am described as " . $self->description . " and I am performing my actions: ";
foreach my $action(@ $self->actions )
$action->say_something;
Now, we can write a script that loads this JSON file and create a new
object using the flattened arguments:
use v5.40;
use MyDSL;
use JSON::MaybeXS;
my $input_file_name = shift;
my $args = do
local $/ = undef;
open my $input_fh, "<", $input_file_name or die "could not open file";
<$input_fh>;
;
$args = decode_json($args);
my $dsl = MyDSL->new(%$args);
$dsl->say_something
Output:
Hello there, I am described as do stuff and I am performing my actions:
Hello there, I am a bar
I am serving drinks!
Hello there, I am a bar
In some more detail, this will:
MyDSL
class;MyDSL
class then uses those arguments to set its attributes,
using Moose coercion to convert the "actions" array of hashes into an
array of Foo::Bar
objects.say_something
method on the MyDSL
objectFoo::Quux
class, making sure it has a method
handles_type
that returns a truthy value when called with quux
as
the argument, and installing it into the perl library path. This is
rather easy to do.
It can even be extended deeper, too; if the quux
type requires a list
of arguments rather than just a single argument, it could itself also
have an array attribute with relevant coercions. These coercions could
then be used to convert the list of arguments into an array of objects
of the correct type, using the same schema as above.
The actual DSL is of course somewhat more complex, and also actually
does something useful, in contrast to the DSL that we define here which just
says things.
Creating an object that actually performs some action when required is
left as an exercise to the reader.
Next.