I was born in California and moved to the Pacific Northwest in 1997. In 2008, I began attending Western Washington University, where I graduated with a Bachelor of Music in 2013 and a Master of Music in 2015, both for Music Composition. I worked for two years as a program coordinator for the Music Department, then worked as an instructor of music theory for two more years. During this time I also worked as a composer/arranger, writing music for games, short & feature-length films, dance, and performers & ensembles such as the WWU Wind Symphony, Equus Sax Quartet, Bellingham Chamber Music Society, Whatcom Wind Ensemble, Jostaberry, and more. My music has been featured at film festivals across the US, most prominently in the cult horror films Hunting Grounds (Valley of the Sasquatch outside the US) and The Last Laugh.
In 2019, I finally came to grips that the hustle of a career in music wasn't for me, so after many years of programming as a hobbyist, I began formally training remotely in software development at Lambda School (now Bloom Institute of Technology). I quickly developed a reputation for being a sharp, detailed programmer and a voracious learner, able to adapt swiftly (no pun intended) to new technologies, architectures, and environments. Since January 2021, I've been with Redzone Software (now QAD | Redzone), as an engineer, team lead, and now as the Principal iOS Engineer.
With 15 years of programming experience, I specialize in crafting keen architectural solutions for iOS, macOS, and other apps in a remote work environment. I have programmed apps, games, and websites using Swift, Objective-C, Scala, Typescript, Rust, C#, Lua, Python, Go, and more.
I currently live with my partner, four cats, and a dog in Bellingham, WA.
A long-time natively remote engineer working on worldwide teams, I can help you achieve your coding goals no matter where you are!
...and more!
Note: I am not currently available for music or sound work (except in rare circumstances on a case-by-case basis).
Anna plopped down lazily in her chair. The hand-carved legs screeched softly against the rough floor. She tossed her canvas bag under the table.
Nick followed, smiling as he gently placed her caramel macchiato. He noted that it was the Starbucks kind—not a real macchiato.
"You know," Nick said, "I have a few friends who say this is their favorite coffee shop in the country." He watched her expectantly as he slowly lowered into his seat, maintaining a stable grip on his Ethiopian pour-over.
"No shit?" She looked up briefly and smiled with her eyebrows, before returning to her phone.
"Shit," he smirked.
"Huh?" She looked up again briefly.
"Oh, no, I was just, uh," he stammered. "Never mind."
Again she looked up absent-mindedly. "What's up?"
"Got a text?" he asked.
"Oh, no, just reading Reddit arguments about fucking transporters again. God, I need to get one of those minimalist phones or something; I'm addicted to this stupid thing." She leaned down and tossed her phone into her bag.
He let out half of a half-nervous chuckle. "I know what you mean. Even in the Valley a lot of folks are getting fuckin' flip phones, can you believe that?"
"Oh my god, do you remember that Motorola phone that was all the rage?"
Nick was mid-sip of his black coffee. He stared back at Anna blankly.
Anna blinked. "God, what was it called—the Razr!"
Nick narrowed his eyes and blinked. "Didn't that just come out like, a few years ago?"
Anna narrowed her eyes and did not blink. "What? No, the super-thin flip phone when we were kids! Everyone wanted it, rich kids had it."
Nick put his coffee down, squinting past her as if the phone in question was floating behind her. "I gotta be honest, I have no idea what you're talking about. But hey, how's work going for you?"
Only now did Anna blink. "Oh, it's... yeah, it's work. Rewarding usually, often, sometimes. I like sticking up for my employees. People can be assholes, though."
Nick gave a slow, almost shrug-like nod. "Yeah. Yeah. I can imagine. Yeah."
"Like..." Anna teetered at the edge of a work tirade, but decided against it for now. "...Anyways, yeah, it's fine, how's yours?"
"Oh, it's great. Honestly, after working remotely for so long, it's nice to work with folks in real life again." Nick glowed.
"Oh, that's cool? I remember you were so excited to go remote, that's funny."
"Yeah, I mean, it has its benefits, but the worst part was the commute and—um... yeah, it's just nice to, like, see the people I'm working with every day, to have an actual water cooler to chat at. Or rather, a kitchen."
Anna rolled her eyes with a smile. "God, yeah, it really is nice to be able to coax the chef to throw something on for me. I can't imagine going to work without that."
Nick laughed softly. "I can't imagine going to work with that."
Anna glowered a little as she sipped her drink. "Well, good luck dealing with the rest of my job."
Nick let out the second half of his half-nervous chuckle. "Yeah, for sure..."
"So, is your commute just shorter now, or did you learn to live with it, or...?"
Nick choked a bit on his coffee. "Yeah, it's, uh, a lot shorter, yeah, just right around the corner."
"Wow, how'd you swing that? The rent must be astronomical!"
"It's actually not bad at all, we moved back up to Washington. We just really missed it."
Anna's face brightened. "Ah, Nick! That's..." Anna's face darkened. "Where the heck are you living that has work just around the corner for you?"
Nick scratched his neck. "Well, we're living in Centralia; I wanted to be closer to the meditation center, I just think it's really good for me to be able to get out there more regularly, and their latest guidelines discourage, um..."
Anna paused a moment. She lowered her chin and raised her eyebrows knowingly. "And the office?"
Nick hesitated for several seconds. "The office is in Madison, Wisconsin." The hip, indie chilltronica playlist hit an uncomfortably long gap, and the din of the busy cafe seemed to quiet.
Anna sighed. "Nicholas..."
"Look," he shot back quietly, "I know where you've landed on this, can we just move on and talk about something else?"
Anna shook her head and looked down at her drink. "Dude..."
"Come on Anna, it's pretty damn common in the industry these days. Most companies have a budget for it because it's improving things so much."
She glanced halfway up at him, squinting skeptically. "Is it, though?"
Nick tried incredulously to meet her avoidant gaze. "Yeah! The company I work for gets a cheap office in the middle of nowhere and gets the best talent from around the globe, we all get to live where we want to. Win-win!"
"Oh, wow!" Anna leaned back, clasped her hands beside her head, and fluttered her eyelashes dramatically. "So you're the best talent from around the globe, how special you must be!"
"Oh, fuck off," Nick spat back. "God, I knew you'd be like this. You know, most of my friends aren't so judgmental."
Anna glared back. "Yeah, well, apparently they're not very good friends."
Nick's face softened. "What's even your problem with it? Like, I've read so many articles and no one's arguments are actually making any technologically-sound points—"
"Are you reading people's actual points, or just responses to those points so you know how to argue about it?" she interrupted.
Nick recoiled. "Anna, what is your actual beef here?"
She shrugged, scoffed, shook her head, and let her hand fall to the table. "It's... I'm not even arguing with you." She looked away as her eyes began to water.
He paused unflinchingly. "What are you talking about?"
She carefully dabbed under her eyes to prevent her makeup from running, her voice quieter now. "Oh come on, you know the whole thing, don't play dumb—"
"Anna," Nick interrupted, chuckling. "It's obviously me. I've got my memories, I didn't die— just because I'm not, like—" he paused and shook his hands "literally made up of the same matter as when we were kids. Neither are you—!"
"Yeah, yeah, every atom in our body is replaced every seven years, blah blah blah," Anna recited, rolling her eyes. "Nick, your atoms don't naturally get replaced all at once, several times a week."
"I mean, at this point it's, like, several times a day for lunch, and if I forgot something from home, but like—"
"NICK." She surprised herself with her own volume.
Nick whispered loudly in an attempt to hush the conversation. "But like! I'm fine! No bugs! Not even a mysterious mark on my skin, no tumors—"
"I would fucking hope not," Anna interrupted under her breath.
"Anna I'm fine!"
"How would you know?"
Nick's eyes narrowed. "What do you mean?"
Anna gestured aimlessly with her hands. "Like—okay how about this, what about what happened in fucking Munich, or what if someone vandalized the transporter at the bus station—"
"Teleporter, not transporter, Anna, and Munich was four years ago, they've got literally the best safety in the world now," Nick interrupted, recalling the wording he'd practiced. "Airplanes used to be the safest method of travel. Last year there were twenty deaths caused by airplanes. Do you know how many were caused by teleporters?"
"No, but it sure sounds like you do."
"Zero. Zero deaths."
"Oh, wow, you've convinced me, all hail our corporate overlords." Anna rolled her eyes.
"Could you quit that?" Nick raised his voice.
"What?!"
"Your eyes, you always do that when—" Nick stopped and sighed. "Anna, can we please talk about something else? How's Sarah?"
Anna looked down at her drink. "Yeah, Sarah's doing great, Nick—look, it's just..." She closed her eyes and took a breath. "I've known you a long time..."
"I thought it wasn't even me?" Nick shot back.
Anna ignored that. "Look, I haven't mentioned this until now, but apparently I should have..." She looked at her bag under the table, as if maybe the right words could be found in her phone. "Ever since you moved to California... it's like every time I talk to you it's a little bit less... you. Like, your whole vibe has been shifting. And now..." She shook her head.
Nick maintained his blank stare at Anna as her eyes shifted around the room.
Anna's eyes finally met his. "Nick, why'd you even reach out to me? We have nothing in common anymore."
Nick seemed to look through her. "I just..." His eyes fell. "It's... it's nothing." He sat back, his shoulders slumping. "I just... wanted to let you know in person." He picked up his coffee and forced a smile. "Marcy's pregnant." He took a last sip.
Anna's expression didn't change at first. She gradually forced the slightest smile of her own. "Oh my god, congrats, Nick. Really, that's... that's awesome," she nodded and let herself relax a little. "When's she due?"
"Uh, in a few months." Nick's mug was empty now, but he picked it up and pretended to sip from it anyways.
Anna gave an almost imperceptible nod and forced her smile to become a little more genuine. "That's really cool. She's going to be a really good mom, I bet."
Nick nodded. Anna nodded. They both looked at their own empty mugs, and pretended to sip from them, each without noticing the other was doing the same.
Anna's phone dinged softly, barely audible over the cacophony of steaming milk, clanking dishes, and synth-pop. She eagerly snatched it from her bag and gazed into it, barely taking in the contents.
Nick stood up. "Anyways, um... I'm gonna go. I... lied when I said I was in town." He turned his head out the window towards the bus station across the street. "The bus station here has a terminal now, and... I didn't think you'd meet up otherwise..." He couldn't look now.
Anna didn't stand. She released her smile and picked up her empty drink again. "Ah. Of course."
Once he was was confident enough that she wouldn't look up at him judgingly, he glanced down at her. "Anna, it was really nice to see you. I just want you to know, you were always a really good friend." He smiled stiffly.
Anna finished her sip, her expression unchanging, turning towards the cafe kitchen, contemplating ordering a muffin. "Whatever man, alright, good to see you I guess."
Nick looked at her for longer than she was comfortable with. He turned slowly towards the door and walked out.
Only then did Anna look back towards him. Her eyes followed him out the window as he crossed the street. She wondered if she would ever see him again. She wondered if she had really seen him today.
I think it's been about an hour since I flipped the lights in the capsule off. My sense of time is... well, what could I say about it that would actually mean anything? It's deteriorated. It's bad. It's completely fucked.
It's been... eight rations since I woke up? Maybe five since I screamed until my throat was raw. I fell asleep at one point. I think I ate another ration when the hunger became too much to bear, even after I vomited up the previous one on what passed for my bed sheets.
How long could the rations and life support sustain me? A couple months at most? What would it be sustaining, anyways? A physical anomaly in what seemed to quite literally be a boundless void. A conscious flesh bag living in a floating metal box.
It's hard to say that my eyes have adjusted to the black; the space seems blacker than my waning mind can comprehend. Given the lack of stars, not even the window offers any ambient light.
Shapes manifest in the darkness; the line between memory and vision loses any semblance of meaning. My life doesn't flash before my eyes, per se — this isn't going to be a quick death, after all — more of an interminable highlight reel.
The orphanage. Trevor, my tormentor. The day I met Mom. My first bedroom (how I long for the clean-but-mildewy scent of the sheets that night). Kit-Kat pawing at my face before settling in and purring me to sleep. My first day at school, learning about the exosphere industry. Daniela, my first heartbreak. The Great San Juan Earthquake. Finding Kit-Kat's body. Mom's funeral.
University... wedding... divorce... rehab... academy... dates with Kat (and the jokes we made about dating my dead cat)... my first exo-trip (thinking the planet looked a lot more grey and brown than I expected from up there)... more heartbreak...
And then the fucking pyxis.
I sit up in the blackness and feel my way out of the "bed" (a slab of light cushioning on hard plastic) into the cockpit, just to give my creaky bones some other posture to rest in, seeking the slightest semblance of comfort. I close my eyes in an effort to stop the thoughts, but they keep coming. The self-flagellation is apparently preferable to the emptiness.
Why in god's name did I volunteer? I guess I always had a knack for... I don't know, being annoyed when no one would do what needed to be done. I always was the one that would just do the assignment while others sat on their asses and waited for orders. And like, I get it, but... well, nothing like rogue exotic matter approaching the solar system to bring the existential hysteria, I guess.
I always knew there was a chance I wouldn't be coming home from this trip. What was home, anyways? The only home I'd ever had was torn apart by the earth and washed away by the sea. I had nothing and no one to lose. Wormhole or dark matter black hole or what-the-fuck-ever, let's go find out.
I could handle the days of isolation punctuated by alternating, scheduled check-ins with Briar and Stills at Control; I had a virtually bottomless collection of books to keep me company (old cyberpunk mostly; Holdo the Hacker was fluffy and fun).
I should have paid more mind to the blip in the sensors. And to the anomalous acceleration. I should have predicted an anomaly would become an emergency before I could even register what was happening. I remember hearing Briar's voice: "We're working on aaaaaa"— the pitch dropping like a record player slowly spinning to a stop, as I was flattened into my seat, like a giant mattress slamming against my body, my consciousness fading away.
I woke up to the same old cockpit. But there was nothing outside. Nothing, in an absolute sense. I don't just mean like, none of the familiar solar system. I mean no asteroids. No planets. No sun or stars. No distant nebulae.
The temperature sensors didn't register anything. No stray radiation or nanostructures. The clock suggested it'd been 117 minutes since the acceleration anomaly.
I thought back to school, and to my work on the exostructures, the arguments we'd have about exotic matter, two-way versus one-way bridges, time dilation. I wished I could see their stupid, smug, laughing faces.
I don't know if the pyxis had drawn in anyone or anything else. Aliens? Trans-dimensional beings? Our sensors had never suggested any kind of life.
I scanned for something, anything else. I didn't bother to count how many times.
Nothing. No one. None of the probes we'd shot at it, no light, no matter of any kind. Not even the pyxis remained.
I turn my attention to where I know the window to be. My throat burns. I remember my first and only cigarette; an offering from one of many heartbreaks. I found it disgusting at the time, but strangely (but perhaps understandably) I find myself wishing I had one now. I can almost see the wisps of smoke as I cough.
I squint. I see something. My attention first stays with my throat. The life support shutting off is probably starting to affect me. But no, that's not what's odd. I turn towards where I know the controls to be. I turn back to the window.
I claw my way out of the seat, limping over the junk I've left lying around, crawling to the cushion by the window. A red glow softly smudges my vision. I can't make out the source. I close my eyes, and it vanishes. I take in a deep breath. It takes a surprising amount of effort to find the air. I open my eyes.
There. The thinnest glimmer, the faintest ember, more distant than I thought possible to make out. A single coal left of the campfire of the universe.
I lean my face against the freezing window, the condensation further blurring this singular vision. Maybe there is a way. Maybe there's another wormhole that somehow, in this stupid, impossible void, will get me back. Back to a cool set of sheets and a purring cat. Back to a bowl of ice cream. Back to a warm, naked body to fall asleep next to. Back to a bottle of whiskey. Back to the crumbling rock that birthed me. Back to Briar and Stills and the stupid raises and promotions they're always blabbing about so they can upgrade their shitty living capsules and ditch me again—
The red spot begins to brighten for the briefest moment. And then, without sound, without any further warning, I'm blinded by a silent, impossibly bright flash of light, brighter than any brightness I've ever seen. Finally, this is it. Death. Nothing extravagant, no gates of heaven or rivers of hell. Just a nice little light show, and settling into nonexistence.
...I notice my throat again, still struggling to find air. I hear my breath quietly strafe across my dry lips. The cold metal grazes my skin.
Not dead. Still there. A blind bundle of flesh in a frozen metal box.
The last coal has gone out.
I recently released my first new track in over 5 years. A friend asked me what drew me back and... I realized I hadn't really thought much about it. So I wanted to think about it and write about it in case anyone else happened to be curious.
I never consciously decided to stop making music. For years after graduating with my Master's degree, I struggled to consistently put out new music. Often the music I did write, I was not all that happy with. I got hired for a couple of movies, I had a couple of discounted commissions from friends, I did some arranging. I never reached my goal writing anything for a commercial game.
I also taught music theory at my alma mater, where I struggled to find my footing, especially when I was thrown into teaching second year music theory. The way the curriculum was structured, there were right and wrong answers, but the way I wrote music, there was only "what effect does this have on the listener"; these were always neutral decisions. I'd gone so far afield in that brain zone that I struggled to find my way back to the structure of the modern study of Romantic music. I also realized that speaking to a room of late-teens and early-twenty-somethings was not how I wanted to be spending the rest of my working days. Not to mention, being adjunct faculty with no interest in pursuing a doctorate, I had zero possibility of anything resembling upward mobility; as a matter of fact, my chances of getting laid off were pretty staggering.
So I quit and decided to learn programming, and basically left behind music entirely. Not just in my professional life, but in my personal life as well. I was just so burnt out at trying to convince people that my music was worth paying money for, that I became burnt out on making it at all. It hadn't been bringing me joy, and it apparently (to my knowledge) hadn't been bringing others joy, so it just wasn't worth spending time on.1
In 2023 (I think?), my mentor and friend Bruce Hamilton reached out and asked if I'd be interested in playing some music with him. I was quite hesitant at first, but agreed; I missed seeing folks, and it seemed relatively low-pressure. It turned out to be even more fun than I had anticipated. Bruce had an established audience, he wrote rad music I could be proud to be a part of, I was playing with other skilled friends, the parts were within my skill level... I felt like I could finally have fun with music again.
I played as part of the Jostaberry live band for a couple of years. We recorded a number of tracks semi-live in the studio. I mostly played bass (something I hadn't done much since my time in my late high school band The Situation). We played live a few times. I even wrote a track or two (I might release one of them myself eventually...).
Then, I got a dog.
Now, I'd heard that dogs (especially puppies) were challenging, and I believed it, and thought I was well-prepared. But I hadn't actually had a dog myself since I was 4 years old. So when we brought Denny home, I was completely floored by how much energy it took to take care of him. If I could do it over again, I'd absolutely adopt him, but I do wish I could have prepared a little bit differently (see also: better). My time in Jostaberry had to come to a regrettably sudden conclusion.
...But I quickly started missing making music. I had fiddled around in DAWs several times over the years, trying to recapture the joy I initially felt making music, but I seemed to have trouble making anything I was happy with.
I have been in therapy off and on for many years now, having seen several different therapists to help with several different issues. One thing that I hadn't quite dealt with yet, though, was the attitude that I had around music making since I was a teenager.
I don't know how it came about exactly, but every time I approached a new song, I seemed to semi-consciously think to myself, "This is going to be my masterpiece. Twenty years from now, people are going to look back at my output and say, 'This was a turning point in her musical output.'" It was completely delusional. I always kept it to myself, but it shaped my entire attitude around the music-making progress. It was so high-stakes. This new string quartet, this new flute solo, this new electroacoustic miniature, this new pop song, this new horror soundtrack... every new piece I wrote would be The One.
So years later, when I was out of practice and had little creative time to myself, I would end up paralyzed with a level of perfectionism impossible to fulfill. "WHY CAN'T I MAKE A MASTERPIECE?!?" It's absurd in retrospect.
I can't point to any particular moment this shifted, but eventually I realized "...I just need to make something dumb." I made some short things I probably will never finish or put out there. I improvised on the piano. Learned Persona tracks and Kessoku Band songs. At some point I just... felt ready to write something simple I could share with folks. But I was pretty sure I'd have to force my own hand.
So I posted on Bluesky.
I will release a song in January.
— June Bash (@june.bash.pizza) December 26, 2025 at 1:29 PM
And I got to work.
I don't think "are you losing sleep?" is a masterpiece. For perhaps the first time, I didn't try to make it one. I don't even know if it's all that good. I don't really care. I feel freer, and I'm finally enjoying writing music again.
I don't say any of this in a sad attempt to try to get folks to reach out saying "omg I always loved your music I was so sad you stopped I'm so happy you're making it again" or "if I had money I would have hired you god fuck capitalism". In fact, please do not do that. This is just an honest illustration of where my head was at at the time.
I like predictions. I like wild predictions. Let's make some wild predictions about 2026!
These are going to be from all over my areas of interest. I will be jumping all over the place. I am not sorry.
I am basing this all entirely on vibes. I have no insider knowledge. This is just guessing based on what I'm seeing and on past experience.
Maybe it's SwiftUI. UIKit. I don't know, something is going to get open-sourced that hasn't been open-sourced before. This is probably just high grade copium.
Maybe EA or Epic or Bethesda or something. It'll be an "experiment". But they'll think it's really cool and release a big trailer at like the Game Awards or something and they'll be real proud that it was made with AI and it's coming in 2027. And the trailer will just be... utter garbage.
Or maybe they'll just surprise-release this experiment this year! And then we'll get to actually experience how dogshit it is!
I don't know what that barrier will look like; I keep hearing the term "inflection point" but I kinda hate it for the same reason I hate the word "synergy". But in any case, we'll have some kind of situation where suddenly AI will be able to do a bunch of stuff people didn't think possible before, and folks will be excited for like a month, and they'll be bracing for the next big thing... but it will never come. Ever. It'll all be done getting better. And then in 2027 the bubble will pop.
And at first people will doubt she has any chance, but she'll slowly win over more and more folks, even centrists who finally see that leaning more towards the left is a viable strategy.
He'll like, be in some stupid cheating scandal or something, and really entrenched Democrats will still support him for way too long.
We've got pourover, espresso, French press, etc. Someone will make some new novel way to make coffee that's kinda easy, and people will like it a lot.
Tea still feels like a bit of a niche. I think some celebrity will start talking about tea on TikTok and a bunch of folks will be really into it. It'll be hip.
This is already happening a little bit I think, but it'll reach new heights. You'll see Gen-Z-ers with physical notebooks, reading physical books, going no-phone, "raw-dogging" life. There will be like, ABC News features about it and shit.
And the world will laugh.
What are your dumb 2026 predictions? Tell me on BlueSky or whatever!
I wrote a poem. I've never really been one for poetry.
birthed from resentment
repeated and frantic
while waltzing through trenches
withholding compassionbeaches of glass
oh, what wicked companions
allay through their seething
extolling, expandedwhat love could be given
I've taken and stabbed at
you've held me back bravely
but strayed at the outsetthis barking of rage
responded with sadness
just one drop of shame
a gaze in the balance
(...I know the website footer says made with Claude, but not this. Art is never for AI.)
There's a common idea amongst a lot of my friends, peers, and colleagues that "AI" is and can only ever be bad. Given a lot of the bullshit that's been peddled by former NFT bros around AI, this is a reasonable conclusion to come to. However, I fear that we are losing track of some nuance in the conversation and throwing the baby out with the bathwater.
I've been using LLMs a decent amount over the past few weeks for a number of applications, and honestly, it's been extraordinarily useful in very practical ways. Although I still have some ethical concerns that I'll discuss, I feel pretty optimistic about it as a technology, and I'd like to share my experiences in the hopes that I can hit a non-centrist middle ground between "it's always evil" and "it's always good."
One area it's been helpful is for dictation. For years I have struggled with carpal tunnel and RSI. I've spent way too much money on special keyboards that should help alleviate it. They have helped but they have not eliminated it. For the past week or so I have been using LLM-powered dictation software, and it is changing my life for the better. There's other, non-AI dictation software out there, but from what I can tell none of it just works out of the box as well as the LLM-powered stuff. At first it made me feel like an early aughts douche talking to my computer, but it is actually a godsend for my wrists, and I hate to admit that.
I've also been occasionally asking an LLM questions in the more "traditional" chat-bot way. Whereas usually before I would've scoured a bunch of blogs on X software vs Y software, gone through Reddit reviews and product websites to try to figure out what would work best for me, now I can turn that into a single query and just have the LLM do that work for me. Is it going to give me "the right answer"? Probably not. Does that ultimately matter? No! Because the answer I would've arrived at doing it myself also might not have been the right answer because I'm susceptible to marketing bullshit as much as the next person, and I might just go for the company with the better marketing. When it's all put together in a little summary, it's a lot easier to see through the marketing bullshit. And because I get sources, I can double-check that whatever I end up choosing seems like a good fit, and not end up going down rabbit holes like I am so prone to do.
Most significantly I have been using LLMs a lot for coding. When Github Copilot came out with its LLM-based autocomplete, I gave it a whirl and was thoroughly unimpressed. It was, at best, marginally better than the fuzzy finder autocomplete we'd had for years. Then, it got a little bit better. I started using it occasionally, but most of the time I ignored it. Then I tried out Windsurf, an LLM-powered VSCode fork. I'd been wanting to rebuild my website for several years, and getting started has been an impossibility for me with ADHD and all the stuff going on in my life. Windsurf got the skeleton of a website rewrite ready in 30 minutes (with my help, obviously, but it did the hardest part for me (getting started)). I was very impressed; that's what convinced me to really give these tools a shot.
Then, I tried Claude Code, and for better and worse, I haven't looked back. That was a few weeks ago, and I've been using it in basically all of my coding projects. It's allowed me to take a step back from the code and view it from a higher level of abstraction, which is often something that I struggle to do when I'm deep in the details. Going back and forth between "deep in the details" and "higher-level overview" is probably the most difficult part of software development. Using Claude Code, which will just write large swaths of code for you, takes away a lot of that difficulty.
It's not perfect; it makes a lot of mistakes, there are a lot of instances where I have to hold its hand, and I read all of its output and test it manually. But I was doing that anyways, it's still saving time, it's saving cognitive load, and it lets me do a bit more multi-tasking.
The biggest surprise I've noticed while using LLms is how my own thinking has evolved. I first realized this as I read through the "thought" processes of "thinking" language models. I'd notice similar patterns and trains of thought in my own thinking, and it seemed to give me this interesting new perspective on my own thoughts. As a meditator, I find this kind of distance and perspective quite valuable. Maybe this is me falling for the groupthink, but I think, just maybe, my thinking is improving.
I talked already about how I'm able to think about code from a higher level of abstraction, but also with dictation, I'm able to think about things in a slightly different way. I can look around the room while I'm talking, just speaking my thoughts as they're coming to me. This isn't always better, but it is a different way of thinking. This sort of multi-modal thinking, writing by hand and speaking, feels like I'm able to see my thoughts from more angles and more clearly. Then, at the end of the day, I've found I'm able to communicate my thoughts more clearly.
There's a lot of places that I still refuse to use these large models. Any visual art or written prose where it's generating an end product that I had no hand in (beyond some vague idea of what I wanted it to look like), I find to be morally dubious at best. Art is an inherently human endeavor; it's about humans communicating with each other and laboring to create something beautiful for other humans to appreciate. Making a machine do all the labor completely defeats the purpose.
Now, there is art out there that didn't require a lot of labor, but it's still a human inherently putting in the work. There's just no point in art if it's not made by a person. I watch, for example, Studio Ghibli movies because I know that every one of those frames was created and decided upon by a person, with every line and every color carefully considered.
Tools are a different story though. Sometimes I'm just using a tool to make something that I can use for myself. Not every gadget in my house has to be artisanally hand-assembled by a human being. I would hope that they've gotten the process down really well for making them, but sometimes the things that I use every day were mostly made by a machine, and that's okay. I think we can think about LLMs in the same way a lot of the time.
There's also the question about what these machines were trained on. With art and writing, it's often going to be trained on copyrighted works owned by other people who did not consent to their art being used in this way. You could make arguments about fair use, but I'm not even going to touch that with a 15-foot pole. We should, flat-out, not be training our models on material which folks didn't consent to.
And then energy usage. This is an open question that I'm pretty darn concerned about. It's why I'm trying to somewhat limit my usage. People have made claims about how much energy LLMs use, but I don't think we actually know. There are a lot of things that we use that use a lot of energy. Computers, game consoles, cars, planes. That doesn't mean that we SHOULD use energy on LLMs, but I think we should weigh these things against each other when we talk about this stuff. (We also need LLM companies to be a lot more transparent about energy usage, and we need to keep demanding that.)
At the risk of hitting a cliche... when I hear people oppose the awful stuff that LLMs enable, much of the time, what they're ultimately lamenting is capitalism. If these technologies were all more open and transparent and for the benefit of everyone and not being sold to us by companies making billions of dollars, I think the conversations would probably be a lot different. I have truly come to believe that, when used with care and understanding, these technologies can benefit us. We just need to make sure we stay nuanced, remember who the real enemy is, and keep fighting the good fights.
(Spoilers for both The Last of Us Part 2 the game as well as The Last of Us the TV series.)
So despite it being very trendy to hate The Last of Us, mostly because it's violent and takes itself very seriously despite being flawed in a multitude of ways, I'm a sucker for it. The first game hit me at just the right time in college. The second game came out in the midst of the pandemic and was a nice distraction, and I dare say it was a small part in me coming to terms with my gender identity, playing through a violent post-apocalyptic pandemic as two strong, flawed women.
I personally liked the Abby gambit. I was puzzled by her introduction, I grew to hate her as Ellie did (despite guessing why she did what she did), and with the third act1 switcheroo, I came to appreciate her as a character, flaws and all. Although the game's pacing is hard to predict and can come across as a bit of a slog by the time it concludes, overall I was enthralled by the story it told.
Season 2 of the TV show kinda fucked that rhythm up, and at this point I'm not sure if it can recover.
I'm all for adaptations drastically changing the source material. Annihilation the book and Annihilation the movie don't even really tell the same story; they just have similar settings and some shared ideas (and the same title, of course). That said, I think both stories are great!
The Last of Us season 2 seems to be trying to tell the same story, and just isn't doing it as well. That's not to say it's all bad though, so let me start with the positives.
I loved seeing more of Jackson's politics, and I thought that the infected assault on the town was really effective as a "good television" action set-piece and provided a bit of extra character motivation as to why the people of Jackson would be extremely hesitant to support seeking revenge for Joel.
That's it, that's the section.
Two infected things noticeably absent from season 1 were reintroduced this season, and I thought they worked pretty well. I'm hoping season 3 will see the spores expanded with the infamous Rat King.
I was hoping to see Shamblers as well, but I don't mind them not showing up. Maybe they will in season 3, but they're more of a gameplay thing anyways (although that's what I thought about stalkers).
All the actors did incredible work with what they were given. Special shoutout to Isabela Merced (Dina) and Young Mazino (Jesse (RIP)).
"This wasn't in the game but it's nice." "Ooooh that's a cool shot." "That was a change but I think it works." "Hey, it's Cypher from The Matrix!" All that kinda stuff. You get the idea.
I love Ellie and Dina. Lesbian icons (well, lesbian and bisexual icons, respectively). In the game they hook up and are madly in love before they leave Jackson. The biggest romantic drama has already happened, we just see it come together.
In the show they play the "will they, won't they" game quite a bit more. I understand it makes for "good television" to play with the audience like that. And the moment of Ellie getting bitten, Dina about to kill her, Dina hearing and slowly believing Ellie is immune, Dina revealing she's pregnant, and them hooking up... that was a masterful beat.
I... still don't quite know how I feel about all of it. I think maybe it works? But I really enjoyed that the game didn't play this game at all except for when the stakes were low. And it made the reveal of Joel's death more impactful, since some early trailers for the game made it appear as if Dina would be the one getting killed by the visitors. So... ya win some, ya lose some?
The extra flashbacks and other scenes involving Isaac were quite well done and gave us some more insight into the character that was only given to us in Act 2 of the game via in-game dialogue.
But... Isaac's entire presence in the season felt completely out-of-place. Again, it feels like they thought the audience was too stupid to gather what was happening between the WLF and Seraphites without some extra scenes from their perspectives. But if they had instead kept in some of the other scenes (finding the bodies of Abby's friends at the hotel and TV station, the scene at the school), we probably wouldn't have needed that.
Maybe one Isaac flashback accompanied by them finding a FEDRA wanted poster would have been nice, but overall this all felt like it belonged in next season. It just wasn't narratively necessary (yet).
This is the big one.
We did not need this spelled out. The mystery of Abby kept me engaged in the game. Having everything spelled out right upfront makes everything much more... boring. And especially when she finally finds Joel, and instead of the subtle look in the face, we get this big cinematic zoom-in of "YEAH THAT'S RIGHT, THE GUY WHO KILLED MY DAD, THIS IS HIM" is just... too much.
AND THEN. And then. She gives that fukken villain monologue to Joel, explaining who she is, why she's there, what she's been through. I stuck with the season to see if maybe they'd come up with a good reason for that, but now that it's over, it just feels insulting to the viewer's intelligence. Any sense of interesting ambiguity is destroyed.
Again, we are shown early on why Ellie is on this journey. She and Joel made up. In the game we don't find this out until after she's lost everything, after she fails to get revenge, fails to go back to living a normal life, goes after Abby again, loses her fingers and thus her closest connection to Joel, loses Dina and her adopted child... only then do we realize that she was just starting to rekindle her relationship with Joel right before it was taken away from her. And suddenly everything is recontextualized, and it's that much more of a gut-punch.
Here it's just... "Oh okay so she did know. Aww, she did make up with him." It still has impact, but there's less of it.
Once she leaves on her revenge quest, we watch Ellie slowly devolve into an amoral killing machine focused only on revenge. In the show, the finale especially sees her questioning her morality, and Dina's questioning is more explicit as well. Once again, the ambiguity is killed.
Here's a reminder of how it goes down in the game.
Ellie gets a boat and recklessly makes her way towards the aquarium where she knows Abby is. The boat crashes just before she gets there, and she swim-crawls her way onto the dock and into the aquarium.
In the show, the boat crashes and Ellie washes onto the Seraphite island, where she is almost hanged and gutted, before they are called to help defend the village, leaving her. Then she boats back to the aquarium.
...What the hell was the point of that?! So that we can watch season 3 and say "Ohhhh that's what was going on!"? There's already plenty of that to be had.
Also, making the evil Seraphite kid do the disemboweling motion was, IMO, a bad choice, arguably even in poor taste. Something I thought worked really well in the game's story was showing that a lot of the Seraphites are just trying to live their lives according to their religion, and the WLF is straight-up murdering them. And the other way around. The problem is with the violent leaderships of each group.2 The evil kid makes it feel like the show wants us to think that the Seraphites are all irredeemably evil, and that just gives me the ick. I guess I could imagine that maybe it was just that one kid being part of the most murderous family in the village or something, but still, I really hope this isn't a sign of things to come.
Overall, the game left more unsaid, and the TV show played too many cards and said too much. The game's most masterful storytelling strokes were in its ability to refrain from showing its hand until the moment was right. The show still did a lot right, but almost all of its best cards have already been played. I hope it still has more tricks up its sleeves for season 3. I want to believe!
The third act of five, if you frame it that way (which I think better reflects the story's structure as opposed to a three-act structure). There's a whole essay to be written about how it feels like a 3-act structure at first and then morphs into a 5-act structure (which is part of why folks having issues with the pacing) (I'm pretty sure someone has already written that essay).
A lot of folks have drawn comparisons to Palestine and criticized the writers for this portrayal, especially since they admitted that's where they got their inspiration. I think it's a really fair criticism; what's happening in Palestine is, especially now, much more cut-and-dry; Israeli soldiers are straight-up genociding Palestinians. I personally find that The Last of Us tells a different enough story that I don't read it as a straightforward allegory, so I'm slower to condemn the writers, but I don't fault anyone for reading it that way, especially with that last scene with the kid.
I have a confession to make: I've been test-driving AI tools a bit in my work lately. I haven't pushed it to its limits, but I have been occasionally impressed with its ability to get me over humps in the road when it comes to software development.
In my off time, I also used it to get up and running with a complete personal website re-write. It hit a lot of edge cases where it couldn't seem to figure out the right way to do stuff, but it finally got me started on a project I had been putting off for years.
My coworkers have been espousing all the seemingly amazing things they've been able to accomplish thanks to their use of AI tools, and I've admittedly been extremely skeptical, but you could almost say I'm coming around to it having some useful applications.
...And yet...
Sometimes at work I find myself stuck and banging my head against the wall, and asking an AI a few questions can help unblock me when my coworkers are all busy trying to complete their own work. One might say that this is an unfortunate side effect of capitalism; the need for companies to "grow grow grow" at all costs means a team of developers can't just stop working on new features and focus solely on supporting each other and fixing bugs, lest they stagnate in the eyes of the shareholders.1
But I digress. The point I want to make is not something I can prove yet, but it's a bit of a hunch and a fear that I have.
Sometimes banging my head against the wall for a bit, leaving, and coming back allows my brain to get some distance and perspective and forge a new neural path. This new path can then serve me in the future the next time I work on something similar.
At other times, I might not know entirely what I want, but I'll just start writing some code to get the juices flowing, and before I know it I've mostly implemented my solution. I end up thinking through the solution through the act of coding.
When AI does the job for me, I miss out on both of these benefits. I don't get to form new neural paths, and I don't get to think through the problem.
"But June," I hear some imaginary AI apologist saying, "your coworkers do this as well. You delegate tasks to them and they write the code and you give it an overview, but you don't think through the solution or form those neural pathways there either!" Well yeah, but someone does. Someone has to think through and form the brain connections when they write it by hand. (Not to mention, I trust my coworkers as human beings. I do not trust the fancy black box autocomplete machine.)
Even if it's arbitrary tasks, when no one writes the code, no one has that depth of understanding of it. And when that starts to pile up, even in tiny, almost unnoticeable increments, things start to fall apart.
In A Philosophy of Software Design, John Ousterhout writes about "strategic" vs. "tactical" coding, "strategic" meaning forethought and planning is put into a chunk of code, "tactical" meaning "it's hacky but it works, let's get this merged." Tactical coding, when it piles up over time, tends to lead to complexity and tech debt, according to Ousterhout.
Vibe coding is tactical coding taken to the extreme. Again, I don't think we have enough data yet, but anecdotally, I've already started to hear some of the horror stories about AI programming being taken too far, and I'm very weary of even small amounts of this. As progessional software engineers, in the coming years, we're going to have to be very careful about how we approach AI tools, and make sure we maintain a strategic approach to the code that we put out as our own.
...And I didn't even get to any of the environmental effects of rising LLM usage, the plagiarism issues, the devaluation of human ingenuity and rise of anti-social attitudes, the general lack of taste. AI is a minefield in ways that I don't think many of us anticipated. Tread lightly.
I'm certainly not saying that about my own work, no sir, I would never, I love my job and everything about it at all times, all hail Adam Smith
Humans did not evolve in a world of eight billion people. We didn’t evolve with the internet, or television, or phones, or ChatGPT, or airplanes, or a 24-hour news cycle, or even light bulbs. We evolved in a cold world where we’d only ever meet a few hundred people and only ever travelled within a few hundred miles.
So when at the end of the day, after dealing with work that likely spans continents, spending “breaks” looking at today’s news from across the planet, and you turn on a screen to watch a lifelike depiction of someone undergoing surgery or something, and you find yourself feeling overwhelmed & exhausted… don’t seem so surprised, maybe.
It’s honestly a miracle that we cope this well with this much stuff going on that’s so antithetical to how we evolved. Forget spears and fires, we have forged sand to think for us and light up our eyes with a bunch of symbols representing abstract ideas. Of course we’re going to feel overwhelmed some amount of the time. Think about how many decisions you have to make in the span of a day. It’s insane!
All this is to say… maybe let yourself tune out from the neverending flood of bad news occasionally, as a treat. Maybe it’s not so good to stay so informed. I’m not saying bury your head in the sand and pretend everything is hunky-dory and that the country isn’t descending into fascism. I’m saying maybe give yourself some actual breaks. Maybe scroll through the headlines for like 5 minutes a day, with an eye for something that you can do about it, and then put it aside.
There’s not much many of us can do about Elon Musk flagrantly disregarding laws and tearing apart government institutions, or Donald Trump declaring that trans people don’t exist (unless you’re a lawyer or politician or something). I am a computer programmer/manager leading a team working on software for frontline manufacturing workers; I’m pretty sure my work is not going to have a direct effect on what’s happening in DC.
What we can do is focus more on our worlds. How can we put a little bit more love out there? What are all the ways that we can say “no” to cynicism & nihilism and say “yes” to the smaller communities around us? How can we adjust the way we talk to folks, the way our words come out, to bring a little bit more light into the world?
This is what I’ve been aspiring to do lately, and I think it’s been going relatively well. I sure as hell don’t always succeed, but it’s been somewhat of a north star amidst all the shitty events I’ve been trying to keep in my periphery without turning away entirely. It’s what’s kept me relatively sane. I’d recommend giving it a try.
Right now, in the year 2025 CE, it is relatively common to believe that if you are born with a penis, you are a man, and if you are born with a vagina, you are a woman. It is common to believe that anyone who attempts to assert otherwise is mentally ill at best.
At face value, when I put it that way free of any other context, reading it as a severed employee of Lumon Corporation might read it, it looks relatively reasonable.
But let's look at it another way: Why the fuck would you care what genitals someone else has?
There's lots of talk about gender lately in the human world, and it's frankly getting exhausting. It's not likely that anyone who reads this will have not heard all the arguments, so I won't bother going over them. But I will say a little bit about my experience.
I never felt comfortable as a boy. As in, when I was a child, I was basically always uncomfortable, but also, the idea of "boyhood" never resonated with me. I have vague, distant memories of being a child wishing I was a girl. But those thoughts were socialized out of me. They were "wrong." I was a boy, and that was that.
Fast-forward 25 years. I've got basically everything I thought I wanted. I've been extremely fortunate. I'm married with no kids and two cats. I'm doing quite well in my school where I'm changing careers. But still something isn't quite right. I've spent years feeling things weren't right, but I couldn't seem to find what it was killing me.1
To make a long story just a tad shorter, I saw this meme.

That was my egg-crack moment.
Then, I had some friends who convinced me that, if I wanted to be another gender, I could just be another gender. I started doing research on hormones and whatnot. But still it seemed scary.
Then, one night while I was bored at home alone between Christmas and New Year's Eve, I decided to download a pre-LLM AI face-changer app. My entire life, every time I looked in the mirror, it was... acceptable, at best. Seeing myself as a woman I realized just how little it took to make myself... hot. I say that half in jest, but truly, I finally saw a version of myself I could be happy seeing in the mirror.
I immediately realized I could no longer live like I had been. It wasn't even a choice. I had opened Pandora's box. Insert another cliche.
Fast forward another 4 years, and I am so much happier with who I am. I've gone through the tumultuous second puberty, the divorce (we're still friends!), figuring out makeup and bra sizes and realizing to a whole new extent how patriarchy affects both cis and trans women.
Now, I just want to live my life. Like, I just want to go the grocery store, movie theater, travel, walk my dog, pet my cats, visit to an aquarium. Normal shit.
I am fortunate enough to live in a place where most people don't bat an eye at me anymore than any other person. In fact, I've been getting a flattering number of comments about my hair being beautiful lately. I'm very grateful!
However, that comfort comes at a price. No, literally, the cost of living in this town is starting to get ridiculous compared to more rural areas of the state, and especially to other states. But living in one of those other places comes with another cost: I'd be much more likely to run into folks that glare at me when I wear a dress, or worse, accost me when trying to use a bathroom for folks that match my general appearance. Transgender people are over four times more likely than cisgender people to experience violent victimization, including rape, sexual assault, and aggravated assault. There are videos of cis folks trying to go to the bathroom and getting harassed by people thinking they're trans and asking to see their IDs.
If I hadn't been an ADHD procrastinating dummy, last year I could have gotten a passport that said I was a lady. Fortunately I have my license, but now with this stupid administration, I can't leave the country without outing myself, which can be a major deal in certain parts of the world.
Two of the times I've travelled by plane in the US since coming out, I've been groped by security and told that they detected something in my pants before I outed myself to them as transgender. Because I didn't do a good enough job tucking. Because they saw my license said I was a lady, and set their stupid machine to that setting, I'm guessing. Or maybe they wanted to humiliate me! Who knows.
The company I work for is based in Florida, where laws, although vague, potentially allow folks to assault me for trying to go to the "wrong" bathroom. Every time I use a gendered public restroom, I do a little speed-walk to the stall with my head down, and upon exiting, try to wash my hands as quickly as possible.
I just want to shit in peace. I just want to travel without danger of being outed and potentially assaulted. I'm not demanding that I be allowed to do a strip dance or inject unwilling children or feminize anyone or put litter boxes in my schools (god that rumor was fucking stupid lol). I'm not trying to shove my identity in anyone's faces. I just want to live my life. And a lot of folks, for some reason, want to make that very difficult. I think that's fucked up.
I Saw the TV Glow really does a fantastic job of portraying this feeling. I deeply recommend it.
note: this post uses Swift 6.0 and Xcode 16.2
Let’s recap the different behaviors we want to allow when duplicate keys come up in our task store1:
When we need to enumerate different possibilities, what do we reach for? That’s right, an enum!
enum TaskStoreDuplicateKeyBehavior: Hashable, Sendable {
case cancelAndWait
case cancelAndRunImmediately
case wait
case runConcurrently
case preferPrevious
}
If you wanted to get fancy you could make it look like this:
enum AlternateDuplicateKeyBehavior: Hashable, Sendable {
case preferNew(cancelPrevious: Bool, waitForPrevious: Bool)
case preferPrevious
}
Or even this:
struct AlternateAlternateDuplicateKeyBehavior: Hashable, Sendable {
struct PreferNewOptions: Hashable, Sendable {
var cancelPrevious: Bool
var waitForPrevious: Bool
}
var preferNewOptions: PreferNewOptions?
}
Logically, those are all equivalent.2 Which option we go with comes down to weighing 1.) what we want the public-facing interface to look like, and 2.) the ease and readability of the implementation details.
I’m going to go with the last option and add some convenience properties that clarify things for us so that we can have the best of both worlds.
struct TaskStoreDuplicateKeyBehavior: Hashable, Sendable {
fileprivate struct PreferNewOptions: Hashable, Sendable {
var cancelPrevious: Bool
var waitForPrevious: Bool
}
fileprivate var preferNewOptions: PreferNewOptions?
var preferPrevious: Bool {
preferNewOptions == nil
}
var cancelPrevious: Bool {
preferNewOptions?.cancelPrevious ?? false
}
var waitForPrevious: Bool {
preferNewOptions?.waitForPrevious ?? true
}
var runNewTask: Bool {
preferNewOptions != nil
}
static var wait: Self {
Self(preferNewOptions: .init(cancelPrevious: false, waitForPrevious: true))
}
static func cancelPrevious(wait: Bool) -> Self {
Self(preferNewOptions: .init(cancelPrevious: true, waitForPrevious: wait))
}
static var runConcurrently: Self {
Self(preferNewOptions: .init(cancelPrevious: false, waitForPrevious: false))
}
static var preferPrevious: Self { Self() }
}
Let’s add this as a parameter to our addTask method.
func addTask(
forKey key: Key,
duplicateKeyBehavior: TaskStoreDuplicateKeyBehavior = .cancelPrevious(wait: false),
priority: TaskPriority? = nil,
isolation: isolated (any Actor)? = #isolation,
operation: @escaping @Sendable () async -> Void
)
I think that cancelling the previous task and not waiting is a good default behavior, personally, but it’s definitely up for debate.
Now, implementing the behavior based on what’s encoded in the duplicateKeyBehavior becomes fairly trivial.
func addTask(/*...*/) {
let preferNewOptions = duplicateKeyBehavior.preferNewOptions
let previousTask = self.currentTasks[key]?.task
if let previousTask {
guard let preferNewOptions else { return }
if preferNewOptions.cancelPrevious {
previousTask.cancel()
}
}
let newTaskID = UUID()
let newTask = Task(
priority: priority,
operation: {
await withTaskCancellationHandler {
if let previousTask, let preferNewOptions, preferNewOptions.waitForPrevious {
await previousTask.value
}
await operation()
self.taskFinished(key: key, id: newTaskID, isolation: isolation)
} onCancel: {
previousTask?.cancel()
}
}
)
self.currentTasks[key] = TaskData(id: newTaskID, task: newTask)
}
Let’s break this down bit by bit.
let preferNewOptions = duplicateKeyBehavior.preferNewOptions
let previousTask = self.currentTasks[key]?.task
if let previousTask {
guard let preferNewOptions else { return }
if preferNewOptions.cancelPrevious {
previousTask.cancel()
}
}
If a previous task exists, we want to check if we are preferring the new task over the previous task, otherwise we want to early exit and just keep running the original task. If we do want to prefer the new task, then we’ll cancel it (or not) based on the preferred behavior.
Finishing off this synchronous context outside the async task:
let newTaskID = UUID()
let newTask = Task(
priority: priority,
operation: { /*...*/ }
)
self.currentTasks[key] = TaskData(id: newTaskID, task: newTask)
Probably pretty self-explanatory; we make our new ID, we make the task with the provided priority, and we store it in our store’s current tasks. (Remember that at this point we’ve checked to make sure we are indeed preferring the new task over the old one (or there is no old task).)
Lastly, inside the async task:
await withTaskCancellationHandler {
if let previousTask, let preferNewOptions, preferNewOptions.waitForPrevious {
await previousTask.value
}
await operation()
self.taskFinished(key: key, id: newTaskID, isolation: isolation)
} onCancel: {
previousTask?.cancel()
}
If the task is cancelled, we want to make sure the previous task is cancelled as well (if it exists), so we wrap it in the withTaskCancellationHandler block. Cancellation will automatically propagate to operation. If the previous task exists and we’ve indicated we want to wait for it, then we do that before starting the new work. Finally, we call the function we made last time to remove the task if this one is still being held by the store.
There’s one more thing we could add to make this even more awesome. Let’s say we want to run some async task when our view appears using the task view modifier. Maybe we even want to cancel it when the view disappears. With just a couple of small additions, we can make this really easy for the end user.
To start with, we’re going to adjust the signature of our addTask method.
@discardableResult
func addTask(
forKey key: Key,
duplicateKeyBehavior: TaskStoreDuplicateKeyBehavior = .cancelPrevious(wait: false),
priority: TaskPriority? = nil,
isolation: isolated (any Actor)? = #isolation,
operation: @escaping @Sendable () async -> Void
) -> Task<Void, Never>
We’re returning a task now! But similar to Task’s built-in initializer, we don’t want to require the end user to use the task that’s returned. It’s already being managed by the TaskStore, we’re just also handing it off if they want a handle to cancel it… such as in the view layer.
Let’s add add a new task to our model.
@Observable
final class MyModel {
private enum TaskKey: Hashable {
case doThing
case viewTask
}
// ...existing stuff...
func viewTask() -> Task<Void, Never> {
taskStore.addTask(forKey: .viewTask) {
// some async work
}
}
}
Now, in the view, all we need to do is call that and await the task that’s returned.
struct MyView: View {
@Bindable var model: MyModel
var body: some View {
VStack {
// ...existing stuff...
}
.task {
await model.viewTask().cancellableValue
}
}
}
And that’s it!3 This also makes it much easier to test; you can assert on what the state looks like while it’s loading, and then await its result, and assert on its state again (of course mocking out your dependencies along the way, because you are a very good smart programmer yes you are).
There are of course more extensions you can write on this type, but I leave that as an exercise to the viewer. I’ve thought of making this type an open-source package, but honestly, I don’t want to deal with the maintenance cost of that. Just use this in your projects and see if it works for you. Give me a shout-out if it does! Or doesn’t. Whatever you feel like!
You can find the complete code for what we made in this series of posts here.
I decided to release this as a library after all. You can find it here.
It’s possible there are other possibilities, but these are the ones that have been relevant to me.
We probably don’t need this to be Hashable or Sendable, but I like to add those conformances where it makes sense and there’s no reason not to.
cancellableValue is a very useful and simple Task extension that can be found in PointFree’s handy swift-concurrency-extras library.
note: this post uses Swift 6.0 and Xcode 16.2
Let’s set up some goals before we get into the weeds. We want to:
Let’s get to work!
The centralized location that will handle our tasks will be called the TaskStore. (Spoiler alert: right off the bat, we’re going to run into what seem to be limitations of Swift’s current ability to model isolation generically, but it shouldn’t actually be that big of a problem for our purposes.)
In order to handle several different tasks at once (say, loading initial data, saving data, loading additional pages of data, deleting data, etc), we should have some kind of key for each asynchronous chunk of work we want to do. (Another spoiler alert: we’re going to use a Dictionary to hold this state, so the key will need to be Hashable.)
The public interface will look something like this:
final class TaskStore<Key: Hashable> {
func addTask(
forKey key: Key,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async -> Void
)
func cancelTask(forKey key: Key)
func taskIsRunning(forKey key: Key) -> Bool
}
Note that addTask mirrors the signature of Task’s initializer, with the addition of the key parameter so we can run multiple tasks at once from our store.
The latter two functions are pretty easy to implement once we add some storage for our tasks.
final class TaskStore<Key: Hashable> {
private var currentTasks: [Key: Task<Void, Never>] = [:]
func cancelTask(forKey key: Key) {
currentTasks[key]?.cancel()
}
func taskIsRunning(forKey key: Key) -> Bool {
currentTasks.keys.contains(key)
}
}
Nice, so far so good! Remember that task cancellation is cooperative in Swift, meaning that the task itself is responsible for checking if it’s been cancelled and responding adequately. It might still have some work to do, so we can’t just remove it when it’s been cancelled. That logic will happen in the addTask method.
However, implementing adding the task is when things get a little tricky. If we just do it like this…
func addTask(
forKey key: Key,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async -> Void
) {
let task = Task(
priority: priority,
operation: { // !!!
await operation()
self.currentTasks.removeValue(forKey: key)
}
)
self.currentTasks[key] = task
}
We get another one of those gnarly warnings: Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure, along with the slightly more helpful note: Closure captures 'self' which is accessible to code in the current task.
We’re running into the same issue we did with the model class previously; it’s not isolated to any domain and concurrent tasks can potentially modify state at the same time as each other, which is bad!
The way around this is… a little bit arcane and strange, if I’m being honest. Essentially, we need to give the compiler enough information to be able to tell that any given instance of this type can only possibly be isolated to a single domain. First of all, it needs to not conform to Sendable; it can’t, being a class with mutable state, but if it did, it could cross concurrent boundaries, and we don’t want that.
What I would love to do is have something like this:
@Isolation
final class TaskStore<Key: Hashable, Isolation: Actor = MainActor> {
//...
}
Unfortunately this is not valid Swift code. Instead, we have to add an extra special parameter to our methods that can possibly concurrently access the mutable state of this class. At the end, it will look like this:
func addTask(
forKey key: Key,
priority: TaskPriority? = nil,
isolation: isolated (any Actor)? = #isolation,
operation: @escaping @Sendable () async -> Void
) {
let task = Task(
priority: priority,
operation: {
_ = isolation
await operation()
self.currentTasks.removeValue(forKey: key)
}
)
self.currentTasks[key] = task
}
That #isolation gets the current isolation domain (ie, whatever actor this is running on, be it the MainActor or some other actor, global or otherwise). _ = isolation lets the task closure know that it should run on the same actor as the one passed into the function. With that, the compiler can figure out, “Oh, these two tasks can never actually run at the same time, because they’re happening on the same actor.” Success!1
In our model, we now need to add a Key to enumerate the different kinds of tasks we’re going to run. For us, for now, it’ll only be one, but we might add more in the future. It still makes the most sense to use an enum to model this.
@Observable
@MainActor
final class MyModel {
private enum TaskKey: Hashable {
case doThing
}
//...
}
The rest of the implementation ends up being pretty straightforward.
final class MyModel {
//...
var someText = ""
private let taskStore: TaskStore<TaskKey> = .init()
var doThingButtonEnabled: Bool {
!taskStore.taskIsRunning(forKey: .doThing)
}
var showCancelButton: Bool {
taskStore.taskIsRunning(forKey: .doThing)
}
func doThingButtonTapped() {
taskStore.addTask(forKey: .doThing) {
// get something from the server or something
await Task.yield()
await self.doThingResponse()
}
}
func cancelButtonTapped() {
taskStore.cancelTask(forKey: .doThing)
}
private func doThingResponse() {
guard !Task.isCancelled else { return }
someText += "!"
}
}
However, if you run this, you’ll realize that the view is no longer updating. What gives?!
Well, Observable only registers changes to value types and to other observable reference types, so we’re going to need to add observation to our TaskStore. This is easy as adding @Observable to the type. That’s it!
This also has the knock-on benefit that we no longer necessarily need to isolate our model to the MainActor. Since SwiftUI’s views will already be MainActor-isolated, this model will as well, and hence our task store will be. Huzzah!
We’ve got another problem, though. The behavior is still not quite right. What happens if another task comes in while the previous one is running, with the same key? When Task 1 finishes, it will remove the current task for that key, even if in the meantime, Task 2 has started and replaced it. Meaning Task 2 might be running still, but it’s been removed from the store, and the store will report it as not running.
So, in addition to task keys, we need another way to check if it’s actually the same task still running as when we started the task. We could try to do some dancing using Task’s built-in Equatable conformance, but I’ve found the simpler way is to just use a separate UUID.2 Let’s store this new identifier together with the associated task using a new lightweight struct.
private struct TaskData {
var id: UUID
var task: Task<Void, Never>
}
And now we can replace our existing storage in the TaskStore:
private var currentTasks: [Key: TaskData] = [:]
We can also separate out the “completion” into a separate function, which will have the knock-on effect of getting to remove the awkward _ = isolation line, since we’ll need to pass on the isolation anyways to give the compiler enough info to know that we’re not breaking any rules here.
func addTask(
forKey key: Key,
priority: TaskPriority? = nil,
isolation: isolated (any Actor)? = #isolation,
operation: @escaping @Sendable () async -> Void
) {
let id = UUID()
let task = Task(
priority: priority,
operation: {
await operation()
self.taskFinished(key: key, id: id, isolation: isolation)
}
)
self.currentTasks[key] = TaskData(id: id, task: task)
}
private func taskFinished(key: Key, id: UUID, isolation: (any Actor)?) {
if currentTasks[key]?.id == id {
currentTasks.removeValue(forKey: key)
}
}
What do we do about the previous task, though? We’re still overwriting it without checking. There’s a few things we could do:
Which behavior should we adapt? Well, there’s a way we can let the caller decide what to do with every single task! We’ll cover that in the next part.
There is a caveat here: don’t try to use the task store in a nonisolated domain. This seems to be a hole in Swift’s concurrency checking, and it may result in a runtime crash (or worse, undefined behavior). Be careful! (You could also just mark the TaskStore as @MainActor if that suits your needs; that’s what I’ve mostly done up to this point, and although in principle I don’t really like that, it hasn’t really proven to be a problem.)
If you want to eke out more performance, you could use a UInt or something, but I haven’t found the need so far.
note: this post uses Swift 6.0 and Xcode 16.2
Concurrency is hard in any language. It was hard in Swift before modern tools like async/await/etc were introduced, and it’s still hard, sometimes harder in ways you might not expect. Over the past few years I’ve found a few somewhat novel solutions to some of the difficulties that arise out of working with Swift’s concurrency tools, and I’d like to share them with you.
You’re writing a SwiftUI app with an MVVM-ish architecture because you want to be able to separate your business logic from the UI because you’re very good smart programmer, yes you are. You’ve got something that looks a little like this.
@Observable
final class MyModel {
var someText = ""
func hitSomeServerEndpoint() async {
// get something from the server or something
await Task.yield()
someText += "!" // do something with the stuff you got from the server
}
}
struct MyView: View {
@Bindable var model: MyModel
var body: some View {
VStack {
TextField("Some Text", text: $model.someText)
Button("Do Thing") {
}
}
}
}
(I mark all my classes as final unless I later decide I want to subclass (which is extremely rare), because inheritance is awful IMO.)
Unfortunately, that button can’t call the async function directly. So maybe you do something like this.
Button("Do Thing") {
Task {
await model.hitSomeServerEndpoint() // !!!
}
}
Uh oh, a compiler error! Sending 'self.model' risks causing data races? What does that even mean? The short version is, because MyModel is a reference type and is not isolated to any concurrency domain, there’s a chance that some mutable state on there could be simultaneously accessed by multiple threads, which could be bad for several reasons I won’t get into here because other folks have talked about it at great length much better than I could. One solution is fairly simple: just make your model isolated to the main thread by annotating it with @MainActor.
@Observable
@MainActor
final class MyModel {
//...
}
Problems solved! The end!
Not so fast. Soon, maybe 5 seconds later, maybe years later, you decide you want to be able to cancel that server call for one reason or another. Er. Well, you can’t because you’re throwing the handle to the task away as soon as you start it, because Task’s initializer is marked @discardableResult, meaning you can initialize a task without holding onto it. So if we want to cancel that task, we have to hang onto it somewhere. The model seems like a good place to do that! Let’s do a bit of refactoring to make this all work together nicely.
@Observable
@MainActor
final class MyModel {
var someText = ""
private var doThingTask: Task<Void, Never>?
func doThingButtonTapped() {
doThingTask = Task {
// get something from the server or something
await Task.yield()
guard !Task.isCancelled else { return }
someText += "!" // do something with the stuff you got from the server
}
}
func cancelButtonTapped() {
doThingTask?.cancel()
}
}
struct MyView: View {
@Bindable var model: MyModel
var body: some View {
VStack {
TextField("Some Text", text: $model.someText)
Button("Do Thing") {
model.doThingButtonTapped()
}
Button("Cancel") {
model.cancelButtonTapped()
}
}
}
}
Note that now we check in that task whether it’s been cancelled, and if so, we don’t finish modifying the model state. That’s looking pretty good! But now we’ve got this cancel button there when there might not be anything to cancel, and they can potentially tap the “Do Thing” button a bunch of times and run a bunch of tasks, and only the last tap will actually be held onto. We could add state as to whether to show the cancel button and whether to allow tapping the “Do Thing” button…
@Observable
@MainActor
final class MyModel {
var someText = ""
private var doThingTask: Task<Void, Never>?
private(set) var doThingButtonEnabled = true
private(set) var showCancelButton = false
func doThingButtonTapped() {
doThingButtonEnabled = false
showCancelButton = true
doThingTask = Task {
// get something from the server or something
await Task.yield()
someText += "!" // do something with the stuff you got from the server
doThingButtonEnabled = true
showCancelButton = false
}
}
func cancelButtonTapped() {
doThingTask?.cancel()
}
}
Sure, that works. But now you need to remember to change those variables every time you use this kind of pattern. Not a big deal, but if a programmer needs to remember to do something, there’s a good chance that at some point they’re going to forget, and that’s going to lead to bugs.
Luckily, there is another way! We are already holding onto some state that can determine whether we should show the cancel button and disable the first button; the Task! Instead of separate Bools for every condition in the UI, we can use computed properties based on whether there’s a Task currently being held onto.
@Observable
@MainActor
final class MyModel {
var someText = ""
private var doThingTask: Task<Void, Never>?
var doThingButtonEnabled: Bool {
doThingTask == nil
}
var showCancelButton: Bool {
doThingTask != nil
}
func doThingButtonTapped() {
doThingTask = Task {
// get something from the server or something
await Task.yield()
guard !Task.isCancelled else { return }
someText += "!" // do something with the stuff you got from the server
doThingTask = nil
}
}
func cancelButtonTapped() {
doThingTask?.cancel()
}
}
Nice! Unfortunately though, we still need to remember to nil out the Task here. And guess what? There’s a bug here! Can you spot it? I almost didn’t. I’ll give you a second.
…That’s right, nice find! If the task is cancelled and we hit the early exit, the task will never be nil’d out. Case in point! If things can be forgotten, then at some point, they will be. (Hopefully you’ll have a decent test suite where you would have caught that, but that’s a pretty big “hopefully”.) In any case, the solution is to put a defer block at the start of the task that nils it out whenever the task is finished, triggering our view update.
func doThingButtonTapped() {
doThingTask = Task {
defer {
doThingTask = nil
}
// get something from the server or something
await Task.yield()
guard !Task.isCancelled else { return }
someText += "!" // do something with the stuff you got from the server
}
}
Cool! So we’ve gotten there, but we still need to remember to do this all correctly next time we write this kind of feature. If only there was a one-stop solution to this sort of problem where this and other behaviors were more explicit and configurable, and we could just have something else worry about having to remember stuff.
…Next time we’ll come up with something that does just that.
Greetings! It's been awhile since I've written something long-form publicly. Three and a half years, to be more precise. Not for lack of want; more for lack of followthrough.
I have ADHD and anxiety, a combination which makes stimulant medications a bit of a catch-22, and I've prioritized other stuff, like just enjoying my life. Unfortunately I also have a desire to fiddle with stuff and "do it myself," so I've tried to maintain a Jekyll-based website and self-host my website and stuff but... honestly I just, as my British coworkers might say, "can't be fucked anymore."
So, inspired by my friend Megan, I'm outsourcing the hard part and focusing on just writing stuff and hitting the 'publish' button now! I hope you'll tune in! I don't know how often I'll write here, but hopefully more than once every 3.5 years!
[edit: 2025-06-28]: Aaaaand I'm back to my own website
It's been awhile! I'm going to start writing stuff over on this other website. Find me over there! You can also find me on Bluesky because Twitter X is a shit-show.
[edit: 2025-06-28]: This didn't end up lasting very long! Turns out I like having ownership of my stuff, and LLMs take a lot of the friction out of the process so I can just write the words and let the computer handle the rest.
(Note: This post uses Swift 5.4 and the iOS 14 SDK)
There may be some circumstances where you want to encode/decode a model in multiple ways, depending on certain conditions or use-cases. There's a lot of potential solutions to this problem. Here's some of them.
Maybe most of the time, you want the simple, default conformance for a model like this:
struct MyModel: Codable {
var name: String
var id: UUID
var created: Date
}
If you do nothing else, then this will encode/decode to JSON (or whatever else) in a very similar format.
{
"name": "June Bash"
"id": "CAFED00D-CAFE-D00D-CAFE-D00DCAFED00D",
"created": 643813980.32495201
}
Let's say that sometimes, you'll only want to encode a partial model (like just the ID and name), or sometimes you won't get the entire model. How do we handle these cases?
First, though, let's look at encoding, as it's much simpler.
The most straightforward way to do this (for both encoding and decoding, really) is to just make a second version of the model.
struct MyPartialModel: Codable {
var name: String
var created: Date
}
That's it, end of blog post.
...There are some downsides to this though. First and foremost, you'll have to make your own way of translating from one version of the model to another, and decide when to use each one. Maybe this isn't a big deal, but if your model has a lot more properties, it can get pretty annoying and complicated.
Let's explore some alternatives.
Encoding is implemented using the encode(to encoder: Encoder) throws method. This can be automatically synthesized by the compiler if all stored properties are also Encodable, or we can manually implement it, similarly to Hashable and Equatable. Let's say that when the model is a child of some other model, we'll only encode the id. Let's say the key will also be uuid instead of id in those cases. That could look something like this:
private extension MyModel {
func encodeForParent(to encoder: Encoder) throws {
enum ParentCodingKeys: CodingKey { case uuid }
var container = encoder.container(keyedBy: ParentCodingKeys.self)
try container.encode(id, forKey: .uuid)
}
}
struct Parent: Codable {
var id: UUID
var someInt: Int
var child: MyModel
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someInt, forKey: .someInt)
try child.encodeForParent(to: container.superEncoder(forKey: .child))
}
}
(Note that CodingKeys is automatically synthesized as long as we leave either init(from decoder:) or encode(to encoder:) as the default, synthesized implementation.)
This will output the following JSON:
{
"id" : "00439575-65F8-4D00-953B-B8605F60310B",
"someInt" : 9,
"child" : {
"uuid" : "D4350E5E-3051-4950-ABE2-2792C7A59D96"
}
}
Exactly what we wanted!
But wait, there's more (alternatives)!
We can hide a second model that matches our JSON spec within the encode method body.
struct Parent: Codable {
//...
func encode(to encoder: Encoder) throws {
struct PartialChild: Encodable {
var uuid: UUID
}
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someInt, forKey: .someInt)
try container.encode(PartialChild(uuid: child.id), forKey: .child)
}
}
This will output the same results as the previous method. If you don't like either version, there's one more way to do this...
Let's make a wrapper around an encode method. (I've talked about type-erasure here a couple of times before.)
struct AnyEncodable: Encodable {
private let _encode: (Encoder) throws -> Void
init(_ encode: @escaping (Encoder) throws -> Void) {
self._encode = encode
}
init<E: Encodable>(_ encodable: E) {
self.init(encodable.encode)
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}
By wrapping an encodable type in this using the second initializer, everything referring to the original type will be erased. In a lot of cases this type wouldn't come in handy, as the Encodable protocol can be thrown around willy-nilly without problems since it has no Self or associatedType requirements. But it also allows us to make arbitrary encode methods.
struct Parent: Codable {
//...
func encode(to encoder: Encoder) throws {
let childEncodable = AnyEncodable { childEncoder in
enum ChildCodingKeys: CodingKey { case uuid }
var container = encoder.container(keyedBy: ChildCodingKeys.self)
try container.encode(child.id, forKey: .uuid)
}
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someInt, forKey: .someInt)
try container.encode(childEncodable, forKey: .child)
}
}
I think this version has the most flexibility out of any of the methods discussed, especially if the model won't be nested within some other model.
try JSONEncoder().encode(AnyEncodable {
enum ChildCodingKeys: CodingKey { case uuid }
var container = $0.container(keyedBy: ChildCodingKeys.self)
try container.encode(myModel.id, forKey: .uuid)
})
Decoding is, perhaps surprisingly, quite a bit more complicated due to the way Swift's type system and the Decodable protocol are set up, but some of the solutions will be similar to encoding.
We could implement this with a single initializer.
extension MyModel {
init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.id = try container.decode(UUID.self, forKey: .id)
self.created = try container.decode(Date.self, forKey: .created)
} catch {
enum ParentCodingKeys: CodingKey { case title, uuid }
let container = try decoder.container(keyedBy: ParentCodingKeys.self)
self.name = try container.decode(String.self, forKey: .title)
self.id = try container.decode(UUID.self, forKey: .uuid)
self.created = Date()
}
}
}
This saves having to call the custom init from the parent, but it's also a decent amount of boilerplate that may not be necessary, and if we need more than one special case, it'll get messy fast. We also leave all the logic of determining when and how to decode this inside the initializer. We could decide how to decode using Decoder.userInfo, but that opens up a whole other can of worms. Let's look at some other ways to do this.
Just like with encoding, we can make a second, custom init just for this circumstance.
extension MyModel {
init(fromParent decoder: Decoder) throws {
enum ParentCodingKeys: CodingKey { case title, uuid }
let container = try decoder.container(keyedBy: ParentCodingKeys.self)
self.name = try container.decode(String.self, forKey: .title)
self.id = try container.decode(UUID.self, forKey: .uuid)
self.created = Date()
}
}
And then we can call this from the parent's custom init, just like we did with encoding.
struct Parent: Codable {
//...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.someInt = try container.decode(Int.self, forKey: .someInt)
self.child = try MyModel(fromParent: container.superDecoder(forKey: .child))
}
}
And again just as with decoding, we can make a special struct that lives within the decode init:
init(from decoder: Decoder) throws {
struct PartialChild: Decodable {
var title: String
var id: UUID
}
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.someInt = try container.decode(Int.self, forKey: .someInt)
let partialChild = try container.decode(PartialChild.self, forKey: .child)
self.child = MyModel(
name: partialChild.title,
id: partialChild.id,
created: Date()
)
}
Note that both of the previous methods will only work when the type is nested in a parent model.
You may be thinking that we could make a type-erased AnyDecodable wrapper as well just as simply as we did with encoding. While we can get most of the way there, this type won't be able to conform to Decodable; there's no way to inject the required init before initializing the value. I've seen some workarounds for this, but they're all far from ideal or overly complex.
Here's a simplified version that doesn't conform to Decodable, but gets things done for our purpose.
struct AnyDecoding<DecodedValue> {
private let _decode: (Decoder) throws -> DecodedValue
init(_ decode: @escaping (Decoder) throws -> DecodedValue) {
self._decode = decode
}
func decode(from decoder: Decoder) throws -> DecodedValue {
try _decode(decoder)
}
}
The call-site will look something like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.someInt = try container.decode(Int.self, forKey: .someInt)
self.child = try AnyDecoding { childDecoder throws -> MyModel in
enum ChildCodingKeys: CodingKey { case title, uuid }
let container = try childDecoder.container(keyedBy: ChildCodingKeys.self)
return MyModel(
name: try container.decode(String.self, forKey: .title),
id: try container.decode(UUID.self, forKey: .uuid),
created: Date()
)
}.decode(from: container.superDecoder(forKey: .child))
}
Not super-pretty, but it gets the job done, and keeps everything inside the Parent init. Unfortunately, therein lies the problem: this will, yet again, only work when we have access to that Decoder. JSONDecoder only provides a wrapper around the method that directly calls init(from decoder:). How can we do this when we aren't in a parent model?
There is a way to get something that does conform to Decodable, but... it's a bit complicated.
protocol DecodeKey {
associatedType DecodedValue
static func decode(from decoder: Decoder) throws -> DecodedValue
}
public struct AnyDecodable<Key: DecodeKey>: Decodable {
public let value: Key.DecodedValue
public init(from decoder: Decoder) throws {
self.value = try Key.decode(from: decoder)
}
}
Any time we want to make an instance of AnyDecodable, we need to create another type that holds our decode method, like so:
enum PartialDecodeKey: DecodeKey, CodingKey {
case uuid, title
static func decode(from decoder: Decoder) throws -> MyModel {
let container = try decoder.container(keyedBy: Self.self)
return MyModel(
name: try container.decode(String.self, forKey: .title),
id: try container.decode(UUID.self, forKey: .uuid),
created: Date()
)
}
}
See how we can even use this enum as the CodingKey? And then our parent decode method would look like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.someInt = try container.decode(Int.self, forKey: .someInt)
self.child = try container.decode(AnyDecodable<PartialDecodeKey>.self, forKey: .child).value
}
And we can even use a top-level decoder like JSONDecoder to decode it outside of a parent model!
let myModel = try JSONDecoder().decode(AnyDecodable<PartialDecodeKey>.self, from: data).value
Notice we have to include the .value at the end of the last line. However, unlike some other implementations of AnyDecodable, this is completely typesafe; we don't have to rely on any runtime conditional casting.
So again, all of these methods of secondary coding implementations have their upsides and downsides. And of course, I'm sure there are things that I've left out. Regardless, choose whatever works best for your situation.
(Note: This post uses Swift 5.3 and the iOS 14 SDK)
SwiftUI is the hot new kid on the block in iOS Development, but as most developers know, UIKit isn't going anywhere anytime soon (and indeed, a decent chunk of SwiftUI utilizes it under the hood). Despite this, there's still a lot of aspects of UI programming that can be pretty obnoxious with UIKit. However, there are some interesting ways that we can make it a little bit more usable by taking some nods from a more functional programming style.
A while back, John Sundell wrote a great article about writing small utility functions, and ever since I've been using his configure blocks all throughout my code (much to the initial-chagrin-and-later-delight of my teammates):
@discardableResult
func configure<T>(_ item: T, with transform: (inout T) -> Void) -> T {
var result = item
transform(&result)
return result
}
(I've marked this as @discardableResult for the cases where a class is passed in; in that case, we don't need to get anything out, since the value pointed to by the reference parameter we passed in will have been mutated.)
This doesn't look like it does much at first, but read John's article and you'll get an idea of its power.
That alone can be really helpful in many ways, but it might not cut down on the carpal tunnel caused by typing translatesAutoresizingMaskIntoConstraints one too many times (okay, you can probably get away with typing tamic and letting the code completion fill in the rest, but you'll still have a decent bit of duplication). It would be nice if we could compose these styles together.
It just so happens that PointFree has an excellent (and free!) video on this topic. In that video, they demonstrate how, using closures and a simple custom operator, you can vastly cut down on the amount of duplication in a codebase and write code that's very reusable and composable.
infix operator <>
func <> <A>(
f: @escaping (A) -> A,
g: @escaping (A) -> A
) -> (A) -> A {
return g(f(a))
}
func <> <A>(
f: @escaping (inout A) -> Void,
g: @escaping (inout A) -> Void
) -> (inout A) -> Void {
return { a in
f(&a)
g(&a)
}
}
func <> <A: AnyObject>(
f: @escaping (A) -> Void,
g: @escaping (A) -> Void
) -> (A) -> Void {
return { a in
f(a)
g(a)
}
}
func baseButtonStyle(_ button: UIButton) {
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
let filledButtonStyle =
baseButtonStyle
<> {
$0.backgroundColor = .black
$0.tintColor = .white
}
let roundedButtonStyle =
baseButtonStyle
<> {
$0.clipsToBounds = true
$0.layer.cornerRadius = 6
}
let filledButtonStyle =
roundedButtonStyle
<> {
$0.backgroundColor = .black
$0.tintColor = .white
}
I'd highly recommend watching the video to get an idea of what's happening here, but the main idea is that styles are really just functions (or closures) that take something in and spit it back out with some changes applied. Taking this as a starting point, we can then compose (or combine, or chain) these styling functions using this custom operator.
As a brief aside, I've heard a lot of pushback on the use of custom operators; folks say that it makes code harder to read, it takes folks longer to figure out what's going on in a project, etc. But I would argue that this is true of any reasonably-sized codebase. Anytime you get started with a project, there will be new functions to learn, custom types and methods, ways of doing things. If you can get past the fact that it's a symbol instead of a word, an operator is just a function that you would have to look up the meaning of, just like any regular function or type or variable in a codebase.
Anyways... this is all great, but where are we going to keep these styles? If they're global, our global namespace is going to get pretty cluttered. Even putting them on their base types might get a little obnoxious.
We could make an empty enum as a namespace:
enum Style {
static func baseButton(_ button: UIButton) {
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
}
...But that could also get pretty cluttered. So how about making it generic?
enum Style<T> {}
extension Style where T: UIButton {
func baseButton(_ button: UIButton) {
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
//...
}
Cool, that can help keep our codebase more organized; everything in its right place.
...But one of my favorite things about having static factory methods like that is the ability to call .whateverStaticProperty or .whateverStaticFunc(). In the above cases, we can never actually instantiate an instance of Style; it's purely a namespace only. And it would get pretty annoying to have to call Style<UIButton>.whatever all the time. And of course, we can't make an extension on a function signature; I can't call ((inout T) -> Void).staticProperty; it doesn't make any sense!
So, let's make a wrapper type for our functions. And really this goes far beyond just styling views, so let's give it a better name as well.
struct Mod<T> {
var apply: (inout T) -> Void
}
You may not realize it yet, but this just opens up so many possibilities.
Let's write an overload on that configure method from the start of the article.
@discardableResult
func configure<T>(_ item: T, with mod: Mod<T>) -> T {
var result = item
mod(&result)
return result
}
And if we wanted to make our configuration calls even quicker, we could define another custom operator for performing configurations.
precedencegroup FunctionalPipePrecedence {
associativity: left
lowerThan: AdditionPrecedence
}
infix operator |>: FunctionalPipePrecedence
@discardableResult
func |> <T>(item: T, mod: Mod<T>) -> T {
configure(item, with: mod)
}
(Don't worry too much about the precedencegroup business for now; maybe for another post...)
We can also bring back the custom operator we used earlier, or even, if we like, just use an addition operator.
extension Mod {
static func + (lhs: Mod, rhs: Mod) -> Mod {
Mod {
lhs(&$0)
rhs(&$0)
}
}
}
This creates a new Mod whose wrapped function performs the left operation and then the right operation. We've got quite the ergonomic composability! So we can do stuff like this now...!
typealias LabelStyle = Mod<UILabel>
extension LabelStyle {
static let dynamicFont = LabelStyle { $0.adjustsFontForContentSizeCategory = true }
static let centered = LabelStyle { $0.textAlignment = .center }
static let centerTitle = dynamicFont(.title1) + centered
static let multiline = LabelStyle { $0.numberOfLines = 0 }
static func textStyle(_ style: UIFont.TextStyle) -> LabelStyle {
LabelStyle { $0.font = .preferredFont(forTextStyle: style) }
}
static func dynamicFont(_ style: UIFont.TextStyle) -> LabelStyle {
dynamicFont + textStyle(style)
}
static func text(_ newText: String?) -> LabelStyle {
LabelStyle { $0.text = newText }
}
static func alignment(_ alignment: NSTextAlignment) -> LabelStyle {
LabelStyle { $0.textAlignment = alignment }
}
static func caption(_ text: String) -> LabelStyle {
.text(text.uppercased()) + .dynamicFont(.caption1)
}
}
extension Mod where T == DateFormatter {
static let shortDate = Mod { $0.dateStyle = .short }
static let noTime = Mod { $0.timeStyle = .none }
static let episodeDatestamp = shortDate + noTime
}
class ViewController: UIViewController {
static let dateFormatter = DateFormatter() |> .episodeDatestamp
let episodeTitle = UILabel()
|> .text("Building a Layout Library")
+ .centerTitle
+ .multiline
let episodeNumber = UILabel()
|> .text("123")
+ .dynamicFont(.body)
let episodeViews = UILabel()
|> .text("1000")
+ .dynamicFont(.body)
let episodeDate = UILabel()
|> .text(Date() |> dateFormatter.string(from:))
+ .dynamicFont(.body)
let episodeNumberTitle = UILabel() |> .caption("Episode #")
let episodeViewsTitle = UILabel() |> .caption("Views")
let episodeDateTitle = UILabel() |> .caption("Release Date")
//...
}
Think about how much more typing we would need to do if we didn't have these, and how much cleaner our configuration code is with it. I think this is quite powerful, quite readable, and makes our jobs as programmers easier. What more could we ask for?
(Note: This post uses Swift 5.3 and the iOS 14 SDK)
In a recent post, we looked at using Combine to refactor networking code. Before that, I looked at refactoring bloated networking functions. Today I'd like to take another stab at refactoring. This time, however, we'll be using a style that looks a lot like Combine, but uses a more "traditional" functional style without using Combine at all.
Like previously, we'll be starting with the following basic network code.
enum NetworkError: Error {
case decodeError(DecodingError)
case encodeError(EncodingError)
case badResponse(URLResponse?)
case noData
case other(Error)
case unknown
}
class Networker {
func fetch<T: Decodable>(
with request: URLRequest,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
URLSession.shared.dataTask(with: request) { data, response, error in
// catch any errors
if let e = error { return completion(.failure(.other(e))) }
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode)
else { return completion(.failure(.badResponse(response))) }
guard let d = data else { return completion(.failure(.noData)) }
// decode and return
do {
let item = try JSONDecoder().decode(T.self, from: d)
completion(.success(item))
} catch {
return completion(.failure(.decodeError(error)))
}
}.resume()
}
}
As the comments indicate, over half of that completion block is just handling various cases that we're interpreting as an error. Why don't we factor that all out? It makes sense to me to factor it out into a function that takes in the Data?, Response?, and Error? and returns a Result<Data, NetworkError>. We'll also pull out an extra convenience error initializer, which will mostly come in handy later.
extension NetworkError {
init(_ error: Error) {
switch error {
case let ne as NetworkError: self = ne
case let de as DecodingError: self = .decodeError(de)
case let ee as EncodingError: self = .encodeError(ee)
default: self = .other(error)
}
}
}
extension Result where Success == Data, Failure == NetworkError {
static func networkResult(
data: Data?,
response: URLResponse?,
error: Error?
) -> Self {
if let e = error { return .failure(.init(e)) }
guard let hr = response as? HTTPURLResponse,
(200...299).contains(hr.statusCode)
else { return .failure(.badResponse(response)) }
guard let d = data else { return .failure(.noData) }
return .success(d)
}
}
(Using a static func rather than an initializer makes it easier for us to perform early exits, and also signals that this isn't quite a "usual" initializer; it has a specific domain usage.)
Now we can tear a lot of the error-checking code out of the fetch method. We can also make use of Result.flatMap, which allows us to transform a success value (if present) into a new Result with a new Success. With a regular map, this would produce a nested Result<Result<NewSuccess, Failure>, Failure>, but flatMap flattens this into a single-leveled Result.
Now our closure looks a lot simpler and, in my opinion, more readable.
URLSession.shared.dataTask(with: request) { d, r, e in
let dataResult = Result.networkResult(data: d, response: r, error: e)
let itemResult = dataResult.flatMap { data -> Result<T, NetworkError> in
do {
let item = try JSONDecoder().decode(T.self, from: data)
return .success(item)
} catch {
return .failure(NetworkError(error))
}
}
completion(itemResult)
}.resume()
We could even take this further and write a tryMap method on Result (this is present on a lot of Combine publishers).
extension Result {
func tryMap<NewSuccess>(
_ transform: (Success) throws -> NewSuccess
) -> Result<NewSuccess, Error> {
Result<NewSuccess, Error> {
try transform(try self.get())
}
}
}
class Networker {
func fetch<T: Decodable>(
_ type: T.Type,
with request: URLRequest,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
URLSession.shared.dataTask(with: request) { d, r, e in
let result = Result.networkResult(data: d, response: r, error: e)
.tryMap { try JSONDecoder().decode(T.self, from: $0) }
.mapError(NetworkError.init)
completion(result)
}.resume()
}
}
Because we can't have "typed" throws in Swift ([yet][typed throws]), we have to map our error after the tryMap, but otherwise, this is quite nice and functional!
...And we could take it even further if we wanted, making it all declarative and in-line.
URLSession.shared.dataTask(with: request) { d, r, e in
completion(
Result.networkResult(data: d, response: r, error: e)
.tryMap { try JSONDecoder().decode(T.self, from: $0) }
.mapError(NetworkError.init)
)
}.resume()
I don't really like this, though; it's getting too close to being an overly-nested "pyramid of doom." There is a way we can fix it, but it can be a bit divisive...
We can write a custom operator!
/// "Pipe" an item into a function. Can help reduce nesting and improve clarity.
func |> <T, U>(
_ item: T,
_ transform: (T) -> U
) -> U {
transform(item)
}
From what I understand, this operator is present in some functional languages, and although it may seem weird at first, in practice it makes a ton of sense and reads very nicely.
URLSession.shared.dataTask(with: request) { d, r, e in
Result.networkResult(data: d, response: r, error: e)
.tryMap { try JSONDecoder().decode(T.self, from: $0) }
.mapError(NetworkError.init)
|> completion
}.resume()
Looking at this, the "order of operations" is very clear; we take this network result, try mapping it, map any error, and pipe that into the completion closure.
We could go further by writing some "functional types" to essentially wrap various kinds of methods, but I'll leave that to the fellows at PointFree!
(Note: This post uses Swift 5.3 and the iOS 14 SDK)
In a recent post, I described a method of type-erasure in Swift using a complicated, verbose system that took advantage of generics, private wrapper types, and subclassing. It turns out, however, that there's a much quicker, Swiftier, and arguably simpler way to accomplish this that uses closures.
Closures in Swift are essentially functions, but they can behave in potentially surprising ways.
Let's start with a simple example.
var number = 42
let provideNumber: () -> Int = {
return number / 2
}
provideNumber() // 21
This seems pretty normal and expected. We wrote this closure expression that simply returns number (set to 42) divided by 2, and it returns 21. Now what if we write a struct that takes in a closure?
struct NumberProvider {
let get: () -> Int
init(_ get: @escaping () -> Int) {
self.get = get
}
}
let numProvider = NumberProvider(provideNumber)
numProvider.get() // 21
Again, this all seems as expected. NumberProvider takes an @escaping closure (since the closure needs to last after the scope of the initializer; i.e., it needs to escape the expected scope), and when we call that we still get the same number. But what if we now change the value of number?
number = 84
numProvider.get() // 42?!
This might not seem so surprising until you recall that Int is a value type, and value types are supposed to be copied when you pass them around. The closure we passed in, though, is referring explicitly to the original variable. Is that what we want?
Well, the good news is we can explicitly capture the variable.
var newNumber = 42
let newNumProvider = NumberProvider { [number] in number / 2 }
newNumber = 84
newNumProvider.get() // still 21!
If you've dealt with retain cycles, you've probably worked with capture lists by capturing [weak self] in a closure. But you can capture other values and references as well, which is what we've done here. By explicitly capturing number, we've essentially copied the value to a new variable, so we're no longer referring to the original copy. Therefore, we can now change the original number and not affect the one in the closure (which may or may not be what we want).
Now back to the really fun stuff.
Let's use the same protocol we did last time for simplicity's sake, and some really simple implementations of it.
protocol Query: Encodable {
associatedtype Output: Decodable
var queryString: String { get }
}
struct GetPersonWithID: Query {
typealias Output = Person
let id: UUID
let queryString: String
init(id: UUID) {
self.id = id
self.queryString = "get person with id \(id)"
}
}
struct GetPersonWithName: Query {
typealias Output = Person
let name: String
let queryString: String
init(name: String) {
self.name = name
self.queryString = "get person with name \(name)"
}
}
Recall that our purpose with type erasure is to have a solid implementation of Query that can wrap, erase, and group any other implementation of the protocol that has the same Output type, so we can do stuff like this:
let queries: [AnyQuery<Person>] = [
AnyQuery(GetPersonWithID(id: UUID())),
AnyQuery(GetPersonWithName(name: "June Bash")),
AnyQuery(GetPersonWithID(id: UUID()))
]
Last time we had to have a wrapper type, a base type, and a box type that inherited from the base. Here, however, we can get away with just a single struct.
struct AnyQuery<Output: Decodable>: Query {
let queryString: String
private var _encode: (Encoder) throws -> Void
init<Q: Query>(_ query: Q) where Q.Output == Output {
self.queryString = query.queryString
self._encode = query.encode
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}
The most interesting bit here is the encode method. Remember I said closures are essentially functions? Well, that's so essential that we can pass in a function as the closure here. Pretty cool!
And we don't have to explicitly capture anything; our initializer essentially does that work for us when the query is passed into the init function. It's an immutable copy of whatever's passed in. (This will be important in a bit.)
Another mostly-unrelated cool thing we can do is actually sort of keep track of the type that we're wrapping.
struct AnyQuery<Output: Decodable>: Query {
//...
let wrappedType: Any.Type
init<Q: Query>(_ query: Q) where Q.Output == Output {
//...
self.wrappedType = Q.self
}
//...
}
Having that wrappedType there may or may not be useful (maybe it would be used for a cell identifier somewhere or something?), but it's certainly interesting!
There's still one potential problem with this, but we'll have to loop back around to it...
What if we had a mutable property on a protocol? Things get a tiny bit more complicated there.
Let's say, for some bizarre reason, our Query protocol allowed mutating the queryString (I don't think I would ever recommend this, but it's the simplest example I can think of for now).
protocol Query: Encodable {
associatedtype Output: Decodable
var queryString: String { get set }
}
struct GetPersonWithID: Query {
typealias Output = Person
let id: UUID
var queryString: String
init(id: UUID) {
self.id = id
self.queryString = "get person with id \(id)"
}
}
struct GetPersonWithName: Query {
typealias Output = Person
let name: String
var queryString: String
init(name: String) {
self.name = name
self.queryString = "get person with name \(name)"
}
}
Our AnyQuery wrapper has to adjust a bit. We might be tempted to start off with the following.
struct AnyQuery<Output: Decodable>: Query {
var queryString: String
private var _encode: (Encoder) throws -> Void
init<Q: Query>(_ query: Q) where Q.Output == Output {
self.queryString = query.queryString
self._encode = query.encode
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}
There's a major problem here, though. If our wrapped query's encode method relies on the value of queryString, it has no way to access it, since it would be referring to the original query.queryString rather than our newly mutated queryString. So we've got a bit more work to do.
struct AnyQuery<Output: Decodable>: Query2 {
var queryString: String {
get { _getQueryString() }
set { _setQueryString(newValue) }
}
private var _getQueryString: () -> String
private var _setQueryString: (String) -> Void
private var _encode: (Encoder) throws -> Void
init<Q: Query>(_ query: Q) where Q.Output == Output {
var copy = query
self._getQueryString = { copy.queryString }
self._setQueryString = { copy.queryString = $0 }
self._encode = copy.encode
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
}
We've done two important things here:
queryString now has a separate getter and setter.query so that we can mutate it within those closures.Remember earlier with the NumberProvider closure and how it kept referring back to the original method? By initializing var copy and using that within these closures, we're keeping that copy alive and continuing to refer to it. Let's test this:
var anyquery2 = AnyQuery2(GetPersonWithName2(name: "June"))
print(anyquery2.queryString) // "get person with name June"
anyquery2.queryString = "HI THERE"
print(anyquery2.queryString) // "HI THERE"
It works!
...But there's one more danger still lurking, which I alluded to before. Let's make it easier to see by adding a method to our Query protocol (along with a default conformance to make things easier on ourselves) and modify our type-erased wrapper.
protocol Query: Encodable {
associatedtype Output: Decodable
var queryString: String { get set }
func speak()
}
extension Query {
func speak() {
print("Self: \(Self.self)\nOutput: \(Output.self)\nqueryString: \(queryString)")
}
}
struct AnyQuery<Output: Decodable>: Query {
var queryString: String {
get { _getQueryString() }
set { _setQueryString(newValue) }
}
private var _getQueryString: () -> String
private var _setQueryString: (String) -> Void
private var _encode: (Encoder) throws -> Void
private var _speak: () -> Void
init<Q: Query>(_ query: Q) where Q.Output == Output {
var copy = query
self._getQueryString = { copy.queryString }
self._setQueryString = { copy.queryString = $0 }
self._encode = copy.encode
self._speak = copy.speak
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
func speak() {
_speak()
}
}
Now let's test it really quick. If it's working as expected, when we mutate the query string, it should be reflected when we call speak().
var anyquery = AnyQuery(GetPersonWithName(name: "June"))
anyquery.speak()
// Self: GetPersonWithName
// Output: Person
// queryString: get person with name June
anyquery.queryString = "HI THERE"
anyquery.speak()
// Self: GetPersonWithName
// Output: Person
// queryString: get person with name June
Uh oh. The speak() method isn't reflecting the mutations we make as we expect. How do we fix this?
Recall that by using copy within a closure, we hang on to a "reference" to the original. However, by assigning copy.speak to self._speak, we are essentially making another copy of copy, and assigning that copy's speak method to self._speak, so it never gets the mutations we assign in the query string setter. So instead of directly assigning the method to the closure like I was so excited to do, we'll have to wrap that method call in another closure to keep a reference to the copy.
struct AnyQuery<Output: Decodable>: Query {
var queryString: String {
get { _getQueryString() }
set { _setQueryString(newValue) }
}
private var _getQueryString: () -> String
private var _setQueryString: (String) -> Void
private var _encode: (Encoder) throws -> Void
private var _speak: () -> Void
init<Q: Query>(_ query: Q) where Q.Output == Output {
var copy = query
self._getQueryString = { copy.queryString }
self._setQueryString = { copy.queryString = $0 }
self._encode = { try copy.encode(to: $0) }
self._speak = { copy.speak() }
}
func encode(to encoder: Encoder) throws {
try _encode(encoder)
}
func speak() {
_speak()
}
}
var anyquery = AnyQuery(GetPersonWithName(name: "June"))
anyquery.speak()
anyquery.queryString = "HI THERE"
anyquery.speak()
Now the speak method correctly reflects our mutations. Huzzah!
Like I mentioned last time, type erasure isn't always the best solution to this problem, but there are certainly times when it might come in handy, and as long as we remember to test our type erasure thoroughly and remember the idiosyncracies of Swift's closures, this method will likely be quicker to pull off than the base-box-subclass-wrapper dance. And similarly to last time, even if we never use this, hopefully we've learned quite a bit about how closures work in Swift!
(Note: This post uses Swift 5.3 and the iOS 14 SDK)
As I mentioned in my previous post, using the Combine framework might require somewhat of a paradigm shift. In some ways, the best way to accomplish this might be to jump all in feet first and use it for all your networking and reactive code. However, this may not be entirely necessary, as Apple has also made it easy to gradually transition your codebase over to using Combine.
Let's say we have the following vastly-oversimplified networking code. (We'll ignore that we have to use the shared URLSession to fetch data, a default JSONDecoder to decode it, etc.)
enum NetworkError: Error {
case decodeError(DecodingError)
case encodeError(EncodingError)
case badResponse(Int)
case noResponse
case noData
case other(Error)
case unknown
init(_ error: Error?) {
// transform error into NetworkError
}
}
class Networker {
func fetch<T: Decodable>(
with request: URLRequest,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
URLSession.shared.dataTask(with: request) { data, response, error in
// catch any errors
if let e = error { return completion(.failure(.other(e))) }
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode)
else { return completion(.failure(.badResponse(response))) }
guard let d = data else { return completion(.failure(.noData)) }
// decode and return
do {
let item = try JSONDecoder().decode(T.self, from: d)
completion(.success(item))
} catch {
return completion(.failure(.decodeError(error)))
}
}.resume()
}
}
We could replace all of this with Combine, but let's say for the moment that for whatever reason we're not allowed to change or even extend this class; we can only change some of the code that calls it. Let's say one of those places is the following (again ignoring any lack of flexibility for now, and imagining that there could be many more functions, properties, and request configuration):
struct User: Codable {
let id: UUID
let screenName: String
}
struct Habit: Codable {
let id: UUID
let name: String
}
class HabitStore {
let baseURL = URL(string: "http://myendpoint.net")!
let user: User
init(user: User) {
self.user = user
}
func fetchRemoteHabits(completion: @escaping (Result<[Habit], NetworkError>) -> Void) {
let url = baseURL
.appendingPathComponent("habits")
.appendingPathComponent(user.id.uuidString)
let request = URLRequest(url: url)
Networker().fetch([Habit].self, with: request, completion: completion)
}
}
If we wanted to, we could replace and refactor this method and anything that calls it. But we could also just write an extension on the class and an "overload" method (meaning a method with the same name but different parameters) that returns a publisher. There also just so happens to be a publisher that will work quite well with what we already have without many changes.
Let's look at (part of) the definition of the Future class.
class Future<Output, Failure: Error>: Publisher {
typealias Promise = (Result<Output, Failure>) -> Void
init(_ attemptToFulfill: @escaping (@escaping Future<Output, Failure>.Promise) -> Void)
}
That initializer looks quite terrifying; it takes in a closure, which in turn takes in its own closure (typealiased as Promise). Without the typealias, it looks like this:
((Result<Output, Failure>) -> Void) -> Void
Yikes! But look at that inner closure; looks familiar doesn't it? It's the same kind of closure we've been using for our networking completion handler! That makes adapting our existing code very easy.
extension HabitStore {
func fetchRemoteHabits() -> Future<[Habit], NetworkError> {
Future { [weak self] promise in
self?.fetchRemoteHabits(completion: promise)
}
}
}
(Note that because the closure Future takes in is @escaping, we need to explicitly use self, which is a good signal to us that we need to use [weak self] so we don't run into any retain cycles.)
That was easy! Now we can take our time transitioning over to using this implementation of the fetch method. If we wanted to future-proof it, we could even add a .eraseToAnyPublisher so we're not restricted to using the Future publisher, but this shouldn't make much of a difference at the callsite.
Let's say that down the line, we've stopped using the old fetch method entirely, and the rest of our team has given us the okay to replace the old networking method with a publisher. Once again, Apple has made things pretty easy for us; URLSession has a dataTaskPublisher whose Output is a tuple containing both Data and a URLResponse, with any Error as the Failure type. Now we can really leverage Combine to write our fetch method.
extension Result where Success == Data, Failure == NetworkError {
init(data: Data?, response: URLResponse?) {
// check data and response, etc
}
}
extension Networker {
func fetch<T: Decodable>(
_ type: T.Type,
with request: URLRequest
) -> AnyPublisher<T, NetworkError> {
URLSession.shared.dataTaskPublisher(for: request) // 1
.tryMap { (d, r) in try Result(data: d, response: r).get() } // 2
.decode(type: T.self, decoder: JSONDecoder()) // 3
.mapError(NetworkError.init) // 4
.eraseToAnyPublisher() // 5
}
}
Let's walk through each of those lines:
URLRequest.Result initializer whose main purpose is to check that our data and response are both valid, otherwise throw an error. We could instead create a custom validate method if we wanted to.DecodingError if it's unsuccessful.NetworkError from earlier.AnyPublisher to vastly simplify our method's return type.If we wanted to be lazy, we could have just done what we did at first with our call site code and wrap our old code in a Future...
extension Networker {
func fetch<T: Decodable>(
_ type: T.Type,
with request: URLRequest
) -> AnyPublisher<T, NetworkError> {
Future { [weak self] promise in
self?.fetch(T.self, with: request, completion: promise)
}.eraseToAnyPublisher()
}
}
...but where's the fun in that?! (Of course this is a very reasonable option, especially if you still have legacy code to support.)
Either way, the callsite will now look almost exactly the same as before. I've reformatted it here to be all inline & declarative.
extension HabitStore {
func fetchRemoteHabits() -> AnyPublisher<[Habit], NetworkError> {
Networker().fetch(
[Habit].self,
with: URLRequest(
url: baseURL
.appendingPathComponent("habits")
.appendingPathComponent(user.id.uuidString)
)
)
}
}
So if things are generally the same in the end, was there any purpose to all of this?! Well, it basically depends on your preferences and how your networking code will be used. Here's a basic fake view class with a couple of methods; one uses the old fetch that takes in a closure, and the other uses the new fetch that returns a publisher.
class HabitListView {
let store: HabitStore
var habits: [Habit] = []
var cancellables = Set<AnyCancellable>()
init(store: HabitStore) {
self.store = store
}
func onButtonTap_withCompletionClosure() {
store.fetchRemoteHabits { [weak self] result in
switch result {
case .failure(let e):
self?.displayAlert(for: e)
case .success(let habits):
self?.habits = habits
}
}
}
func onButtonTap_withPublisher() {
store.fetchRemoteHabits()
.sink { [weak self] in
if case .failure(let e) = $0 { self?.displayAlert(for: e) }
} receiveValue: { [weak self] in
self?.habits = $0
}.store(in: &cancellables)
}
func displayAlert(for error: Error) {
// do something
}
}
In this case, the Combine version adds the need to hang on to the subscription. Really you could also pass along the dataTask that is returned using the old URLSession method, so you would also have something you could cancel, but we don't need to hang on to the data task like we do with the subscription.
The ability to easily map, merge, and perform other operations on publishers is one potential upside of using Combine. For example, we may want to merge with another publisher that fetches persisted objects from on device, or from another server.
func fetchFromDevice() -> AnyPublisher<[Habit], Error> {
Just([])
.mapError { _ in NetworkError.unknown as Error }
.eraseToAnyPublisher()
}
func onButtonTap_mergeWithDevicePersistence() {
store.fetchRemoteHabits()
.mapError { $0 as Error }
.merge(with: fetchFromDevice())
}
Hopefully that gives an idea of how we might be able to start moving in a more functional/reactive direction. This is really only the tip of the iceberg, though!
(Note: This post uses Swift 5.3 and the iOS 14 SDK)
Combine is Apple's new-ish framework for functional reactive programming, a coding paradigm that specializes in working with asynchronous streams of values in a "declarative" style. That's a lot of buzzwords that may not make a lot of sense until you see it in action. So let's take a look at how we can use it to write more adaptable asynchronous code.
Let's parse out what that phrase actually means. We hopefully already know what "programming" means, but what about the other two words in there?
"Functional" basically means a style of programming that prefers getting its jobs done in a way that uses lots of functions working together. Pure functions, meaning one that simply takes something in and spits something out (with the same output for any input every time) without any side effects, are especially preferred. Often functions will take in other functions (i.e., closures), making them "higher order functions." (If you've ever used map, filter, reduce, etc on arrays, other sequences, or other types, then you've used higher-order functions.)
"Reactive" in this context means that we can react to changes. As a simplified example, have you ever put a didSet observer on a property?
var models: [Model] {
didSet {
updateViews()
}
}
This is, more or less, a kind of "reactive" programming. We're reacting to changes in the models array; when the models change, we do something.
Put together, "functional reactive programming" is using this functional style of programming to react to changes. But what does that look like in practice?
Before we answer that question, let's look at how Combine accomplishes all of this.
The Publisher is a protocol that publishes an Output. It may publish once and finish, or it may go on publishing values forever. It may publish immediately, or it may wait for a network response. It may wrap another publisher, mutate its output, and republish it. What matters is that at some point it spits out a value (or a stream of values). If it can fail for some reason, it may also spit out an error (its Failure). A publisher might also be guaranteed not to fail, in which case its Failure will be Never.
When a publisher fails, it "completes," signaling that anything subscribed to it will no longer receive more values. It might also complete successfully. For example, for fetching data from the internet, a publisher might publish a single value (the data it was asked to fetch), and then signal that it completed successfully. This Completion signal is modeled as an enum very similar to the Result type, but its finished case has no associated value; only its failure case does.
extension Subscribers {
enum Completion<Failure: Error> {
case finished
case failure(Failure)
}
}
To work with these publishers, you can also attach operators to them. Technically most of these are also publishers in their own right, but we call them operators when they're implemented as extensions on the Publisher protocol. You can therefore use dot-syntax to chain operators one after the other, performing powerful transformations like map, flatMap, filtering, merging with other publishers, waiting for another publisher, receiving on specific threads, and more.
URLSession.shared.dataTaskPublisher(for: request)
.tryMap { (d, r) in try Result(data: d, response: r).get() }
.decode(type: T.self, decoder: JSONDecoder())
.mapError(NetworkError.init)
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
We get the values published by these using a Subscriber, another protocol. Like operators, most subscribers are implemented as extensions on the Publisher protocol, and are usually accessed via dot syntax. Subscribing to a publisher produces a Subscription, which you'll need to hold a strong reference to if you want to continue to receive values. Usually you'll have access to this via an AnyCancellable instance. You can explicitly cancel this subscription, or it will be auto-cancelled if you let it die by letting go of all strong references to it.
Let's look at a couple of simple examples. Our first example uses PassthroughSubject. This publisher can be sent values via the send method, and subscribers can subscribe to it like any other publisher.
Try putting this in a new playground:
import Combine
struct SimpleError: Error, CustomStringConvertible {
let description: String
init(_ description: String = "unknown error") {
self.description = description
}
}
let stringSignal = PassthroughSubject<String, SimpleError>()
stringSignal.send("Hello, world!")
Of course, nothing happens yet, because we haven't subscribed to it yet. Let's do that! The easiest way is via the sink operator. This allows you to pass in two closures; the first will be run when the publisher "completes", and the second anytime the publisher publishes a value.
Add the following below our previous code:
stringSignal
.sink { completion in
switch completion {
case .finished:
print("all done!")
case .failure(let error):
print("WHOOPS. Error: \(error)")
}
} receiveValue: { str in
print("received message: \(str)")
}
We still don't see anything, though. That's because we didn't subscribe until after the string signal sent its value. If you add stringSignal.send("Hello again!!") after we set up our subscription, we should then see "received message: Hello again!!" printed to the console.
There's another "subject" publisher called CurrentValueSubject that works very similarly; I'll let you take a look at that in Apple's documentation! For now, let's look at another publisher type.
let justAString = Just("Hello goodbye.")
Just, like its name implies, just publishes a single value when it's subscribed to, and then it's done. This may not seem super useful, but we'll see later how it can actually really come in handy in some scenarios. Here's a more simple use case, though.
let justAString = Just("Hello goodbye.")
justAString
.sink { print("Someone said: \($0)") }
justAString
.sink { _ in print("See ya!") } receiveValue: { _ in }
First, notice we don't have to handle the completion case at all if we don't want to. This is true of any publisher whose Failure type is Never; it can't fail, so we can choose to ignore the completion entirely if we want to.
Also notice that if you run this code now, we'll see the "someone said" print statement, but the second subscription won't fire. That's because Just publishes its value just once and immediately completes, so any additional subscribers are essentially arriving at the party after it's already finished. It's a very short one-person party.
Let's try publishing a bunch of stuff from our stringSignal from earlier.
stringSignal.send("blep")
stringSignal.send("539")
stringSignal.send("beep")
stringSignal.send("77.22")
stringSignal.send("123.333")
stringSignal.send("bwup")
If you run this, you'll see that the previous subscription still does its thing. Let's add another subscriber. Somewhere before these signals are sent (remember we need to set up the subscription before the value is published, or else we'll miss it), add the following code:
stringSignal
.map { Double($0) }
.sink { _ in } receiveValue: { print("Double! \($0)") }
We're using the Double initializer to turn those strings into doubles if we can. But in the console, we'll see some awkward output, and we'll also get the compiler warning us about a debug description. Remember that the Double initializer returns an Optional value, since not all strings can be converted into doubles.
So instead, we can replace map with compactMap to essentially ignore any nil values.
stringSignal
.compactMap { Double($0) }
.sink { _ in } receiveValue: { print("Double! \($0)") }
Alternatively, we might want to stop and throw an error if there are any nil values. For that, we could use tryMap.
stringSignal
.tryMap { str throws -> Double in
guard let d = Double(str) else { throw SimpleError() }
return d
}.sink { _ in } receiveValue: { print("Double! \($0)") }
With compactMap, though, we could even simplify further. Consider that the Double initializer could be thought of as a function that takes in a String and returns Double? ((String) -> Double?). And recall that any function that takes in a closure could also take in a function. Now look at the signature of the closure that compactMap takes in ((T) -> U?). (String) -> Double? fits that pattern! So rather than calling the method within a closure we provide, we can instead just provide the initializer itself!
stringSignal
.compactMap(Double.init)
.sink { _ in } receiveValue: { print("Double! \($0)") }
There's a lot more we could potentially do with operators; for one, we could merge this publisher with the other string publisher we made, so it acts as one stream of values! First, though, we'll have to decide how we want to handle the difference in errors. Remember our passthrough subject might fail, but our Just publisher can't fail. This is unfortunately a bit of a pain to solve, but here's the best solution I've come up with:
stringSignal
.merge(with: justAString.mapError { _ in SimpleError() })
.sink { completion in
print(completion)
} receiveValue: { str in
print("merged publisher says: \(str)")
}
Since we can't merge these two publishers without addressing the difference in Failure types, we have to adjust one of the errors. It makes the most sense to map the error of the Just publisher; since it will never fail, that mapError closure will never actually run, but it signals to the compiler that their failure types are now equivalent.*
I'd highly recommend just browsing through the various operators you can perform on publishers; I feel like I'm still learning new operators and usages for them every time I use Combine!
If you're like me, you might be a little worried about that PassthroughSubject's send method being so out-in-the-open like that. Consider the following code:
struct Habit: Codable {
let id: UUID
let name: String
// ...
}
struct HabitListState {
var habits: [Habit] = []
// ...
}
class HabitsStore {
private(set) var state = CurrentValueSubject<HabitListState, Never>(HabitListState())
// ...
}
Anything that can access an instance of HabitStore can send a new value through the state publisher. We may not want that; maybe we only want to be able to subscribe to it. In this case, we could easily fix this by using the @Published property wrapper, and that would be my recommended way to go, but let's pretend for a moment that that didn't exist. How would we make it possible to subscribe to the publisher, but not to publish new values?
class HabitsStore {
private var _state = CurrentValueSubject<HabitListState, Never>(HabitListState())
var state: AnyPublisher<HabitListState, Never> {
_state.eraseToAnyPublisher()
}
//...
}
The eraseToAnyPublisher operator wraps, well, any publisher, and essentially forwards all the usual publisher protocol methods to its wrapped, "erased" publisher. It doesn't matter what kind of publisher it was before; what matters is that it's some kind of publisher that outputs HabitListState and never fails.
But we can go further with this and make our code more testable using this type erasure. Let's make a protocol called HabitStoring. We'll have our HabitsStore conform to that class, and in testing we can replace it with a mock implementation of the protocol.
protocol HabitStoring {
var state: AnyPublisher<HabitListState, Never> { get }
//...
}
class HabitsStore: HabitStoring {
// (same as before)
}
extension Array where Element == Habit {
static let mockHabits = [
Habit(id: UUID(uuidString: "00000000-0000-0000-0000-000000000000")!,
name: "Exercise"),
Habit(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!,
name: "Meditate"),
]
}
struct MockHabitStore: HabitStoring {
var empty: Bool
// other options
var state: AnyPublisher<HabitListState, Never> {
Just(HabitListState(habits: empty ? [] : .mockHabits))
.eraseToAnyPublisher()
}
//...
}
As you can see, behind the scenes, the publishers are completely different. One is a CurrentValueSubject and the other is a Just. But since they're both type-erased, all that matters is that the output and failure types are the same. We could even have several different options of publishers and erase them all so the return type is always the same.
struct MockHabitStore: HabitStoring {
var version: MockHabitStoreVersion
var state: AnyPublisher<HabitListState, Never> {
switch version {
case .empty:
return Just(HabitListState(habits: [])).eraseToAnyPublisher()
case .twoHabits:
// Just doing this one a different way to show that it could be anything!
return Array.mockHabits.publisher
.collect()
.map(HabitListState.init)
.eraseToAnyPublisher()
case .duplicates:
return Just(.mockHabits)
.append(.mockHabits)
.map(HabitListState.init)
.eraseToAnyPublisher()
}
}
}
Let's try another experiment. Let's say we've got the following class.
class ListView {
@Published var strings: [String] = []
init() {
$strings
.sink { print("new strings! \($0)") }
}
}
The @Published property wrapper, as I previously alluded to, is sort of like a wrapped CurrentValueSubject that allows us to easily change/send new values as if it were a regular variable, but we can also access its publisher using the $ in front of the variable name (in property wrapper terminology, this is actually accessing the wrapper's projectedValue).
Let's now try instantiating this class and setting its strings.
let myListView = ListView()
myListView.strings = ["bleep", "blep", "meep", "morp"]
You might expect to see those strings printed, as we set up a sink that did just that. And indeed, we see an empty array printed out. However, note that sink returns an AnyCancellable, which we've thus far ignored. That's our subscription, and if we want to keep running the closure we pass into sink, we need to hang onto it somehow! (Note that we didn't need to do this in a playground (I'm not certain why this is, but it doesn't matter much for now).)
If our class is just going to have one subscription, we can just make it a simple property on the class.
class ListView {
@Published var strings: [String] = []
private var stringSubscription: AnyCancellable?
init() {
stringSubscription = $strings
.sink(receiveValue: { print("new strings! \($0)") })
}
}
Or, if we're going to have several subscriptions, it might make more sense to just throw them all in a colLection. If we make that collection a Set, we even get a handy convenience operator:
class ListView {
@Published var strings: [String] = []
private var subscriptions: Set<AnyCancellable> = []
init() {
$strings
.sink(receiveValue: { print("new strings! \($0)") })
.store(in: &subscriptions)
}
}
Using .store here is essentially a shortcut to writing:
subscriptions.insert(
$strings.sink(receiveValue: { print("new strings! \($0)") })
)
...which feels a lot weirder to write. In any case, we are now holding on to the subscription, so it'll keep doing its thing until we call cancel on it or it gets deallocated when we let go of all strong references to the list view holding the subscription.
We covered a lot of ground, but this is only the beginning! Combine is a powerful framework that takes a bit of a paradigm shift to fully grasp, but once you do, it makes operations of all kinds much easier to build, compose, parse, mutate, and generally work with.
If you want to read more on Combine, here's a few resources:
As I mentioned earlier, I seem to learn new Combine stuff every time I use it. Today was no exception!
It turns out that this code from earlier...
stringSignal
.merge(with: justAString.mapError { _ in SimpleError() })
.sink { completion in
print(completion)
} receiveValue: { str in
print("merged publisher says: \(str)")
}
...has a simpler solution.
Since justAstring's error type is Never, we have to make its Failure type to match stringSignal's SimpleError in order to perform the merge operation, even though that mapError closure will never actuall run. There's a simpler way to do this though.
stringSignal
.merge(with: justAString.setFailureType(to: SimpleError.self))
.sink { completion in
print(completion)
} receiveValue: { str in
print("merged publisher says: \(str)")
}
This will essentially get the same result, but setFailureType was explicitly meant for this type of scenario. The previous version was using a sledgehammer where a regular hammer would suffice.
(Note: This post uses Swift 5.3)
Type erasure is what allows us types like AnyCollection. For any type that conforms to the Collection protocol, their interfaces will be exactly the same (except of course for the type of Element used). AnyCollection allows us to wrap up any collection and swap in one (e.g., an Array) for another (e.g., a Set, or even a custom collection type).
Unfortunately, writing a type-erased instance of a protocol is far more complicated than it has any right to be, and personally I avoid it where at all possible. Still, there are instances where it can be very useful, and it's sort of interesting from a mere curiosity perspective. So let's take a look at how we can write a type-erased implementation of a protocol!
We're going to be using the situation from my previous post, where we have a networking function that takes in a query and returns a publisher for our expected output.
Here's what the method signature and the protocol looked like then.
func query<Input: QueryInput>(_ input: Input) -> AnyPublisher<Input.Output, QueryError>
protocol QueryInput: Encodable {
associatedtype Output: Decodable
static var queryString: String { get }
}
One quick change I'll get out of the way now: from what I could tell, we won't be able to use a static property for our queryString. The Any_ wrapper needs to be able to get a different string for whatever it's wrapping without actually knowing what it is after initialization, and the only way to do that is by making it an instance property rather than a static one.
With that in mind, here's how our protocol looks now:
protocol QueryInput: Encodable {
associatedtype Output: Decodable
var queryString: String { get }
}
In the implementation, this change shouldn't make much of a difference; we can implement it in such a way that the caller will never be able to change those query strings.
So let's say we're working with the following query structs:
struct FetchPersonWithName: QueryInput {
typealias Output = Person
let name: String
let queryString: String = "blah blah blah"
init(_ name: String) {
self.name = name
}
}
struct FetchPersonIDWithName: QueryInput {
typealias Output = UUID
let name: String
let queryString: String = "blah blah blah blah"
init(_ name: String) {
self.name = name
}
}
struct FetchEventCreator: QueryInput {
typealias Output = Person
let eventID: UUID
let queryString: String = "blah blah blah blah blah"
}
Our goal is to be able to make an array of queries that happen to have the same Output. Right now, if we try to do that using just our protocol, we'll get an error about Self or associatedType.
// This won't compile!
let queries: [QueryInput] = [
FetchEventCreator(eventID: myEvent.id),
FetchPersonWithName(name: "June Bash")
]
We don't have any way to tell the compiler that we just QueryInputs with an Output of Person. Our solution will be to implement a type that conforms to QueryInput, but for each of its requirements, it simply "forwards" from another type of query input that has the Output we want. So in the end it'll look something like this:
let fetchMyEventCreator = FetchEventCreator(eventID: myEvent.id)
let fetchJune = FetchPersonWithName(name: "June Bash")
let queries: [AnyQuery] = [
AnyQuery(fetchMyEventCreator),
AnyQuery(fetchJune)
]
Now comes the weird and fun part. The final structure of our AnyQuery wrapper will look like this when all is said and done (generalized for any type-erased):
final class AnyX<AssociatedType>: XProtocol {
var requiredProperty: Type { box.requiredProperty }
private var box: _AbstractAnyXBase<AssociatedType>
init<Wrapped: XProtocol>(_ wrapped: Wrapped) where Wrapped.AssociatedType == AssociatedType {
self.box = _AnyXBox(wrapped)
}
}
(The underlines in some of those names are just an extra signal to anyone looking at the code that they're a private implementation detail; the end user should never be seeing any of that. We'll also make it all private or fileprivate to enforce that with the compiler.)
_AbstractAnyXBase will be an "abstract" class; we won't be able to instantiate an instance of that base class, but we will subclass it and use that in place of a property asking for the base class, similar to how we can use a UILabel where a UIView is expected.
That's where _AnyXBox comes in; it subclasses _AbstractAnyXBase, and we use that in place of the base class. Why do we do this? That's where the magic comes in: they're generic over different types. At this point it's probably best to leave the general case and move over to our specific case.
Here's what our abstract base class looks like for our queries:
fileprivate class _AnyQueryBase<Output: Decodable>: QueryInput {
var queryString: String { fatalError("must use subclass") }
private init() { fatalError("must use subclass init") }
func encode(to encoder: Encoder) throws {
fatalError("must use subclass") // maybe use more descriptive messages
}
}
Your first impression might be "Ahhh! Everything causes a fatal error!" But remember, we won't ever be using an instance of this base class (and since the initializer is private, we can't even initialize it). We'll only be using the subclass, which will override everything.
What's more important is that this base class matches the signature we're after: it's generic over the output, and it conforms to QueryInput. That may not seem important yet, so let's move on to the subclass:
fileprivate final class _AnyQueryBox<Input: QueryInput>: _AnyQueryBase<Input.Output> {
typealias Output = Input.Output
var wrapped: Input
override var queryString: String { wrapped.queryString }
init(_ input: Input) {
self.wrapped = input
}
override func encode(to encoder: Encoder) throws {
try wrapped.encode(to: encoder)
}
}
Now we're getting somewhere. We're "forwarding" requirements of the protocol (i.e., the queryString property and the encode method) to our wrapped input, so no more fatal errors. But the magic is again in the generics.
Our box is now generic over the Input of the query... but it subclasses from a base generic over that input's Output. So anywhere we might ask for an _AnyQueryBase<Person>, we could feed this Box any query where the output is Person, and use it in place of the subclass.
"So why don't we just use these two classes?" you might ask. "Why do we have to wrap it in yet another class?"
Essentially, it's to make it easier for anyone using this API. I don't want to have to remember to never instantiate this base class, to use its signature all over but always instantiate only its subclass. Especially if we were to leave a codebase and come back, it would simply be harder to reason about and use if we're counting on us to remember that all or even read all the documentation that we left. So we're doing more work up front to save ourselves time and energy later, which I think is one of our primary duties as programmers.
With that in mind, let's finish the final wrapper, the class that folks will actually be using.
final class AnyQuery<Output: Decodable>: QueryInput {
var queryString: String { box.queryString }
private var box: _AnyQueryBase<Output>
init<I: QueryInput>(_ input: I) where I.Output == Output {
self.box = _AnyQueryBox(input)
}
func encode(to encoder: Encoder) throws {
try box.encode(to: encoder)
}
}
Now, as intended, our final class is generic over just the output type. The only place where we consider our input is in the initializer.
I here is simply the actual type of the Query we're wrapping (e.g. FetchPersonWithName or FetchEventCreator). The _AnyQueryBox we use (which, again is a subclass of _AnyQueryBase) will be generic over this query type I. The Output of our AnyQuery and of our _AnyQueryBase will be generic over the Output of I. Once we set the box property to the box we make in the initializer, our AnyQuery forgets all about I; all it knows is that box is an _AnyQueryBase<Output> (or in this case, a subclass of it).
This all might look very confusing, I know. I didn't really get it until I made my own protocol with an associated type and made my own type-erased box around it. I'd highly recommend giving this a try if you'd like to understand it better!
Now, finally, we can make an array of different queries that spit out a Person.
let fetchMyEventCreator = FetchEventCreator(eventID: myEvent.id)
let fetchJune = FetchPersonWithName(name: "June Bash")
let queries: [AnyQuery] = [
AnyQuery(fetchMyEventCreator),
AnyQuery(fetchJune)
]
...and we can, of course, use it in our query method.
for q in queries {
query(q)
.sink(// ...etc
}
We could even use the publisher that Combine adds to arrays to do some work in a slick, functional style.
queries.publisher
.flatMap(query(_:))
.collect()
.sink { completion in
if case .failure(let error) = completion {
print(error)
}
} receiveValue: { people in
print(people)
}.store(in: &cancellables)
...But maybe that's best saved for another time.
In my previous post, we discussed a better solution to for this particular problem; having to wrap everything in AnyQuery is pretty tedious, not to mention that whole rigamarole we went through just to implement the type-erasure.
However, writing a struct in place of a protocol won't always be the most effective way to go, in and some cases it may not even be possible. And hey, we had some fun, and we got more comfortable with how protocols, generics, and subclassing work in Swift! Even if this may not be an ideal solution, now we can see clearly why, and we're better equipped to handle similar challenges in the future.
(Note: This post uses Swift 5.3)
I've been going through PointFree's video backlog lately and have been learning a ton. The concept that's stuck in my mind the most, though, is that of using a generic struct as a replacement for protocols. If you've ever been frustrated working with protocols, running into issues with associatedTypes and Selfs and type erasure, then you may have your mind blown a little bit.
Let's say we're working on a networking class for a social media app. Our backend uses GraphQL, but using the Apollo framework would be overkill for our purposes, so we're handling all of the network code ourselves. (We'll also be using the Combine framework's publishers rather than traditional completion closures; I'll be writing a separate post about that in the near future.)
Our main Person model looks like this:
struct Person: Codable {
let id: UUID?
var name: String
let birthdate: Date
let friends: [Person]
init(id: UUID? = nil, name: String, birthdate: Date, friends: [Person] = []) {
self.id = id
self.name = name
self.birthdate = birthdate
self.friends = friends
}
}
...and we want the main query/mutation method signature to look something like this:
func query<Input, Output>(_ input: Input) -> AnyPublisher<Input, QueryError>
...where QueryError is just a simple Error-conforming struct.
How can we make this method as simple to use as possible, where the callsite can look as simple as this?
query(.addPerson(named: "June Bash", withBirthdate: myBirthdate))
.sink(/* handle response here */)
.store(in: &cancellables)
One thing that might be nice is to have an enum that stores all of our queries, like this:
enum QueryInput: Encodable {
case addPerson(Person)
case fetchFriends(UUID)
var queryString: String {
switch self {
// insert query string for each case here
}
}
}
(GraphQL queries and mutations are formatted in a very specific way; the way we handled it recently in our work on the Eco-Soap Bank app was to have these static strings that would be sent as part of the request. Usually code for these would be generated by the Apollo framework, but that sort of code generation and third-party library usage isn't really necessary, and this gives us finer-grained control of how our app works.)
But there's a problem: how will the query method know what the output is? Maybe we can add a computed property to the enum.
extension QueryInput {
var outputType: ??? {
switch self {
case .addPerson: return UUID.self
case .fetchFriends: return [Person].self
}
}
}
...but what is the actual type of outputType? Well, it could be just about any type... so let's try using Any.Type.
extension QueryInput {
var outputType: Any.Type { /*...*/ }
}
This will compile, but actually, it needs to be Decodable, so let's switch it to...
var outputType: Decodable.Type
...but when we go to adjust our query method, we run into more problems...
func query(_ input: QueryInput) -> AnyPublisher<QueryInput.outputType, QueryError>
// Error: "Property 'outputType' is not a member type of 'QueryInput'"
The compiler can't tell what the output should be, and there's isn't any way (that I'm aware of) for us to tell the compiler to look at this computed property to determine the type. We could make the enum generic and just pass in what we're expecting every time, but that would complicate the callsite more than we'd like, and it would be too easy to make programmer errors (for example, I could pass in a fetchFriends query and tell it I'm expecting a [Double: [UInt8]] back).
Perhaps we can make a protocol that our input can conform to; something like this:
protocol QueryInput: Encodable {
associatedtype Output: Decodable
static var queryString: String { get }
}
...and we can extend our Person model as such:
extension Person: QueryInput {
typealias Output = UUID
static var queryString: String { "<insert GraphQL query string here>" }
}
...as well as UUID, which will be the input for fetching friends.
extension UUID: QueryInput {
typealias Output = [Person]
static var queryString: String { "<insert GraphQL query string here>" }
}
Now we can adjust our query method, and it looks like it just might work!
func query<Input: QueryInput>(_ input: Input) -> AnyPublisher<Input.Output, QueryError>
However, there's still a few problems. Here's what it looks like when we call this method for fetching friends:
query(personID)
.sink // ...etc...
It's not easy to tell at a glance that what we're doing is fetching friends. Even worse, what if there was also an Event model that had an id property of type UUID, and we wanted to fetch some info about that? We've already conformed UUID to the protocol for use with fetching friends, and we can't conform to the protocol twice.
One solution might be to make a wrapper struct for each possible input and output, each of which would conform to QueryInput. We could even "namespace" them in an empty enum (this is how the Combine framework namespaces some of its publishers):
enum QueryInputs {
struct AddPerson: QueryInput {
typealias Output = UUID
let name: String
let birthdate: Date
static let queryString = "<insert query string here>"
}
struct FetchFriends: QueryInput {
typealias Output = [Person]
let id: UUID
static let queryString = "<insert query string here>"
}
}
Now, the callsite looks like this:
query(QueryInputs.AddPerson(name: "June Bash", birthdate: myBirthdate))
.sink( // etc...
I think this will work!
...But it could be better. It's just a bit longer than our "ideal" that we started out with. Because QueryInputs (plural) isn't actually the type of this input, we can't remove anything there.
Even worse, let's say we want to make an array of some different queries that fetch a Person. When we go to initialize this array, we immediately run into a problem...
let queries: [QueryInput] = []
// Error: Protocol 'QueryInput' can only be used as a generic constraint because it has Self or associated type requirements
That dreaded Self/associated type error. We can't add a generic to it either; a protocol with an associated type is technically not a generic type.
This is, as far as I've been able to tell, a limitation of the language, and it's why complicated type erasure exists. I'll be writing a separate post later about type erasure works, but for our purposes, there's a much simpler, more elegant, and, in my opinion, more interesting solution.
We can accomplish what we want using one simple generic struct. And although the PointFree folks like to call this a "protocol witness," despite the name, we don't need any protocol at all.
struct QueryInput<Output: Decodable>: Encodable {
var input: Encodable
var queryString: String
init(_ input: Encodable, queryString: String) {
self.input = input
self.queryString = queryString
}
func encode(to encoder: Encoder) throws {
try input.encode(to: encoder)
}
}
Our input could be anything that can be encoded; what matters is the output type. This is somewhat similar to how a traditional type-erasure would work, but with much, much less work.
One thing you might notice is that we see the output type in the signature there... but it's not used anywhere (yet). This is a "phantom type" (which you can read more about at Hacking with Swift). It's not stored anywhere in an instance of the type, but it can be used when we pass it in to our query method.
So how do we construct one of these? It would be a pain in the butt to have to give it the query string every time we run a query. Luckily, we can take advantage of the language to add some convenience "initializers"...!
extension QueryInput where Output == UUID {
static func addPerson(_ person: Person) -> QueryInput<UUID> {
QueryInput<UUID>(person, queryString: "<insert query here>")
}
}
And we can make a few quick changes to make it even simpler, especially at the callsite:
extension QueryInput where Output == UUID {
static func addPerson(
named name: String,
withBirthdate birthdate: Date
) -> Self {
Self(
Person(name: name, birthdate: birthdate),
queryString: "<insert query here>"
)
}
}
We can replace QueryInput<UUID> with Self, since the compiler can infer these specifics from the where clause in the extension signature. This will make it easier as we add more static convenience methods for our different queries.
At first glance, the rest of it may look more complicated, but compare the two callsites for these implementations:
query(
.addPerson(
Person(
name: "June Bash",
birthdate: Date()
)
)
)
query(
.addPerson(
named: "June Bash",
withBirthdate: Date()
)
)
The first has an additional layer of nesting (which the second avoids), and the second lets us customize our parameter names to make it read almost like a sentence ("query: add person named 'June Bash' with birthdate Junevember 41nd, 1010220"). Which you prefer might depend on the context; you could even keep both if it made sense!
Also, notice that we've finally got the clean syntax for calling our queries that we set out to attain at the start! And we can now have arrays of different queries that expect the same output, just like with traditional type erasure:
extension QueryInput where Output == Person {
static func fetchPerson(withName name: String) -> Self {
Self(name, queryString: "<insert query string here>")
}
static func fetchOrganizer(forEventID id: UUID) -> Self {
Self(id, queryString: "<query string here>")
}
}
var queries: [QueryInput<Person>] = [
.fetchPerson(withName: "June Bash"),
.fetchOrganizer(forEventID: UUID()),
.fetchPerson(withName: "Jim Bash")
]
Which gives us some powerful flexibility:
var cancellables = Set<AnyCancellable>()
var people = [Person]()
for q in queries {
query(q)
.sink { completion in
if case .failure(let error) = completion {
print(error)
}
} receiveValue: { person in
people.append(person)
}.store(in: &cancellables)
}
...And we could even leverage Combine to make this even cleaner (more on what all this means later...):
queries.publisher
.flatMap { $0.run() }
.collect()
.sink { completion in
if let e = completion.error { print(e) }
} receiveValue: { people in
// handle full array of people
}.store(in: &cancellables)
At this point, depending on the scenario, there may not even be a reason to have a whole separate networking class; we could run a query directly from this struct, perhaps with a quick rename...
struct Query<Output: Decodable>: Encodable {
var input: Encodable
var queryString: String
init(_ input: Encodable, queryString: String) {
self.input = input
self.queryString = queryString
}
func encode(to encoder: Encoder) throws {
try input.encode(to: encoder)
}
func run() -> AnyPublisher<Output, QueryError> {
// do the work of making encoding the data, making the request, running the query, decoding the data, etc
}
}
Now running a query to fetch a person with a specific name is a simple as:
Query.fetchPerson(withName: "June Bash").run()
.sink( // etc...
We could even pass in separate encoders or decoders for different queries, different URLSessions (or mock replacements), different URLs or endpoints, or even a closure in place of run. All we'd need to do is add a property and use that in the run() method.
To solve our problem of making generic network queries, we tried a non-functional enum, a protocol, a namespaced input struct that conforms to that protocol, and finally landed on a simple struct that allows us to essentially do everything in one spot. This solution also allows for greater testability, of course, which is yet another benefit for which we would usually go to protocols. But we've managed to avoid the downsides of protocols, like issues around Self, associated types, and type erasure.
Although it's not necessarily the most intuitive solution at first glance, it ends up being really simple to write and use once you know how to make it work.
(Note: This post uses Swift 5.2 and the iOS13 SDK)
It's been almost exactly a year since I started taking classes at Lambda School, and now I'm on the cusp of completing my time there and starting my job search in earnest (hit me up if you know a place!). As part of completing my "endorsement" (aka graduation), I need to write a "what I learned at school today"-style article. Once again you can probably read my hint of snarkiness, but also, again, I think this is a potentially useful exercise.
My team worked with the non-profit organization Eco-Soap Bank. They describe themselves thus:
Eco-Soap Bank is a humanitarian and environmental non-profit organization working to save, sanitize, and supply recycled hotel soap for the developing world. Our work has three objectives:
- Contribute a highly cost-effective hygiene product to improve health.
- Significantly reduce the waste generated by the hotel industry.
- Provide livelihoods and free education to disadvantaged women with no other reliable source of income.
Lack of access to soap remains a critical factor in fighting the spread of preventable diseases worldwide. In some areas of the developing world, only 1% of households have soap for handwashing. Eco-Soap Bank seeks to address the critical need for hygiene. Working since 2014, Eco-Soap Bank has sustainably supplied over 650,000 people with soap and hygiene education.
This is definitely a project I can get behind, so I was happy to be building an app for their hotel partners to interface with the company.
In general our team has been working quite well together, despite some different work styles and communication styles. One thing that's come up a couple of times is not quite undercommunication, but just not communicating certain important things that we didn't realize were important.
For example, at one point a team member and I both ended up making helper functions for layout code. After realizing we had both put in this work, we ended up using one set of functions for consistency. They were spur-of-the moment decisions, but they ended up having ramifications.
In the future, I'd love to set up some central repository whose sole purpose would be to record and report what each team member is working on at any given moment. This way, upon starting any task, I could see what everyone else is working on and avoid this sort of overlap. Making this easy to set up would be key, of course; no one wants to use a clunky tool, as evidenced by our minimal use of some of the tools we'd been asigned to use.
For our app's architecture, we decided to use the coordinator pattern (which I recently wrote about here). This allows us to more easily create views programmatically and separate concerns between views and other objects, which in turn increases testability.
It's not without its downsides, though. Being a non-standard pattern, we had to figure out for ourselves how best to use it, how to inject dependencies, what class had ownership of what, etc. Along the way I think we've learned ways to make the pattern work better, including working in a more test-driven development way to ensure everything is as testable as possible even beyond the coordinators and view layer. Use of protocols also helps tremendously so that we can insert mock data in lieu of our live networking code, views, and delegates.
I decided early on to write some of my view code in SwiftUI, Apple's newer UI framework. Many tasks end up being much quicker and easier using this new framework, and I was excited to get to play with it in production.
This turned out to be a bad call. Although it is possible to do anything in SwiftUI that you could in UIKit by bridging to the older framework, this often turns out to be more work than it would have been to just write it in UIKit in the first place, especially when it involves rewriting entire views because SwiftUI obfuscates certain options away from any kind of customizability.
As I went I often discovered 100% "native" SwiftUI methods for accomplishing certain things, but they involved a whole new way of conceiving of things. For example, here's a simplified version of code I wrote to horizontally align the edges of vertically stacked views.
struct EditProfileView: View {
@State var profile: EditableProfileInfo
/// The width of the widest label; used for aligning labels in a column.
@State var labelWidth: CGFloat?
var body: some View {
Form {
Section(header: Text("Name".uppercased())) {
self.textField("First", text: $profile.firstName)
self.textField("Middle", text: $profile.middleName)
self.textField("Last", text: $profile.lastName)
}
}
}
/// A custom label and text field, aligned vertically.
func textField(_ title: String, text: Binding<String>) -> some View {
LabelAlignedTextField(
title: title,
labelWidth: $labelWidth,
text: text
)
}
}
struct LabelAlignedTextField: View {
let title: String
@Binding var text: String
@Binding var labelWidth: CGFloat?
init(title: String, text: Binding<String>, labelWidth: Binding<CGFloat?>, ) {
self.title = title
self._text = text
self._labelWidth = labelWidth
}
var body: some View {
HStack(alignment: .center) {
Text(title)
.readingGeometry(
key: WidthKey.self,
valuePath: \.size.width,
onChange: {
if let new = $0, let old = self.labelWidth {
self.labelWidth = max(new, old)
} else {
self.labelWidth = $0
}
}
).frame(width: labelWidth, alignment: .leading)
TextField(title, text: $text)
}
}
}
extension LabelAlignedTextField {
struct WidthKey: PreferenceKey {
static var defaultValue: CGFloat?
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
if let new = nextValue() {
if let old = value {
value = max(old, new)
} else {
value = new
}
}
}
}
}
extension View {
/// Reads the provided key path value from a `GeometryReader`'s proxy
/// applied to the view's background and applies it to the provided
/// preference key, performing the provided closure when the preference
/// changes. Useful for reading size values, writing them to a size binding,
/// and aligning several views according to this value.
func readingGeometry<K: PreferenceKey, V>(
key: K.Type,
valuePath: KeyPath<GeometryProxy, V>,
onChange: @escaping (K.Value) -> Void
) -> some View where K.Value == V?, V: Equatable {
self.background(GeometryReader { proxy in
Color.clear
.preference(key: K.self,
value: proxy[keyPath: valuePath])
}).onPreferenceChange(K.self, perform: { onChange($0) })
}
}
Yes, all that code was written to get this.

Some of the code was written in a way to increase reusability, of course (though I only ended up reusing it once or twice, to the point that I probably should not have wasted the time in writing the reusable version). It's also quite possibile that I'm missing a much more obvious way of accomplishing this goal (although I did steal the GeometryReader-background-Color.clear-PreferenceKey-alignment trick from objc.io, and they seem to know what they're talking about).
The point still stands, however, that this likely would have been much simpler in UIKit. Although a couple of views still use SwiftUI, I ended up having to entirely re-write some SwiftUI views using UIKit, which obviously ended up wasting some time in the end. Still, the sunk cost fallacy comes to mind; no use wasting more time on something that's not working just because you've already spent a lot of time on something. Sometimes this lesson is best learned the hard way.
A couple more quick tidbits I learned this year at Lambda School:
I of course reserve the right to change my mind on any of these.
Happy graduation!
(Note: This post uses Swift 5.2 and the iOS13 SDK)
The coordinator pattern is probably my favorite "unofficial" design pattern in semi-common usage in the iOS community.
There's a lot of great articles out there about what it is and why it's beneficial1, so that's not my purpose here today. As of iOS13 and the introduction of the SceneDelegate, the way to implement it has changed a little bit, so I thought I'd write a bit about what needs to be done to make it work.
There's a lot of variations on the coordinator pattern; you can use one coordinator, you can use several conforming to a protocol, you can have the view controllers hold weak references to directly, you can have them interact only through custom delegate protocols, you can have one storyboard that contains all your view controllers, you can have one storyboard for each view controller, or anything between those extremes... so on and so forth. So for this example, I'll stick to the basic commonalities that are required to get started, and let you figure out the details of how you'd like to use the pattern with your app.
The best way to demonstrate this is to start off with a shiny new "Single View" iOS app. As an optional step, you might want to add a 'Hello world!' label to the view controller that comes with the default Xcode project just so you know that things are working as intended.
Next, you'll need to go to your project settings, and under the "General" tab, under "Deployment Info," you'll find a setting called "Main Interface". By default this is set to "Main," meaning that behind the scenes, your app will magically start up with the initial view controller of the Main.storyboard file. We want to delete this; we're going to handle setup of the window and the initial view controller ourselves in code.
The settings should look like the image below.

I seem to remember at some point having to remove some other things from the project settings and/or Info.plist file (other things that reference storyboards or Main.storyboard, basically), but this seems to be enough to get us going from my testing today.
Next, you'll want to create a new Swift file with the following content:
import UIKit
class AppCoordinator {
private var window: UIWindow
init(window: UIWindow) {
self.window = window
}
func start() {
window.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
window.makeKeyAndVisible()
}
}
Again, usually there's also a Coordinator protocol that this conforms to (I actually like to make an abstract class that this subclasses), but for now we'll keep things simple.
Let's head over to the SceneDelegate and change it to contain the following:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var appCoordinator: AppCoordinator?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
appCoordinator = AppCoordinator(window: window!)
appCoordinator?.start()
}
// ...other scene delegate methods
}
(Basically, we're just adding the AppCoordinator property and changing the body of the scene(_:willConnectTo:options:) method.)
Here's how I look at what's happening here:
UIWindowScene (a subclass of UIScene) is made, and its delegate property is set to this class.scene calls this delegate method.Main storyboard. Since we disabled that, we have to do it ourselves.UIWindowScene (it might be a good idea to fatalError or preconditionFailure here if it's not).UIWindow and make sure the SceneDelegate holds a reference to it.AppCoordinator, which the SceneDelegate will also hold onto so it doesn't disappear into the ARC ether.start our coordinator, which sets the root view controller of the window we made, making our view controller appear on the screen.You should now be able to run your app. Congratulations! You're doing the coordinator pattern!
Obviously this isn't super-useful yet; the end result currently looks the same as it did before we changed anything. However, we've now unlocked a whole lot of power in terms of containment, testability, UI flow, and more. If you're still confused or not yet convinced, I'd encourage you to read the articles linked below to see how and why to use coordinators more effectively.
Here's just a few articles about it:
(Note: This post uses Swift 5.3)
Lately I've been doing a lot of "code challenges" as part of the conclusion of the computer science portion of my time at Lambda School. Testing my solutions using the often clunky web "IDE" interfaces of these code-challenge websites quickly becomes a subtle source of pain when I'm doing a lot of these every day.
So naturally, I wrote some code to solve this.
Because these sites need to be able to easily test the code that users submit, the code we have to write is inherently easily testable, usually by virtue of being "pure functions"; that is, for any input, the function will return the same output without any side effects. Ergo, most code challenge solutions could be thought of as simply:
Input type (could be basically anything)Output type (again could be anything)input instance of type Inputexpected output of type Output(Input) -> Outputactual output of type Output, determined by the aforementioned codeWe could probably use a subclass of XCTestCase for this, but I'd prefer to make something simple that's exactly what I need rather than co-opt something else into working how I'd prefer.
With this in mind, we can construct a basic structure that can act as a wrapper for this data.
struct CodeChallengeTestCases<Input, Output> {
let expected: KeyValuePairs<Input, Output>
let solution: (Input) -> Output
}
The expected collection contains matched pairs of an input (the key) and an expected output (the value). KeyValuePairs is a relatively uncommon type; it's similar to a dictionary, and can be initialized with a "dictionary literal" (i.e., [0: "value0", 1: "value2"]), but with some key differences:
Key type (Input here) doesn't need to conform to Hashable (giving us more flexibility)key is not necessarily unique (though for us it will be)Dictionary(You can read more about in Apple's documentation.)
From here, there's all sorts of directions we could go. Obviously it would be nice if we could feed the struct our test data and solution, call a function, and get a "read-out" of sorts of how our solution performed. We could just use a tuple for this read-out data, but let's just make a lightweight struct to make things simpler for us.
extension CodeChallengeTestCases {
struct Failure {
let input: Input
let expectedOutput: Output
let actualOutput: Output
}
func evaluate() -> [Failure] {
expected.compactMap { ioPair -> Failure? in
let o = output(for: ioPair.key)
let e = ioPair.value
if o == e {
return nil
} else {
return Failure(input: ioPair.key, expectedOutput: e, actualOutput: o)
}
}
}
}
Here we define a basic Failure struct (nested within the CodeChallengeTestCases struct) which gives us information about any failure cases for our solution (what the input was, what was expected, and what our solution spat out).
However, you'll notice that if you tried to run this, you'll get an error: Binary operator '==' cannot be applied to two 'Output' operands. What's going on there?
It turns out, our Output type needs to conform to Equatable if we want to test equality. Most types in Swift's standard library already do, but our type has no way of knowing that by default. We could make Output be required to conform to Equatable, but in the heat of working on a code challenge, I don't want to have to worry about this.
Instead, we could have evaluate take in a closure that evaluates equality for us, and then use that in an overloaded version of this method for the cases where Output: Equatable.
extension CodeChallengeTestCases {
func evaluate(_ outputEqualsExpected: (Output, Output) -> Bool) -> [Failure] {
expected.compactMap { ioPair -> Failure? in
let o = output(for: ioPair.key)
let e = ioPair.value
if outputEqualsExpected(o, e) {
return nil
} else {
return Failure(input: ioPair.key, expectedOutput: e, actualOutput: o)
}
}
}
}
extension CodeChallengeTestCases where Output: Equatable {
func evaluate() -> [Failure] {
evaluate { $0 == $1 }
}
}
Now we're getting somewhere!
The outputEqualsExpected closure also ends up being useful with some code challenges even when the Output does conform to equatable. For example, some problems will expect you to return an array, but its order won't matter. If you just tested for basic equality, our test would think it failed. Instead, we can pass in a closure that checks if the expected output simply contains the same members as the actual output, without checking if they're each at the "correct" index.
We can still go a bit further with our automation here. We currently get a list of data where our solution failed, but it'd be nice if we could just print that to the console with one method call.
extension CodeChallengeTestCases {
func printFailures(_ outputEqualsExpected: (Output, Output) -> Bool) {
printFailures(evaluate(outputEqualsExpected))
}
func printFailures(_ failures: [Failure]) {
let titleText = title ?? "\(Input.self) -> \(Output.self)"
if failures.isEmpty {
print("All tests passed for '\(titleText)'!\n")
return
}
print("Tests failed for '\(titleText)':")
for f in failures {
printEvaluation(for: f.input,
expected: f.expectedOutput,
actual: f.actualOutput)
}
print("\n----------------\n")
}
private func printEvaluation(
for input: Input,
expected: Output,
actual: Output)
{
print("Input: \t\(input)\n"
+ "Expected: \t\(expected)\n"
+ "Actual output:\t\(actual)"
)
}
}
This code is fairly self-explanatory, checking if any failures exist and, if so, looping through them and printing the data contained therein.
I wrote two variants of this method; one takes in an array of Failures that would be spat out by evaluate(_:), while the other simply takes in the same closure that evaluate(_:) does, so we can accomplish everything in one go.
We could also have put this method directly on the Failure type:
extension CodeChallengeTestCases.Failure {
func print() {
Swift.print("Input: \t\(input)\n"
+ "Expected: \t\(expected)\n"
+ "Actual output:\t\(actual)"
)
}
}
...As well as an extension on Array!
extension Array {
func print<I, O>() where Element == CodeChallengeTestCases<I, O>.Failure {
let titleText = "\(I.self) -> \(O.self)"
if self.isEmpty {
Swift.print("All tests passed for '\(titleText)'!\n")
return
}
Swift.print("Tests failed for '\(titleText)':")
self.forEach { $0.print() }
Swift.print("\n----------------\n")
}
}
This allows us to write code either declaratively or imperatively depending on our preference. Here's some of the code in action for some very, very simple "challenges":
// Write a method that increments the input number by 1
func increment(_ num: Int) -> Int {
num + 1
}
let incrementTest = CodeChallengeTestCases(
expected: [
1: 2,
3: 4,
1231: 1232
],
solution: increment(_:)
)
incrementTest.printFailures()
// Write a method that adds an Int to a Double
func add(_ int: Int, to double: Double) -> Double {
double + Double(int)
}
CodeChallengeTestCases(
expected: [
(3, 4.0): 7.0,
(8, 9.5): 17.5
],
solution: add(_:to:)
).evaluate { expected, actual -> Bool in
// accounts for potential precision errors
abs(expected - actual) < .ulpOfOne
}.print()
There are of course more improvements we could make (giving a title to our test cases, measuring time and space used, comparing different solutions, etc), but at this point I've probably spent more time writing this "simple" helper than I have actually working on the code challenges, so let's get back to work on those!
p.s., Here's a gist with the CodeChellengeTestCases code for you to play with, use, and/or extend!
(Note: this post uses Swift 5.2)
I remember when I first learned about protocols, I often got annoyed that we couldn't make optional methods or properties without bridging to Objective-C, which came with its own set of limitations. However, it turns out there's a fun workaround for this!
Let's take the following example:
protocol DayViewControllerDelegate: AnyObject {
var habitStore: HabitStore { get }
func dayViewController(_ dayVC: DayViewController, didSelectHabit: Habit)
func dayViewController(_ dayVC: DayViewController, shouldDeleteHabit: Habit) -> Bool
}
This is a delegate protocol for a UIViewController subclass called DayViewController. The conforming type could be any class (not a struct or enum, since it's restricted to AnyObject). It could be another UIViewController subclass that presents an instance of DayViewController, or some other object that conforms to the protocol.
So what if I want to reuse this view controller in several different contexts, but in some of these places, the implementations for some methods should be exactly the same? I could make a whole new object to solely act as the delegate, but it'd be nice if I could just make some of these methods optional or provide some default behavior or something.
Well, it turns out you can! The secret, as often seems to be the case, is to use an extension of the protocol.
extension DayViewControllerDelegate {
var habitStore: HabitStore { .shared }
func dayViewController(_ dayVC: DayViewController, didSelectHabit: Habit) {
// do nothing
}
func dayViewController(_ dayVC: DayViewController, shouldDeleteHabit: Habit) -> Bool {
return true
}
}
By using this extension, whatever conforms to this protocol now doesn't need to implement those methods; they've effectively become "optional," in a sense. If I want to "override" any of these methods, I can easily do so:
extension PresentingViewController: DayViewControllerDelegate {
var habitStore: HabitStore { self.temporaryStorage }
func dayViewController(_ dayVC: DayViewController, didSelectHabit: Habit) {
showHabitDetails()
}
}
Sadly, this doesn't seem to work with { get set } protocol properties, though I can't imagine a scenario where I'd particularly want that anyways.
(Note: This post uses Swift 5.2 (and a bit of 5.3 at the end))
One of my favorite things about Swift is how it balances type safety with hidden power, such as in its implementation of generics. In certain scenarios where you want to use this power alongside an enforced type-safety, however (especially if you're coming from a more "loosey-goosey" language like Objective-C or Python), things can seem to get a little bit obnoxious.
If you're not familiar, generics are essentially placeholder types that, upon initialization, will be replaced with a concrete type. For example, here's part of a struct I made for SwiftUI:
struct ColorHeader<NameView: View>: View {
let nameView: NameView
let color: Color
init(nameView: NameView, color: Color) {
self.nameView = nameView
self.color = color
}
var body: some View { /*...*/ }
}
ColorHeader conforms to the View protocol, but it also uses in its body a generic NameView that also conforms to View. In the initializer, I can pass in any type that conforms to View, and henceforth, for that value, NameView will refer to that specific type. For example, I could pass in a Text value...
let header = ColorHeader(nameView: Text("Title"), color: .purple)
...And the type of header will now always be ColorHeader<Text>. The NameView placeholder has been replaced with Text.
Let's say that in my app, my ColorHeader is usually going to use Text for as its NameView. It'd be nice if I could just pass in a string to an initializer and save some typing.
let header = ColorHeader("Title", color: .purple)
You might think this would be as simple as creating a second "convenience" initializer.
init(_ name: String, color: Color) {
self.init(nameView: Text(name), color: color)
}
However, the compiler gives us the error Cannot convert value of type 'Text' to expected argument type 'NameView'. Luckily, it also provides us a fix that, at first glance, seems to work!
init(_ name: String, color: Color) {
self.init(nameView: Text(name) as! NameView, color: color)
}
That exclamation point makes me nervous, but sometimes it's necessary. Worse, though, if we go to use this new initializer, we get a new error...
let header = ColorHeader("Title", color: .purple) // "Generic parameter 'NameView' could not be inferred"
The fix it offers indeed allows it to work...
let header = ColorHeader<Text>("Title", color: .purple)
...but that kind of defeats the purpose of having the second, non-generic initializer if we have to explicitly provide the type every time. Let's try this:
init(_ name: String, color: Color) where NameView == Text {
self.init(nameView: Text(name), color: color)
}
Here, we're telling the compiler: "In this initializer, NameView is going to be Text." ...At least, that's what I thought I was doing. Unfortunately it doesn't compile (in Swift 5.2); we get the error 'where' clause cannot be attached to a non-generic declaration. We'll come back to this later...
So how do we solve this? Well, there's another way to look at what we were telling the compiler in the last example: "When NameView is Text, give me this initializer." This sounds like we need to extend ColorHeader.
extension ColorHeader where NameView == Text {
init(_ name: String, color: Color) {
self.init(nameView: Text(name), color: color)
}
}
What does that mean? Let's walk through it.
By extending ColorHeader conditionally (where NameView == Text), we're telling the compiler: "Hey, for this ColorHeader, if I have an instance where its generic NameView placeholder type is actually Text, give me the following extra stuff." We could put just about any computed properties1 or methods on this extension, including new initializers.
It may seem a little bit backwards to say "Give me this initializer when NameView is Text" when what we want is to make NameView equal to Text. Swift's language shephards must have agreed, because as of Xcode 12 beta/Swift 5.3, this...
struct ColorHeader<NameView: View>: View {
let nameView: NameView
let color: Color
init(nameView: NameView, color: Color) {
self.nameView = nameView
self.color = color
}
init(_ name: String, color: Color) where NameView == Text {
self.init(nameView: Text(name), color: color)
}
var body: some View { /*...*/ }
}
...now works! This makes much more sense to me, especially if we don't need that extension for anything other than this initializer.2 Once Xcode 12 and Swift 5.3 get official stable releases, I believe that you should be able to use it on older versions of iOS/macOS/etc. So fortunately, most of us should be able to enjoy the simpler solution to this before long!
Extensions in Swift cannot contain non-static stored properties; we can add a static let blah = "blah" to an extension of a class/struct/enum, but we could not add a let blep = "blep".
Given the error message we got previously, I'm guessing the 5.3 compiler is doing some new magic here to essentially synthesize the same extension we wrote before if where is used on a non-generic initializer. Or something like that? I'm just spitballing here.
(Note: This post uses Swift 5.2)
Conway's Game of Life is a classic programming concept that I was tasked with implementing as part of the computer science curriculum at Lambda School recently. This is the sort of task I really enjoy; not a ton of practical use, but a lot of fun!
The game takes places on a rectangular, integer-based grid and operates on the following rules:
These rules can be demonstrated simply with the following function:
func cell(_ cell: Cell, willLivegiven neighborCount: Int) -> Bool {
if cell.isAlive {
return neighborCount == 2 || neighborCount == 3
} else {
return neighborCount == 2
}
}
We could remove some redundancy and simplify this even more to:
func cell(_ cell: Cell, willLivegiven neighborCount: Int) -> Bool {
return neighborCount == 2 || (cell.isAlive && neighborCount == 3)
}
The question now is how to get neighbors, whether a cell has a position or a position contains a cell, and other implementation details.
I went through a few different iterations, but my data structures ended up looking like this when all was said and done:
struct Tilemap {
private var tiles = [Point: Tile]()
private(set) var width: Int = 1
private(set) var height: Int = 1
private(set) var population: Int = 0
var gridWraps = true
init(width: Int = 1, height: Int = 1) {
self.width = width
self.height = height
}
}
enum Tile: UInt8, CaseIterable {
case dead
case alive
init<I: BinaryInteger>(safe rawValue: I) {
if let tile = Tile(rawValue: UInt8(rawValue)) {
self = tile
} else {
self = Tile(safe: abs(Int(rawValue)) % Self.allCases.count)
}
}
}
typealias Point = Vector
struct Vector {
var x: Int = 0
var y: Int = 0
}
(I chose Tilemap because I had recently been going through Nick Lockwood's excellent Retro Rampage game engine tutorial series, which I took a lot of cues from. Check out objc.io's excellent video series on the first few steps of the tutorial as well.)
In Swift, structs often end up being more performant than classes in many circumstances, and it came in handy for a couple of specific reasons here, which we'll see later.
My first version of the Tilemap used an array of Tile objects and a width property, with height and population being computed based on those properties. Although height wasn't terribly expensive computationally (tiles.count / width), population needed to loop through the entire array every time it was calculated. Making it a stored property and incrementing it every time a tile is changed makes it so values are only recalculated as they need to be, and reducing the need for additional loops.
The dictionary mains makes resizing the map more efficient; for most purposes, an array would probably work just as well (if not better). Overall this is likely a little more complicated than it needs to be, but as I was working through various implementations, this one ended up being the most performant and useful. I also have a tendency at times to get caught up in details before I get something working, so I leaned a little further towards "just get something working." I could likely improve it further with some adjustments, but this gets the job done for now. With more time I'd love to do more detailed testing.
Tile could probably just have been a Bool, but when I started, I had planned to possibly make other types of tiles ("walls" that couldn't become alive, for example, or coloring tiles differently if they were about to become alive or had just died).
Points and Vectors are essentially the same thing for our purposes, in that they consist of an x and y coordinate, so it makes sense to use a single type to represent them both, making arithmetic with them easier (which helps with finding neighbors).
extension Vector {
static var zero: Vector { Vector(x: 0, y: 0) }
static var up: Vector { Vector(x: 0, y: -1) }
static var down: Vector { Vector(x: 0, y: 1) }
static var left: Vector { Vector(x: -1, y: 0) }
static var right: Vector { Vector(x: 1, y: 0) }
static var upLeft: Vector { Vector(x: -1, y: -1) }
static var upRight: Vector { Vector(x: 1, y: -1) }
static var downLeft: Vector { Vector(x: -1, y: 1) }
static var downRight: Vector { Vector(x: 1, y: 1) }
static var neighborVectors: Set<Vector> {
[.up, .upRight, .right, .downRight, .down, .downLeft, .left, .upLeft]
}
var neighbors: Set<Vector> {
Set(Self.neighborVectors.map { self + $0 })
}
}
With that, I can now call point.neighbors to get the set of all points adjacent to that point.
The requirements for this assignment required that I use a buffer map on which to map changes. So although my initial instinct was to write an update method that updates the map in place, the buffer method ended up having some benefits.
First, we need to generate the new changes to apply to the buffer:
extension Tilemap {
//...
func newGenerationChanges() -> Set<Point> {
self.compactMapToSet { point in
let tileIsAlive = self[point].isAlive
var count = 0
var tileWillLive: Bool = false
for neighbor in point.neighbors {
if self[neighbor].isAlive == true {
count += 1
} else { continue }
if count == 3 || (tileIsAlive && count == 2) {
tileWillLive = true
} else if count >= 4 || (tileIsAlive && count >= 3) {
tileWillLive = false
break
}
}
return tileIsAlive != tileWillLive ? point : nil
}
}
//...
}
(Check out the source code to get a look at some of the helper methods used here and elsewhere.)
Essentially, for every point on the map, we'll check whether its status should change, and if it does, we'll add that point to the Set of changes. When we're done, we'll have a subset of the entire map that contains only what will change rather than an entirely new map.
Notice that in the for-loop in newGenerationChanges, I didn't end up using the function we wrote earlier; using that, I needed to go through every neighbor every time, but doing it this way, we can skip the remaining neighbors if we hit our "overpopulated" threshold, saving some time.
After this, the changes are then applied to the buffer map by the GameEngine object (which we'll look at later).
mutating func apply(_ changes: Set<Point>) {
changes.forEach { point in
population += self[point].isDead ? 1 : -1
tiles[point].toggle()
}
}
For each point that needs to be changed:
population is incremented or decremented based on whether the tile is about to become alive or dead.The GameEngine takes a lot of credit for keeping things relatively efficient, using Grand Central Dispatch to run as many operations as possible on background threads, letting the main thread do the work of updating the UI.
class GameEngine: ObservableObject {
@Published var tilemap: Tilemap {
didSet { bufferMap = tilemap }
}
private var bufferMap: Tilemap
private let updateThread = DispatchQueue.global()
private var lastUpdateTime = CFAbsoluteTimeGetCurrent()
init(
tilemap: Tilemap = .init(width: Tilemap.defaultSize,
height: Tilemap.defaultSize)
) {
self.tilemap = tilemap
self.bufferMap = tilemap
self.gridWraps = tilemap.gridWraps
}
}
(Note: I'm leaving out some stored properties in the interest of keeping this post shorter.)
Because I used SwiftUI for the UI, the class conforms to ObservableObject and contains several @Published properties to properly keep the UI up-to-date. More on that later.
The real magic happens in the main method.
extension GameEngine {
func toggleRunning() {
isRunning ? stop() : start()
}
func start() {
guard !isRunning else { return }
isRunning = true
main()
}
private func main() {
updateThread.async { [weak self] in
while self?.isRunning == true {
guard let self = self else { return }
let currentTime = CFAbsoluteTimeGetCurrent()
let deltaTime = currentTime - self.lastUpdateTime
if deltaTime < self.frameFrequency {
continue
}
self.lastUpdateTime = currentTime
let computedFramerate = 1 / deltaTime
DispatchQueue.main.async {
self.actualFrameRate = computedFramerate
}
DispatchQueue.global().sync {
self.update()
}
}
}
}
private func update() {
let changes = self.bufferMap.newGenerationChanges()
DispatchQueue.main.sync {
self.tilemap.apply(changes)
self.generation += 1
}
}
func stop() {
isRunning = false
}
}
As soon as start is called, the engine begins running through the main run loop repeatedly until isRunning is switched to false (i.e., until either stop is called or the object is deallocated).1 Every run through the loop, we check whether enough time has passed for our requested framerate; if not, we wait (continue).
If we're ready, we calculate the current framerate in case it's slower than we'd like, so we can display that in the UI (this is why setting the actualFrameRate happens on the main queue; since it's a @Published property, it directly updates the UI as a result, so that all needs to be dealt with on the main thread). Then, we run the update method on a new queue. Calling it on this same background thread seemed to, strangely, cause some UI issues. I believe this has to do with the way the framerate/delta time is calculated, but again, I ran out of time to do much thorough testing.
The update method then generates the new changes on the new background thread, and applies them on the main thread (again, because this directly affects the UI2). Then, back in main, the loop continues for every generation until we stop.
Most of the UI was implemented using SwiftUI. I first attempted to implement the TilemapView using a nested HStack and VStack, but this ended up being incredibly inefficient, so I ended up using custom path-drawing. Again, I wanted to use SwiftUI, but their touch APIs don't yet allow one to get the position of a single touch, so using a UIKit UIView subclass (wrapped in a UIViewRepresentable) ended up being simpler.
Here's the draw method of that view, which is called whenever the view needs to be redrawn:
override func draw(_ rect: CGRect) {
let liveColor = UIColor { traits in
switch traits.userInterfaceStyle {
case .dark: return .white
default: return .black
}
}
let deadColor = UIColor { traits in
switch traits.userInterfaceStyle {
case .dark: return .black
default: return .white
}
}
let tileSize = getTileSize(tilemapSize: tilemapSize, rect: rect)
deadColor.set()
UIRectFill(rect)
if showGrid {
gridColor.setStroke()
}
for column in 0..<tilemap.width {
for row in 0..<tilemap.height {
let point = Point(x: column, y: row)
guard let tile = tilemap.tile(at: point) else { continue }
let origin = getTileOrigin(point: point, tileSize: tileSize)
let tileColor = tile.isAlive ? liveColor : deadColor
if showGrid {
tileColor.setFill()
} else {
tileColor.set()
}
let tileRect = CGRect(origin: origin, size: tileSize)
UIRectFill(tileRect)
UIRectFrame(tileRect)
}
}
}
As you might expect, it loops through every possible tile and colors it on the screen based on whether it is dead or alive (and on whether dark mode is on!). It may also draw a visual grid if that option is enabled (this would probably better be factored out into a separate view that could be drawn on top of this one).
The rest of the views are fairly straightforward SwiftUI; having not worked much with it, I quite enjoyed learning it more thoroughly and making an adaptive, relatively-pretty interface.
In the end, for a 25x25 grid (the minimum required by the assignment), the simulation easily runs at 20 generations per second, which is already too fast to really keep track of. It starts to lag a bit around 50x50, but it's still quite usable even at 100x100.
I'm certain that there are many, many ways I could improve upon this (I wanted to implement the HashLife algorithm, but that's going to take a bit more research than I have time for). I'm happy with how it turned out, though, as it gave me an excuse to practice working with these sorts of custom structures and algorithms and game-dev-like concepts in a very encapsulated way. I'm excited to take these lessons into future projects.
Because external references to the engine could theoretically be nil'd while the loop is still running, we capture [weak self] to hold a weak reference to the GameEngine within the main loop. This way, if that does happen, we won't get a reference cycle where the loop continues running and we don't have access to it to do anything about it. Instead, the reference count will go to zero, the object will be deallocated, and the loop will cease running.
I experimented with using ObservableObject's objectWillChange publisher directly, but because the UI will modify the tilemap directly at times, this didn't work.
(Note: This post uses Swift 5.2)
I recently completed time as a Team Lead at Lambda School, where I was responsible for reviewing and offering feedback on students' code. One of the biggest benefits I took away from this experience was the value of writing readable code. To that end, I thought I would share some quick tips that, in my opinion, can help improve the organization and readability of your code.
I'll be using Swift in this article, but the principles should apply to many modern programming languages.
Let's take the following code as a starting point.
struct Object: Codable {
var title: String
var date: Date
var content: String
}
class APIController {
let baseURL = URL(string: "https://example.com")!
func fetchObject(
withTitle title: String,
dateInterval: DateInterval?,
completion: @escaping (Result<Object, NetworkError>) -> Void
) {
var urlComponents = URLComponents(
url: baseURL,
resolvingAgainstBaseURL: true)!
urlComponents.queryItems = [URLQueryItem(name: "title", value: title)]
if let dates = dateInterval {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .short
let start = dateFormatter.string(from: dates.start)
let end = dateFormatter.string(from: dates.end)
urlComponents.queryItems?.append(contentsOf: [
URLQueryItem(name: "startdate", value: start),
URLQueryItem(name: "enddate", value: end)
])
}
guard let url = urlComponents.url else {
completion(.failure(.badURL))
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
// check response status code (2xx = good)
if let httpResponse = response as? HTTPURLResponse,
!(200...299).contains(httpResponse.statusCode)
{
completion(.failure(.badResponse(code: httpResponse.statusCode)))
return
}
guard let data = data else {
completion(.failure(.noData))
return
}
do {
let object = try JSONDecoder().decode(Object.self, from: data)
completion(.success(object))
} catch {
completion(.failure(.decodeError(error)))
}
}.resume()
}
}
enum NetworkError: Error {
case badURL
case noData
case decodeError(Error)
case badResponse(code: Int)
case deallocatedAPIController
case other(Error)
}
(Note that I usually don't like to force-unwrap things, but in cases like this (a bad baseURL), a crash would be programmer error and we can easily write a unit test to protect ourselves.)
We have an a basic model struct called Object, an APIController class that handles fetching an instance of the object from a URL, and an enum that declares the different kinds of errors we might run into along the way.
There's a lot happening here, and we haven't even looked at any view code that would call this. If we've seen something like this before, it might not be too difficult to parse. However, we can still improve readability & organization and make things more testable & reusable should we decide to expand our app's functionality.
One of the guiding principles I like to use is to keep each method as clear, straightforward, succinct, and readable as possible, abstracting out any complexity into smaller helper methods, so that each method does basically only one primary thing with as few side effects as possible.
Sometimes it can help to plan out the steps taken. In fetchObject, we want to:
URLRequest from the provided title string and optional DateInterval
QueryItems that can be added to the URLWith our plan in place, let's start by factoring out the entire first half of the method into its own private helper method.
private func fetchRequest(
forTitle title: String,
dateInterval: DateInterval?
) -> URLRequest? {
var urlComponents = URLComponents(
url: baseURL,
resolvingAgainstBaseURL: true)!
urlComponents.queryItems = [URLQueryItem(name: "title", value: title)]
if let dates = dateInterval {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .short
let start = dateFormatter.string(from: dates.start)
let end = dateFormatter.string(from: dates.end)
urlComponents.queryItems?.append(contentsOf: [
URLQueryItem(name: "startdate", value: start),
URLQueryItem(name: "enddate", value: end)
])
}
guard let url = urlComponents.url else { return nil }
return URLRequest(url: url)
}
Now the start of our fetch method is much more clear and simple.
guard
let request = fetchRequest(forTitle: title, dateInterval: dateInterval)
else {
completion(.failure(.badURL))
return
}
Most of this new helper method is taken up by parsing the optional DateInterval, which seems counter-intuitive. This is another good candidate for some refactoring, including making that DateFormatter into a property on the class instance; after all, if we're going to use it for several calls, it doesn't make sense to make the computer recreate it every time and clutter up our code.
let fetchDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .short
return formatter
}()
private func fetchRequest(
forTitle title: String,
dateInterval: DateInterval?
) -> URLRequest? {
var urlComponents = URLComponents(
url: baseURL,
resolvingAgainstBaseURL: true)!
urlComponents.queryItems = [
URLQueryItem(name: "title", value: title),
]
addDateIntervalQueryItems(to: &urlComponents.queryItems, for: dateInterval)
guard let url = urlComponents.url else { return nil }
return URLRequest(url: url)
}
private func addDateIntervalQueryItems(
to queryItems: inout [URLQueryItem]?,
for dateInterval: DateInterval?
) {
guard let dates = dateInterval else { return }
let start = fetchDateFormatter.string(from: dates.start)
let end = fetchDateFormatter.string(from: dates.end)
queryItems.append(contentsOf: [
URLQueryItem(name: "startdate", value: start),
URLQueryItem(name: "enddate", value: end)
])
}
Notice we're using an inout array of query items. This allows us to pass in our query items by reference, which simplifies the fetchRequest method syntax a bit from something like this:
if let dateQueryItems = dateIntervalQueryItems(for: dateInterval) {
urlComponents.queryItems?.append(contentsOf: dateQueryItems)
}
...to this:
addDateIntervalQueryItems(to: &urlComponents.queryItems, for: dateInterval)
However, taking some inspiration from John Sundell's excellent article about utility functions, we can simplify a bit more by writing an extension on Array:
extension Array {
func transforming(_ transformation: (inout Self) -> Void) -> Self {
var newArray = self
transformation(&newArray)
return newArray
}
func appending(_ elements: [Element]) -> Self {
transforming { $0.append(contentsOf: elements) }
}
}
...Negating the need for the (relative) complexity of inout at the (relatively) higher level of our APIController (and giving us a handy API that we could reuse in other scenarios!).
private func fetchRequest(
forTitle title: String,
dateInterval: DateInterval?
) -> URLRequest? {
var urlComponents = URLComponents(
url: baseURL,
resolvingAgainstBaseURL: true)!
urlComponents.queryItems = [URLQueryItem(name: "title", value: title)]
.appending(dateIntervalQueryItems(for: dateInterval))
guard let url = urlComponents.url else { return nil }
return URLRequest(url: url)
}
private func dateIntervalQueryItems(
for dateInterval: DateInterval?
) -> [URLQueryItem] {
guard let dates = dateInterval else { return [] }
let start = fetchDateFormatter.string(from: dates.start)
let end = fetchDateFormatter.string(from: dates.end)
return [
URLQueryItem(name: "startdate", value: start),
URLQueryItem(name: "enddate", value: end)
]
}
This one is a bit tricky, as we'll see. My first instinct is to factor out the dataTask's completion handler entirely.
func fetchObject(
withTitle title: String,
dateInterval: DateInterval? = nil,
completion: @escaping (Result<Object, NetworkError>) -> Void
) {
guard
let request = fetchRequest(forTitle: title, dateInterval: dateInterval)
else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(
with: request,
completionHandler: handleDataResponse(data:response:error:)
).resume()
}
private func handleDataResponse(
data: Data?,
response: URLResponse?,
error: Error?
) {
if let error = error {
completion(.failure(.other(error)))
return
}
if let httpResponse = response as? HTTPURLResponse,
!(200...299).contains(httpResponse.statusCode)
{
completion(.failure(.badResponse(code: httpResponse.statusCode)))
return
}
guard let data = data else {
completion(.failure(.noData))
return
}
do {
let object = try JSONDecoder().decode(Object.self, from: data)
completion(.success(object))
} catch {
completion(.failure(.decodeError(error)))
}
}
There are few things I love more about Swift than passing a method into another method as a closure. However, if you try this, you'll immediately notice our problem; the new helper method needs access to our completion closure!
For now, let's keep things simple and within a reasonable scope for the sake of keeping this post a reasonable length, if nothing else. Instead of using the completion handler within the response-handling method, we can have it simply return a Result type, which can then be handled within our original public-facing fetch method.
We can also make the method generic, allowing it to handle any entity that conforms to Decodable, meaning we could reuse it verbatim in this or in other projects.
private func decodeModel<Model: Decodable>(
_ model: Model.Type,
fromData data: Data?,
response: URLResponse?,
error: Error?
) -> Result<Model, NetworkError> {
if let error = error {
return .failure(.other(error))
}
if let httpResponse = response as? HTTPURLResponse,
!(200...299).contains(httpResponse.statusCode)
{
return .failure(.badResponse(code: httpResponse.statusCode))
}
guard let data = data else {
return .failure(.noData)
}
do {
let object = try JSONDecoder().decode(Model.self, from: data)
return .success(object)
} catch {
return .failure(.decodeError(error))
}
}
This gives us our final fetch method:
func fetchObject(
withTitle title: String,
dateInterval: DateInterval? = nil,
completion: @escaping (Result<Object, NetworkError>) -> Void
) {
guard
let request = fetchRequest(forTitle: title, dateInterval: dateInterval)
else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: request)
{ [weak self] data, response, error in
guard let self = self else {
completion(.failure(.deallocatedAPIController))
return
}
completion(self.decodeModel(
Object.self,
fromData: data,
response: response,
error: error))
}.resume()
}
Most notably, we use [weak self] because we need to access self.objectResult, and without capturing self weakly, the closure would hold a strong reference to self, which, in some rare circumstances, could lead to a memory leak.
When all is said and done, the top-level method fetchObject has been reduced in size by nearly 70%. This isn't necesarily all that meaningful, but by using helper methods with descriptive titles (fetchObject, which calls fetchRequest(forTitle:) (which in turn calls dateIntervalQueryItems) and decodeModel(_:fromData:)), it's much more simple for humans to grok.
We could of course go further with this in all sorts of ways. We could abstract away the use of URLSession and/or with a generic networking class that could handle all sorts of requests and result types, or we could even switch to using Combine's new networking publishers.
The most important takeaway, though, is that although the computer can handle the first version of our method just fine, taking a couple of minutes to factor things out a bit can help you (and any collaborators) in a multitude of ways in the long run, increasing readability, testability, and reusability.
(Note: This post uses Swift 5.1)
Most tutorials I've seen out there that have to do with local persistence (i.e., saving your app's data on the device) end up with the framework's tendrils wriggling all throughout the app's codebase. This isn't an issue in a tutorial app, or even in smaller apps, but in a larger app, it can become a problem especially if you want/need to switch to a different framework. For example, Core Data has NSFetchedResultsController that's basically designed to be adopted by a UITableViewController, leading to an explicit dependency on Core Data. If you decide to switch your app's persistence to Realm and your table views are using the aforementioned FRC directly in your view controller code, you could have quite a bit of refactoring to do.
One solution, as you might have guessed from the title of this post, is to abstract away your persistence layer, so that the majority of your app has no knowledge whatsoever about what system is being used for persistence. The bad news is, this can be more difficult than you might expect...
Our goal here is to have a generic API that can accomplish what we need to accomplish for data persistence without being explicitly dependent on any particular persistence framework; we want to be able to switch out Core Data for Realm for basic SQLite for raw JSON for some custom system without (too much) trouble.
This is a perfect use case for protocols; using protocols, we can design a PersistentRepository interface, write a class that adopts this protocol (and uses our desired persistence framework to implement its methods), and pass in this "generic" object throughout the app wherever it's needed, with all of these entities being none-the-wiser for what sort of framework is being used behind the scenes.
protocol PersistentModel {}
protocol PersistentRepository {
func fetch<T: PersistentModel>(_ model: T.Type, options: FetchOptions?)
throws -> [T]
func save<T: PersistentModel>(_ entity: T) throws
func write<T: PersistentModel>(_ entity: T, changes: () throws -> ()) throws
func delete<T: PersistentModel>(_ entity: T) throws
func deleteAll<T: PersistentModel>(_ model: T.Type) throws
}
This is a pretty basic interface for CRUD operations and loading your saved objects. PersistentModel is a blank protocol that just lets us limit what can be passed in to the methods, letting us write types that adopt the protocol, signaling that they can be persisted using this system. (FetchOptions is a tiny custom struct I wrote that just encapsulates a couple of options that are commonly used when fetching data (NSPredicate, a string for the key by which to sort, and an "ascending" boolean).
There's a couple of problems with this though. Here's how I've implemented part of this protocol with Realm.
class RealmRepository: PersistentRepository {
var realm: Realm
init(realm: Realm? = nil) throws {
let realm = try realm ?? .main()
self.realm = realm
}
func fetch<T: PersistentModel>(
_ model: T.Type,
options: FetchOptions?
) throws -> [T] {
var results = realm.objects(T.self)
if let predicate = options?.predicate {
results = results.filter(predicate)
}
if let sorting = options?.sorting {
results = results.sorted(byKeyPath: sorting.key,
ascending: sorting.ascending)
}
return results.asArray
}
//...
Some quick notes:
Realm.main() is a static extension with my default configuration. I've written the method this way because Realm's initializer can throw an error, and throwing initializers can't be used as default parameters in a method.results.asArray is a custom Realm Results extension that converts it to an array. I'll get back to that later...The problem may not be immediately obvious unless you know how Realm works or try to compile this. Realm.objects(_:) expects a subclass of Object, Realm's equivalent of Core Data's NSManagedObject from which any persisted object must inherit. But there's no restriction in this method that T must inherit from anything; it just has to conform to PersistentModel. deleteAll(_:) ends up having the same problem.
"No worries," you might be thinking. "Just make Object conform to PersistentModel and we're golden." Not so fast! Our fetch method still expects any object that conforms to PersistentModel, so what we get might not be an Object subclass. The compiler requires that the method accept any PersistentModel.
"Then just downcast it as an Object." No can do, friend. As soon as we do that, Realm.objects(_:) thinks we want every ol' Object that's being persisted, rather than the specific type we asked for. We seem to be out of luck.
It took me several weeks to find the solution to this issue, with several diversions into type erasure and protocol associated types (which cannot be conformed to or inherited from, because they can be structs/enums). Ultimately, the solution required literally thinking outside the box. While working through a related issue, I remembered that Core Data's NSManagedObject fetches can be performed from a static method on the subclass itself.
This was fairly straightforward to accomplish using a couple of adjustments and extensions.
protocol PersistentContext {}
extension Realm: PersistentContext {}
protocol PersistentModel {
static func fetch(
in context: PersistentContext,
options: FetchOptions?)
throws -> [Self]
static func deleteAll(in context: PersistentContext) throws
}
Since we need a Realm from which to fetch our objects (and, as another example, Core Data uses an NSManagedObjectContext), we'll need to pass that in to the fetch request, hence our new PersistentContext protocol to which Realm is conforming.
We now need to write the code that will be used when our persisted objects inherit from Object and conform to PersistentModel.
extension PersistentModel where Self: Object {
static func fetch(
in context: PersistentContext,
options: FetchOptions?
) throws -> [Self] {
guard let realm = context as? Realm else {
throw CustomError.incompatiblePersistentContext
}
var results = realm.objects(Self.self)
if let predicate = options?.predicate {
results = results.filter(predicate)
}
if let sorting = options?.sorting {
results = results.sorted(byKeyPath: sorting.key,
ascending: sorting.ascending)
}
return results.asArray
}
}
Rather than extend Object directly and having that conform to PersistentModel, I like the idea of explicitly telling the compiler which types I want to conform to this protocol. With this code, if I tell an object to conform to this protocol, and it already happens to inherit from Realm's Object type, it will now get these static methods "for free." It begins by making sure the PersistentContext we passed in is actually a Realm, or else throws a custom error. It then uses Self, referring to the type of this conforming object (not downcasted, avoiding our previous issues) to perform the fetch from this Realm.
Unfortunately, if we wanted to use this version of the fetch method throughout the app, we'd have to manually pass in a Realm, ruining our persistence abstraction. However, our persistence manager's fetch method could "forward" the parameters that can passed in to call the model's static fetch method.
The RealmRepository's fetch now becomes very simple:
class RealmRepository: PersistentRepository {
var realm: Realm
//...
func fetch<T: PersistentModel>(
_ model: T.Type,
options: FetchOptions?
) throws -> [T] {
try T.fetch(in: realm, options: options)
}
//...
}
Now, because anything that conforms to PersistentModel must have a throwing, static method called fetch(in:options:), and we've successfully implemented it when it inherits from Realm's Object, we've now successfully abstracted away our persistence. Of course, we could accidentally pass in an object that conforms to PersistentModel but does not inherit from Object, and we won't get any build-time errors, but we will be thrown an error at runtime. As long as we make sure to write unit tests for all of our code, I think this is an acceptable trade-off.
There's (at least) one last problem with this, and it has to do with Realm's Results type. This is a custom collection that contains references to a set of fetched objects in the database, but these objects are not actually loaded into memory until they are directly accessed, making for a nice performance boost especially with large sets of data.
You might remember earlier I mentioned a custom extension on this type that transforms it into an array. Sadly, this totally ruins the entire benefit of the Results class, as it accesses every object in the collection and crams it into an array. To be fair, I haven't tested this, so it might not really matter in most cases, but on principle (and as an exercise in learning) I'd like to try to have the best of both worlds.
I can't just pass the Results type around, since that would mean the app has explicit awareness of the framework again. I can't return a generic Collection<T>, because protocols can't be genericized like that.
The solution is to use AnyCollection, a type-erased wrapper which wraps, well, any collection, and any call to the standard Collection methods and properties are "forwarded" to the wrapped collection. This way, whether it's wrapping a basic array, Realm Results, or literally any other Collection, the logic behind the scenes is unaffected, and hence we get to keep the performance gains of Realm's Results class.
To implement this, we only have a few more changes to make...
protocol PersistentRepository {
func fetch<T: PersistentModel>(_ model: T.Type, options: FetchOptions?)
throws -> AnyCollection<T>
//...
}
protocol PersistentModel {
static func fetch(
in context: PersistentContext,
options: FetchOptions?)
throws -> AnyCollection<Self>
//...
}
extension PersistentModel where Self: Object {
static func fetch(
in context: PersistentContext,
options: FetchOptions?
) throws -> AnyCollection<Self> {
//...(same as before)...
return AnyCollection(results)
}
//...
}
All we do is change our return types and wrap our results in an AnyCollection. Easy-peasy. Now when I want to write an object to be persisted in Realm, the only new thing I need to add is to make sure it conforms to PersistentModel, which will be enforced by the compiler.
class NewEntity: Object, PersistentModel {
//...insert entity properties here...
}
We've now got a set of interfaces for persisting our data on-device with any persistence framework, while keeping the implementation details relegated to just a few spots in our code, allowing us both the to use the power of these frameworks as well as the flexibility to switch out frameworks without rewriting our entire app. There are of course edge cases we have not yet handled, and some ways we can further improve this (our persisted objects are still explicitly dependent on the persistence framework...), but we've got the start of something that we can build upon. In the future I'll likely revisit this, and let you know what I find!
In the meantime, do you see anything that doesn't quite work, or an obvious piece of code that could be easily & quickly improved? Get in touch & let me know!
(Note: This post uses Swift 5.1.)
This weekend I was working on a framework for fetch operations, and was wanting an easy way to make access to a property atomic in order to prevent race conditions where multiple things try to access or change something at the same time, which could cause all sorts of issues.
After doing a bit of research, this ended up being a two-step process, but in the end it makes both creating and accessing this property easy and succinct.
To start with, let's write a struct that wraps any value type with a dispatch queue.
struct Atomic<Value> {
private lazy var queue: DispatchQueue = {
let appName = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String
return DispatchQueue(label: "\(appName).AtomicQueue.\(Value.self)")
}()
private var _value: Value
var value: Value {
mutating get { queue.sync { _value } }
set { queue.sync { _value = newValue } }
}
init(_ value: Value) {
self._value = value
}
}
The DispatchQueue essentially makes a "checkout line," where anything that tries to either get or set the value first has to wait in line behind anything else that's queued up to try to access it. I label it with the name of the app and its type for easier debugging purposes1.
The underlying _value is only accessible within this struct, so there's no danger of it being accessed by something outside without going through the queue (unless we were to re-write/add to it and mess up somewhere, but prefixing with the underscore makes that less likely).
It's a little strange that we need to mark the getter of value with mutating; that's because, as I understand it, adding something to the queue actually changes the queue, which changes the struct, and anything that does so must be marked mutating so the compiler knows whether certain actions can be taken when using let vs. var instances of this struct.
Now if we want to make something atomic, all we need to do is:
var x: Atomic<Int> = Atomic(1)
// later...
x.value = 2
print(x.value)
// on another thread somewhere else at the same time...
x.value = 3
print(x.value)
As you might imagine, though, writing .value every time we want to use it is probably going to get old really quickly.
This is where property wrappers save the day.
Essentially, a property wrapper is a new-ish Swift feature that allows us to build a wrapper object around another object and make access to the wrapped object quick & easy. This post explains things quite well.
Even using a small portion of their capabilities, this is a perfect use case for our Atomic struct, and quite fortunately, it only requires a very minor rewrite of our previous code.
@propertyWrapper
struct Atomic<Value> {
private lazy var queue: DispatchQueue = {
let appName = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String
return DispatchQueue(label: "\(appName).AtomicQueue.\(Value.self)")
}()
private var value: Value
var wrappedValue: Value {
mutating get { queue.sync { value } }
set { queue.sync { value = newValue } }
}
init(wrappedValue: Value) {
self.value = wrappedValue
}
}
The only things we've changed from before are:
@propertyWrapper keyword immediately before the declaration of our type_value to value, value to wrappedValue)That's it! Now you might be thinking, "Okay June, but now I have to type even more characters every time I access it; .wrappedValue has 8 more key-presses than .value!2 Why would you do that to me?!"
Fear not, my dear, lazy reader. Firstly, @propertyWrapper is sort of like a protocol that requires a non-private var .wrappedValue, so we're required to use that name exactly.
As for having to type more... well, check this out:
@Atomic var x: Int = 1
// later...
x = 2
print(x)
// on another thread somewhere else at the same time...
x = 3
print(x)
All we need to do now when we want to make something atomic is put the @Atomic keyword before it, and then anything else that accesses it doesn't even need to know that it's a wrapped value, and you never need to access .wrappedValue (in fact, I don't think you can at least without a few more steps). I don't know about you, but I found this to be awesome.
Now the only danger is avoiding the temptation to needlessly use it everywhere...
Donny Wals made a great post about why this will not work with collections in Swift. Worth a reading and worth keeping in mind.
Theoretically, at least; honestly, I've never needed to make use of this.
If you include the shift for the capital V.
Happy New Year! I realized recently that I haven't written in awhile and I never really wrote about Super Countdown Tracker, so I'm now taking care of both of those things.
From the App Store description:
Add the dates/times of important events in the near or distant future, and Super Countdown Tracker will tell you how much time is left.
- Super Countdown Tracker is super simple!
- Add and edit events
- Add tags to your events
- Sort & filter events by date and/or tag
- Add notes to your events
- Notifications inform you when your countdowns end
- Completed events are automatically archived
- Automatic, adaptive dark/light themes
Even in the two months since initially writing the app, I've learned a ton, so not everything in the app follows best practices or even kinda-okay practices. Still, it gets the job done pretty well as far as I'm concerned, and I'm very proud of what I was able to accomplish after only a few weeks of iOS Development.
At Lambda School, the first 16 weeks follow a pattern of:
In the iOS track, the first Build Week is (optionally) a solo endeavor. We were given a few different ideas of apps to build, and we chose our favorite. I'm a sucker for simple "tracker" style apps, and at various points in my life I've recalled wanting to have a handy spot where I could count down the days to various events. (If a habit or to-do tracker had been an option, I probably would have picked that.)
So it's really as simple as that; it was just the option that appealed to me most.
As for the title? Well, "Countdowns" wasn't available. Adding a "super" in front of things seemed silly and fun. So I did it.
I don't want to go through the entire app, so I'll highlight a couple of things I was excited about, that I think or interesting, or that I think I should have done better.
At the center of the app is the EventController class, which, as you might imagine, controls the handling of events throughout the app. Since at the time I didn't know any better, I implemented it as a singleton in a kinda weird way I learned from some C# tutorials I'd been watching for Unity.
private static var _shared: EventController?
/// Returns the current shared instance of EventController if already existing.
/// If not existing, creates instance.
static var shared: EventController {
if let sharedInstance = _shared {
return sharedInstance
} else {
_shared = EventController()
_shared?.loadEventsFromPersistenceStore()
return _shared!
}
}
Of course there are much simpler ways to do this, even if I did want to utilize the singleton pattern (which I now know can be somewhat problematic, but I'll avoid that topic for now):
static var shared = EventController()
private init() {
loadEventsFromPersistenceStore()
}
Not only is this a lot simpler, but by using a private initializer, we ensure that only the EventController class can access the initializer, thus ensuring that there will only ever be one instance of it.
An even better solution would probably be to use dependency injection and pass an EventController instance around the app as needed.
For sorting and filtering the list of countdowns, I wanted the user to have a set number of ways they could sort and/or filter. This is a perfect use case for enums.
enum SortStyle: String, CaseIterable {
case soonToLate = "End date ↓"
case lateToSoon = "End date ↑"
case creationDate = "Date created ↓"
case creationDateReversed = "Date created ↑"
case modifiedDate = "Date modified ↓"
case modifiedDateReversed = "Date modified ↑"
case numberOfTags = "Number of tags ↓"
case numberOfTagsReversed = "Number of tags ↑"
}
enum FilterStyle: String, CaseIterable {
case none = "(none)"
case noLaterThanDate = "Now → ..."
case noSoonerThanDate = "... → ∞"
case tag = "Tag..."
}
The strings here are displayed to the user on the pickers of the sort/filter screen. The CaseIterable protocol is adopted for counting and indexing the different styles for use in setting up the UIPickerViews.
It would probably have been "better" (in terms of modularity, separation of concerns, and simplicity of types) to separate the enums' logical cases from the UI text that the user sees. We could instead instead simply adopt Int as the enums' raw value type, which would also mean we don't necessarily have to use the CaseIterable protocol. I'll come back to that idea later...
The sorting and filtering of events happens in the EventController. First, sort:
/// Sort the lists of active & archived events by the given style.
func sort(_ events: [Event], by style: SortStyle) -> [Event] {
return events.sorted {
switch style {
case .soonToLate: return $0.dateTime < $1.dateTime
case .lateToSoon: return $0.dateTime > $1.dateTime
case .numberOfTags: return $0.tags.count < $1.tags.count
case .numberOfTagsReversed: return $0.tags.count > $1.tags.count
case .creationDate: return $0.creationDate < $1.creationDate
case .creationDateReversed: return $0.creationDate > $1.creationDate
case .modifiedDate: return $0.modifiedDate < $1.modifiedDate
case .modifiedDateReversed: return $0.modifiedDate > $1.modifiedDate
}
}
}
As you might be able to tell, this simply takes in a list of events and a sort style, switches on that enum, sorts it in one of a few different ways depending on that style, and returns the newly sorted array. I think I'm pretty happy with the simplicity of this, but since there's essentially twice as many cases as we actually need (every other one is just the opposite of the previous one), there are ways we could restructure this.
enum SortStyle {
// bool for ascending or descending
case endDate(Bool)
case creationDate(Bool)
case modifiedDate(Bool)
case numberOfTags(Bool)
}
Unfortunately doing this means refactoring a number of other spots of the app (since the enum can no longer have a raw value or easily conform to CaseIterable), but for this specific use case it works quite nicely.
To make it even more clear, we could also implement a second enum:
enum SortDirection {
case ascending, descending
}
...and use that as the argument of the SortStyle, so one doesn't have to look at the type's declaration to figure out what that Bool is supposed to be for.
Or, we could even make a struct that takes a SortStyle and SortDirection:
struct Sorter {
var sortStyle: SortStyle
var sortDirection: SortDirection
}
...and perhaps a helper function in our SortStyle enum:
enum SortStyle {
case endDate
case creationDate
case modifiedDate
case numberOfTags
func eventOrderShouldRemain(for lhs: Event, _ rhs: Event) -> Bool {
switch self {
case .endDate: return lhs.dateTime < rhs.dateTime
case .creationDate: return lhs.tags.count < rhs.tags.count
case .modifiedDate: return lhs.creationDate < rhs.creationDate
case .numberOfTags: return lhs.modifiedDate < rhs.modifiedDate
}
}
}
...And so by abstracting at the different elements of the sorting, we repeat ourselves less and simplify the sort method:
func sort(_ events: [Event], with sorter: Sorter) -> [Event] {
let isAscending = (sorter.sortDirection == .ascending)
return events.sorted {
let shouldRemain = sorter.sortStyle.eventOrderShouldRemain(for: $0, $1)
return isAscending ? shouldRemain : !shouldRemain
}
}
Again, though, this means refactoring other parts of the app, and the initial version really wasn't that bad to begin with. Lessons learned for the future perhaps?
The filter method is a tad more complicated:
/// Returns an array of events filtered by the provided filter settings
/// from the provided events array.
func filter(
_ events: [Event],
by style: FilterStyle,
with filterInfo: (date: Date?, tag: Tag?)?
) -> [Event] {
return events.filter {
switch style {
case .none:
return true
case .tag:
if let tag = filterInfo?.tag, tags.contains(tag) {
if tag == "" {
return $0.tags.isEmpty
} else {
return $0.tags.contains(tag)
}
} else {
return false
}
case .noLaterThanDate:
guard let date = filterInfo?.date else { return true }
return $0.dateTime < date
case .noSoonerThanDate:
guard let date = filterInfo?.date else { return true }
return $0.dateTime > date
}
}
}
This method is actually very similar in basic structure to the sort function; it takes in an array of Events, filters them using an array method and a switch, and spits the new array out.
Again, there are some things we can do to improve it:
filterInfo tuple is pretty ugly. One way to fix it would be to make it its own type (probably a struct). There's arguably an even better alternative, though...filterInfo as arguments of the enum!tags (that is, eventController.tags) .contains(tag); eventController.tags is a computed property that returns all tags used by all events. Therefore, if any events have that tag, then it'll be there. And we're already checking all these events. It's a redundant check, so we can get rid of it..tag case. In fact, it's a perfect spot for another ternary operator (which, despite how weird they look, I really quite love).enum FilterStyle {
case none
case endDate(Date, Bool) // isBefore
case tag(Tag)
}
//...
func filter(
_ events: [Event],
by style: FilterStyle
) -> [Event] {
return events.filter {
switch style {
case .none:
return true
case .tag(let tag):
return tag == "" ? $0.tags.isEmpty : $0.tags.contains(tag)
case .endDate(let referenceDate, let showingDatesBeforeReference):
return $0.dateTime < referenceDate && showingDatesBeforeReference
}
}
}
Much simpler! Again, we could also add a second enum in place of the .endDate bool so it's clearer to the caller what it's actually doing.
You might remember that previously, FilterStyle had a raw value that was used in the pickers. Well, we can't use both associated values and raw values, and like I said, UI text probably doesn't really belong with the logic anyways.
We could, like with the Sorter previously, make a separate struct that holds our possible reference date and possible tag.
enum FilterStyle: Hashable {
case none, endDateIsBeforeReference(Bool), includesTag
}
struct Filter: Hashable {
var style: FilterStyle
var referenceDate: Date?
var tag: Tag?
}
...And then put a dictionary like this with the UI code.
var filterText: [FilterStyle: String] = [
.none: "(none)",
.endDateIsBeforeReference(true): "Now → ...",
.endDateIsBeforeReference(false): "... → ∞",
.includesTag: "Tag..."
]
Although if we want it to stay in order, it might be better to use an array of info.
typealias FilterUIInfo = (style: FilterStyle, text: String)
let filterUIInfo: [FilterUIInfo] = [
(style: .none, text: "(none)"),
(style: .endDateIsBeforeReference(true), text: "Now → ..."),
(style: .endDateIsBeforeReference(false), text: "... → ∞"),
(style: .includesTag, text: "Tag...")
]
Using a tuple can be a little quick and dirty, but it could lead to annoying problems down the line, so a small struct might work a little better.
struct FilterUIInfo {
var style: FilterStyle
var text: String
}
var filterUIInfo: [FilterUIInfo] = [
FilterUIInfo(style: .none, text: "(none)"),
FilterUIInfo(style: .endDateIsBeforeReference(true), text: "Now → ..."),
FilterUIInfo(style: .endDateIsBeforeReference(false), text: "... → ∞"),
FilterUIInfo(style: .includesTag, text: "Tag...")
]
...but I think the best, simplest, and clearest way to go might just be to make a function that takes in a filter style and returns a string for the filter text.
func filterUIText(for style: FilterStyle) -> String {
switch style {
case .none:
return "(none)"
case .endDateIsBeforeReference(let isBeforeReference):
return isBeforeReference ? "Now → ..." : "... → ∞"
case .includesTag:
return "Tag..."
}
}
Let's take a look at the UI for sorting and filtering.
Here's the setup for the sort-style picker:
class SortPickerDelegate: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(
_ pickerView: UIPickerView,
numberOfRowsInComponent component: Int
) -> Int {
return EventController.SortStyle.allCases.count
}
func pickerView(
_ pickerView: UIPickerView,
titleForRow row: Int,
forComponent component: Int
) -> String? {
return EventController.SortStyle.allCases[row].rawValue
}
}
There are then two more similar classes for filtering and tags (if the user chooses to filter by tag).
Conforming to NSObject is required for reasons you'll discover if you try to not do that and implement these protocols. (From what I understand it's part of the long, bloated line of inheritance of UIKit that goes way back to macOS's ancestor, NeXTSTEP.)
As a brief aside: UIPickerViews are... weird. In fact I'm still a bit baffled by how Apple has chosen to separate various UIKit objects from their -Delegate and -DataSource protocols, and which methods belong where, and what kind of should adapt each (as using the UIViewController can quickly lead to Massive-View-Controller syndrome).
From my very (very) brief research, it seems like Combine and SwiftUI are both attempting to begin addressing this weirdness. Since we're still working with UIKit for now, though (which I hear is advisable for the time-being, as SwiftUI sounds like it's still got a ways to go before it's ready for total adoption), we've got to figure out a way to deal with the weirdness.
Here's part of how I implemented the FilterPickerDelegate:
class FilterPickerDelegate: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
let delegate: SortFilterViewController
init(delegate: SortFilterViewController) {
self.delegate = delegate
}
// ...some other methods...
func pickerView(
_ pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int
) {
let filterStyle = EventController.FilterStyle.allCases[row]
delegate.showHideFilterComponents(for: filterStyle)
}
}
A couple of issues here:
delegate should probably be a weak var to avoid memory leaks.FilterPickerDelegate.delegate. A delegate of a delegate. That's, uhh... funky.FilterPickerDelegate shouldn't care that the delegate is going to use the filterStyle to show and hide various components; it just wants to hand over the data, and then the delegate can do what it wants with it.There are a couple of ways to fix all of this. The most simple would just be to put all of this in the SortFilterViewController and avoid it altogether, but that means that view controller code gets pretty big and unwieldy, as I mentioned before.
We could also just do some minor adjustments for now:
protocol FilterPickerDelegate {
func filterPicker(
_ filterPicker: UIPickerView,
didSelectFilterStyle filterStyle: FilterStyle)
}
class FilterPickerDataSource: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
weak var delegate: FilterPickerDelegate?
init(delegate: SortFilterViewController) {
self.delegate = delegate
}
// ...
func pickerView(
_ pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int
) {
let filterStyle = FilterStyle.allCases[row]
delegate?.filterPicker(
pickerView,
didSelectFilterStyle: filterStyle)
}
}
Problem #1 was a fairly easy fix. Problem #2 is still there, but it's made a little better by renaming the class.
Problem #3 was the big one; here, we can follow Apple's example and create a protocol that follows their naming conventions. That protocol has a single method, into which the the pickerView is passed (further following Apple's example, in case later we want to do something with that specific picker), along with the new filter style.
Now the only thing that would remain is for the SortFilterViewController to adopt the FilterPickerDelegate protocol and implement that method.
extension SortFilterViewController: FilterPickerDelegate {
func filterPicker(
_ filterPicker: UIPickerView,
didSelectFilterStyle filterStyle: FilterStyle
) {
showHideFilterComponents(for: filterStyle)
}
}
In this case we don't even need the filterPicker argument at all, but in the future we might want to, in turn, modify that picker depending on some factor of the FilterPickerDelegate.
I'm sure there are other, better ways to fix this as well.
Okay, I'd say that's more than enough for now. Be sure to check out Super Countdown Tracker on the iOS App Store, free to download (no in-app purchases or anything)! You can also check out the complete source code on Github.
In September I released two covers as a single called Two Others' Songs, featuring electro-pop-ish arrangements of "Here Comes a Thought" from Steven Universe1 and Radiohead's "How to Disappear Completely"2 from Kid A. You can listen to it on all of your favorite music platforms...
Both songs have a certain meditative bent to them (the first moreso than the second), so I thought they went well together.
I first heard this song in September 2017, and it simultaneously destroyed me and partially encouraged me to throw a lot more energy into meditation and my mental health. It's still probably the best song about meditation that I know of.
It also encouraged me to finally watch Steven Universe, which has since become one of my favorite shows.
This cover sort of began writing itself in my head over the summer. It was so fun to arrange; I don't think I've ever had this much fun writing a drum part, and I got to use a lot of my favorite instruments (clarinets, electric piano, choir-y multi-tracked vocals, arp-y synths).
Somehow the idea to change the time signature from 4/4 to 6/8 landed in my brain and wouldn't release itself. This led to a lot of challenges in modifying the whole flow of the tune, especially in the "take a moment" sections. I opted to make most of them slower and more meditative, whereas the second iteration is more frantic and rushed. Both takes reflect different angles on the meditative experience nicely, I think.
The clarinets are another idea that wouldn't let go of me. There's a fun technique I stole from John Adams where the 3 clarinets trade off on patterns, making it sound like one continuous pattern that's swirling around the listener1.
After finishing, it feels weird to go back to the original tune; it feels different (and uhhh definitely better than my own arrangement), but at the very least I enjoyed the hell out of putting my own warped spin on it.
If "Here Comes a Thought" marked a period of late-20s angst, this Radiohead tune probably best stands in for some of my late-high school angst. I had a hard time deciding whether to put this first or second, but I opted to start off more cheerfully. Really it could go either way, but I think thematically this tune somehow feels less "mature," which is somewhat ironic seeing as "Here Comes a Thought" comes from an animated family-friendly TV show and "How to Disappear Completely" is a track from arguably one of the most significant albums of the past couple of decades.
I again switched up the time signature, this time in the other direction from 6/8 to 4/4. I had intended to do most of the instruments in this track using [VCV Rack][], but that ended up proving to be more cumbersome than I had hoped. I still ended up doing the hi-hats, some synth flourishes, some samples, and some glitchy stuff using VCV Rack.
Glitch 2 from Illformed got some heavy use, especially on the drums. My usual workflow for that plugin is generally to put it on the drums, record the output to a new track, and flip between different settings, then find the best bits of that to use as "drum fills" essentially. I might have borrowed this technique from Disasterpeace, who I believe did something similar on the Fez soundtrack?
For most of the synths, I used several instances of NI Massive (still on the old version). It's an extremely versatile synth, but I often find myself making the same dreamy, vibrato-y, retro-y sawtooth wave, or something derived from that sound.
The original track's bassline is pretty weird; it walks around unchanging while the harmony changes around it, somehow adapting to every chord it accompanies. I stuck with that for parts of the intro and first verse, but it seemed to be begging for something a little groovier with this particular drumbeat. I did end up repurposing the original line to become an accompanimental plucky synth (also Massive) in the second verse.
I couldn't resist throwing in more choir-y vocals. I can't get enough of them.
I had planned to do more tunes and make an EP, but essentially ran out of time. I still thought this put a nice bow on one period of my life, though.
Here's the original version of "Here Comes a Thought" in its episode, "Mindful Education."
Here's an especially transcendent performance featuring several Ondes Martenot players.
See 1:09. [VCV Rack]: https://www.vcvrack.com
This week I started orientation at Lambda School, where I'll be studying primarily iOS development (and macOS & related techologies). One of my assignments during orientation is to write about why I'm here. Although the cynical part of me rolls my eyes at the very typically "first-day-of-school" style of the assignment, I thought I'd take it at least relatively seriously and make my thoughts public in the case that it happened to be helpful to someone else down the line.
My life up to this point largely feels like a series of (mostly happy) accidents. I did pretty well through high school, but rarely tried particularly hard. I seemed to have a difficult time finding inspiration (a fact I now attribute to (until fairly recently, undiagnosed) ADHD). Music was the first thing that seemed to really capture my attention and make me want to put in wholehearted effort. It was a natural fit to study at college.
At first, I opted for the practical route, working towards a dual teaching endorsement in music and math to increase my likelihood of employment. Then I discovered music composition, which has been the core of my professional life since then. I first dropped the math in order to focus on composition more, and then, upon realizing that teaching kids and teenagers was not a great fit for my personality, I dropped the education track entirely. I ended up getting both Bachelors and Masters of Music Composition degrees with honors. That initial inspiration to make music my primary craft had stuck with me for a decade and a half.
Upon graduating, though, I realized that the demand for composers in the job market is... very low, to be frank. There are swarms of obscenely talented folks all writing and recording and performing incredible music, and the number of folks willing to pay for their services just cannot support all of them. Even some of the most talented musicians hold jobs often completely unrelated to music.
Rather than a kind of initial shell shock, this realization was a bit of a slow burn. I thought I had what it took to be one of those mythical Successful Full-Time Composers. But as I've discussed here and elsewhere previously, "making it" in a traditional sense wasn't actually something that I wanted. I wasn't interested in making the lifestyle changes that I now believe would have been necessary to make a full-time living of music (moving to a big city (or travelling frequently), going to as many events as often as possible, aggressively researching and writing the most commercially appealing music I could).
I seemed to have a path ahead of me to make an okay living teaching at the local university and community college, but closer contemplation revealed, once again, that a career in what is essentially public speaking was (still) not a good fit for me. (It also turns out that things are changing and that may not have ended up turning out very well anyways.)
So what would be a good fit? If I were to craft a custom career for myself, I thought, it would involve me working mostly alone on deep, engaging, detailed problems towards some larger, meaningful outcome. Music for games was one such career, but as discussed, this just wasn't in the cards.
There was a great alternative, though...
I've loved computers my whole life. As a kid, music and computers were my two passions. It just so happened that my parents didn't really understand computers (to this day they do not own a computer or smartphone (yes, really)), so they discouraged that path and encouraged my latent musical talent (I don't blame my parents for this, especially since most of my childhood interactions with computers were entirely unproductive).
Doing this career reflection, though, it soon became obvious that software development was (if you'll forgive the drama) my destiny. The idea excited me immensely. Demand for developers was high and would likely remain high for the foreseeable future (at least higher than composers for most folks). It's something I might even be able to do remotely. And although at first it felt like I was giving in to a "lower" passion, the more I thought about it, the more I realized that I was just as excited about this as I was about music.
Music is awesome and I still love it deeply, but I'm also excited to provide something tangibly useful for a large number of people. As a composer, it often felt like I was writing music just for myself, especially in the moments where I felt like just another voice in the crowd. I'm excited for the prospect of being part of a team that improves the lives even in a small way for hundreds, thousands, or even millions of people.
It certainly doesn't hurt that software development jobs tend to pay fairly well. This isn't really a priority in my life in general, but the prospect of not having to worry so much about money is a pretty intensely positive privilege, and I'd be lying if I said I wasn't looking forward to that. Once my career has begun, I hope to be able to give back to those in need, living within my needs and avoiding the extravagent lifestyles that seem to be common in the tech industry.
So all things considered, software development seemed like a very good option, obviously. The next step was research. Going back to college didn't seem to be a wise choice. Moving to a big city for school wasn't, either. Although taking the self-taught option was cheapest, I think having accountability and systemized assistance is extremely valuable. So an online education looked to be the best option. However, I also wanted to avoid going in to debt or spending a ton of money, especially if things didn't pan out...
Enter Lambda School. No cost upfront, pay a percentage of your income only when you get a tech job that makes $50,000/year. At first I thought there had to be a catch. Other similar schools did have one or more catches (only jobs in certain cities, you have to pay no matter what after x amount of time, etc). Although Reddit seemed extremely convinced that the school was a total scam based on a couple of unhappy students, based on my research it seemed pretty objectively legit.
It also isn't just limited to web development. Although web development is obviously in extremely high demand and is a vital part of the world, there's a lot about it that just doesn't interest me personally. iOS development seemed to fit me very well. I've been drinking the Apple juice for almost 10 years now. Although I wouldn't call myself a fan-boy (I did go a year or two with Android), I find them to be one of the more ethical software companies out there a lot of the time, and I tend to enjoy using their products. In addition, being able to get "closer" to the hardware is something that appeals to me; the layers of abstraction in web development rub me the wrong way, personally.
So I'm about to start iOS Development at Lambda School! I applied for a September start date, completed pre-course work (confirming that I do indeed quite enjoy coding, and am pretty decent at it), completed an interview, experienced some communication SNAFUs (Lambda School is growing extremely quickly, and at times they're having a hard time keeping up, especially with admissions (the inside seems to be more stable than the outside, judging from orientation))... and now, in a few days, I'm starting classes!
I realize this is a lot of words for what maybe should be a fairly simple question. Let me see if I can summarize why I'm doing this...
I also think it's important to point to all of the people that have brought me to where I am today; they're a huge part of why I'm here. That's one reason I added an acknowledgements section to my About page. Please go there and look at all of the folks who have helped me. I did not do this alone. This is not my accomplishment. Who I am now is the result of countless causes and conditions. It's so important for everyone to know this deeply about themselves and others.
Hopefully that gives you some kind of idea of what I'm doing and why. In the coming months, I hope to write more about my experiences studying iOS Development at Lambda School, so be sure to check back if that interests you!
This is a decision I've been wrestling with for awhile now. For the past few years at least I've been developing a "backup plan" to attend a coding school and transition into software development if I found my current path wasn't working out for me for one reason or another.
Well... it's not quite that the path explicitly wasn't working. I just wasn't happy with where I was or the level of power I felt I had over my own destiny. My plan until recently was to keep teaching, with more teaching coming up at Whatcom Community College. If I did well there, maybe, if I put my whole heart into that work, I could eventually apply for tenure and make that the focus of my professional life. Or, perhaps, I could just keep banging my head against the freelance-music-and-sound-design-for-games wall and hope that I get noticed in the sea of people that are basically just like me but with more fire to make that work and a more convenient (i.e. big city) location. I'm being a little cynical in my description, but only to point out that, if I was being honest with myself, I just wasn't going to be happy with this route. I've gotten to the point where I haven't really written that much music at all. I mess around at the piano and drums and sometimes even in a DAW, but it hasn't led much of anywhere in quite awhile.
On the other hand, when I thought of the possibilities of transitioning to software development, I got excited. In speaking with people close to me, I was surprised to discover that I really, really wanted to switch to software development, and it seemed like a solid practical option as well.
So that's what I'm doing!
As soon as applications for September are open, I will be applying to Lambda School for either iOS Development or Web Development. I haven't fully decided which option I'll go with yet, as they both have their appeals and drawbacks. And really the whole path itself is not without its challenges, of course. It won't all be 100% easier than my current route. But I'm more excited about it in terms of learning and skills, job prospects and advancement opportunities, potential income, and lots more.
It's possible I may retransition into music, sound, or games in some form, but for now I'm basically putting it on the backburner. I'm also hoping that, once music feels less like discouraging work and more like the fun activity it once was for me, I'll make more music again. Maybe not! But I hope so, largely because I have a lot of music equipment I'd like to use and would hate to get rid of...
It's likely this website will have to change in a big way again as well, since right now it's very music-focused. I haven't decided how that will play out, but my music will remain online indefinitely as far as I'm concerned.
Thanks to everyone who's supported me and advised me in big and small ways. I'm forever grateful. I hope that no one thinks their efforts went to waste. Even if I ended up abandoning one path, the experiences I've had on it are invaluable and will stay with me and inform the rest of my life in a positive way.
Dr. Peter Attia on meditation:
...there are just going to be some people who guide in a way that you’re willing to be guided, and you shouldn’t be put off if someone’s listening to this thinking, “You know, every time I try meditation it doesn’t work for me,” or something like that. I don’t want to name the apps I went through, but there were many apps that I went through that just didn’t resonate for me. Just the way they talked about this didn’t make sense to me. Then, when you find the ones that do, and there are several that do for me, including Sam [Harris]’s Waking Up [app]... it really resonates with me, and there are others that do so the same. Jeff Warren is also one of the guides on Dan Harris’s 10% Happier [app]... I just love the way he explains stuff. So, I would say to anybody who’s listening to this, who’s feeling sort of bearish on meditation, try a different app, try a different guide, try a different book, try another way. Keep going until you find someone who can walk you through how to do this in a way that resonates.
I can also vouch for both Sam Harris's Waking Up as well as Jeff Warren's style of teaching meditation on the 10% Happier app, which, despite the kitschy name, has a lot of really high quality stuff from some real master teachers.
Considering that all living beings are subject to pain and suffering, it is morally incumbent upon us to endeavor to refrain from causing any avoidable and unnecessary pain and suffering to any living being. Furthermore, we should undertake, through positive action wherever possible, to prevent the avoidable and unnecessary pain and suffering of living beings.
— Culadasa, in a retreat handout from 2012
I've decided to start posting really tiny things like this on here a la micro.blog. Expect more of this in the future. I may at some point separate the micro-posts out into their own page.
A couple of years ago, my friend Silas Stewart recorded an EP of classical music featuring percussion. He wrote one of the pieces, I wrote two, and he performed on everything with a slew of friends. One of the engineers for that session recently asked if he could sample one of my pieces, Evocations for alto saxophone and percussion, for a track by his hip-hop group Lamarr Family Values. I'm pretty stoked about how it turned out! Check it out.
I want to use this space to give a bit of an update on where my life and career are at, for anyone that happens across this for any reason.
It's not exactly a well-kept secret that the games industry is a crowded place. The audio and music side of things is especially crowded. Survivorship bias tends to prevent folks from talking about this very much in my experience. A lot of blogs and books and courses and even events out there have a financial interest in convincing people that all it takes to "break in" is the right knowledge and the right efforts. But even with all the right knowledge, there's a hell of lot of variables at play that affect one's "success" in the games industry, and not all of them are within one's control. Beyond that, the factors that are within one's control can demand significant sacrifices.
I've slowly realized if I want to be traditionally "successful" in the games industry at this point in my life, I'd have to become someone that I didn't want to be. I'd need to either move to a big city or regularly make the three-hour round-trip to a nearby one. I'd need to spend weekends away from my loved ones working on music and sound at game jams. I'd need to force my voice into conversations where I didn't feel I belonged. I'd need to convince people that my music and skills were more desirable than those of my peers whom I deeply respect. Significantly less time would need to be spent doing things I loved, like waking up early, meditating, exercising, reading, or just hanging out with my wife and cat.
After struggling with the dichotomy of who I was and who I would need to be to become what I thought I wanted to be, I just decided that… I didn't want to be that person. I wasn't enjoying how I was spending my time. It started to become cliché to find myself in at a games event full of project-thirsty composers and projects that already had a composer attached to them. I'd be one of several musicians who wanted to convince you that they were the perfect candidate to write music for your game. Every time I found myself in one of these scenarios, my situation felt more futile. It never felt right. My self-worth was attached to my career growth, which refused to budge. It was totally reliant on other people, completely outside my control. Even writing music was beginning to feel less like the joyful privilege it began as and more like a chore, like another thing I needed to do to get to where I thought I wanted to be.
If this sounds pessimistic… well, yes, I was very pessimistic in the midst of all of this. I couldn't see any other way around it. I realized I was at my happiest when I was satisfied with where I was rather than trying to become something else. I was never satisfied searching for success in the games industry.
So I stopped. And I've surprised even myself with how much happier I've been since then.
For the past couple of years I've been making most of my living from teaching music theory (and a basic, general university, online intro to video game music class I developed at WWU), and although the first year had some growing pains, I've really come to love it. It's fairly steady, my skills are improving, feedback improving, responsibilities increasing.
For a time I was considering a career pivot by attending a coding bootcamp or something similar and trying to find remote work as a computer programmer. I've taught myself intro-tutorial-level skillset in a number of different languages, frameworks, and engines, from Javascript and Python to Love2D and Unity. At this point because teaching has been going well, I'm happy to leave this as more or less a useful hobby that may or may not become something more down the line.
I'm meditating a lot. This has become a very significant part of my life on many levels. I try (with varying degrees of success) not to talk about it much because I get the feeling most people just find it obnoxious. So if you want to know more, I'd be more than happy to talk about it however much you're comfortable with. And possibly more, depending on the day.
And of course I'm still writing music… and even enjoying it most of the time. I did about half a month's worth of making a little piece of music every day in November. More than anything in recent memory, that reminded me of the joy of making music. I stopped once it started to feel like a chore. I released a new album about a month ago. I wrote music for a feature horror film over the summer; it will probably be playing at festivals in the next year or so. I've also been transcribing & arranging 60s and 70s pop songs for a cover band in Minnesota, which has been surprisingly fun and rewarding. I see no reason to think I'll stop writing music at this point.
I'm also still co-organizing the semi-annual Audio Bash event, in which the Seattle and Vancouver game audio communities meet in Bellingham. I still help out with the Full Indie Summit in Vancouver every year. I'm still have a significant interest in games, game music, and game audio. I'll still continue to develop my skills in that area at a comfortable pace. Perhaps this makes me unprofessional, a hobbyist at best.
I don't care.
I did at one point. That's a big part of why I spent several years constantly ridden with anxiety. If someone likes me and my music and wants to hire me to make music for their thing, awesome. I'm down. Once the project is there, I'll be in mega-professional mode. I'm just done going out of my way to try to impress people.
I also know that, despite my lack of success as I once envisioned it, my current situation is still one of immense privilege. I'm still a white, middle-classed man living in one of the richest countries in the world, working in the arts. I still will try to do what I can to use this privilege to make the world better and amplify the voices of the disenfranchised.
Rather than ramble on any further, I'll just end with this quote by Jon Kabat-Zinn:
The only time you ever have in which to learn anything or see anything or feel anything, or express any feeling or emotion, or respond to an event, or grow, or heal, is this moment, because this is the only moment any of us ever gets. You're here now; you're only alive in this moment.
This is a complicated question to answer, but I think it’s worth answering for those that care. I worry that it will come across as preachy, but my point is not to convince you to meditate and become a Buddhist; I just want to explain why in the hell I would bother to spend so many hours sitting on my ass doing nothing. Also I enjoy talking about it and don't get many opportunities to do so, so here's an explosion of words.
For 2500 years, meditators in the tradition of the Dharma (ie, Buddhism) have reported, when following the instructions of skilled and experienced teachers, having experienced a predictable accumulation of positive traits and irreversible changes in worldview.
What are these changes? Well, that’s a little bit more complicated, and I haven’t experienced all of them myself, so I’ll have to go at least partially on the reports of those who claim to have experienced these changes.
(It’s worth mentioning that at least some of these changes have been measured with brain scanning technology. Permanent physiological changes of some kind have taken place in the minds of some very experienced meditators. That much, at the very least, is as close to a verifiable fact as we can get.)
According to the Dharma, the world as we know it contains three interconnected characteristics:
Are you your body? Are you your mind? Are they even separable? Are you an accumulation of your experiences? Are you your consciousness? Where is your consciousness? What happens to it when you die? (We’ll ignore popular Buddhist ideas of reincarnation for now; that’s not something I (and many other meditators) subscribe to.) Where is you? Which part of your brain are you? Which mood are you? Is the ‘you’ that you are around your parents the real ‘you’? Or maybe the ‘you’ with your best friend? Or with your significant other? Or when you’re by yourself? How can any of those be more or less ‘you’?Perhaps more simply… maybe there is no solid, separate ‘you.’ Maybe you’re just a bundle of processes of a mind and body accumulating memories and activities and habits and actions, making impressions on other bundles of processes.
Maybe this is a scary thought, but indeed, within the realm of neuroscience, we haven’t discovered any soul or “seat of consciousness” or “true self.” The lack of any concrete, unchanging, separate, or distinctive ‘you’ makes sense from a scientific perspective and groks with the experiences of those who have done a significant amount of meditating. So you’re still a real thing, a living organism with a body, a mind, and consciousness… you’re just not more than that, even though it often feels like it.
Through meditation, those three characteristics of reality can be seen more clearly. But what do we do about all that? The ‘me’ I feel like I am is an illusion, everything is changing, and nothing makes me ultimately happy.Well, 2500 years ago a guy named Siddhartha Gautama saw that and sought some way to deal with it. He did a lot of meditating that made him relaxed and blissful and essentially ‘high’ (funny how history repeats itself; this seems to be the goal of a significant swath of modern McMindfulness), but he saw that it wasn’t really going anywhere. So he tried some different things, and eventually, through experimentation with his own mind and first-person experience, changed himself.
After integrating what he had experienced, what had changed, and how it had happened, he figured out how to conceptualize it and teach it to others. This is the Dharma, or what is often referred to as Buddhism (though the religion of Buddhism, as with all religions, comes with a lot of baggage of its own).Over 2500 years people have added their own ideas about what Siddhartha (or the Buddha) taught, adapting it to their own cultures. Sometimes the original teachings would be warped and misconstrued, and sometimes folks would figure out ways to realize what he did in different ways. As with everything, it’s a mixed bag, and everyone basically has to figure out for themselves what to take and what to leave (or just accept it all on blind faith if that’s your thing, although the Buddha did encourage people to not take his word for it, but to see for themselves the truth of what he taught).
So: suffering, which is caused by craving & clinging, can, according to the Buddha, be uprooted by following what has been called the Eightfold Path, traditionally translated as follows:
This list can be split into 3 sections: 1 and 2 make up the wisdom (and later, Insight) portion, 3–5 make up virtue, and 6–8 make up meditation. (Finally, we're getting to the point of this whole thing.)
The order is important, but it’s also very cyclical; once you get to the end, you go back to the beginning. It’s a constant process.
That's why I meditate. That's what I'm working towards. I believe it's possible. I've seen glimpses. There's even some scientific evidence at this point. This isn't a religious faith about some heavenly realm I'll supposedly go to when I die. It's about this one life I have and making it the best it can be for me and for the rest of the world. (Sidenote: many Buddhists do believe in something like heaven. I politely (well, I try to be polite...) but firmly disagree with them.)
You may notice that my explanation of the eightfold path is basically the first time I brought kindness into this. Although it hasn’t been explicitly mentioned before, the quality of metta (usually translated as “lovingkindness”) (along with associated qualities of compassion, sympathetic joy, and equanimity) is a vital ingredient throughout the Buddha’s teachings even though it isn’t very explicit in this more general overview. Indeed, when the Buddha first realized all this (when he “awoke” or, to use another poorly applied Western term, became “enlightened”), he first didn’t think he should even try to share it, thinking it was too subtle and difficult. But it was his compassion for those suffering in the world that led him to decide otherwise.
Furthermore, it might be tempting to call this whole ordeal selfish; after all, maybe this will end our own suffering, but what about everyone else’s? But the whole idea isn’t just for one person to suffer less; it’s for that person to see more clearly the causes of suffering and alleviate it for everyone. This may sound like an excuse to become some kind of Buddhist missionary, but that’s definitely not the intention.In all of our interactions with people in the world, we can use what we learn in meditation to be kinder, gentler, more understanding, and to work to create a better world with less suffering for everyone.Telling someone to “go meditate” is not useful if they don’t have a roof over their head, if they’re harassed for their gender or sexuality or race, if their mind is telling them that the best way to feel better is to die but they can’t afford healthcare. These are all very solvable problems through means that are not esoteric or “spiritual.” As we see how interconnected we are, it only makes sense to try to alleviate as much suffering as possible for every living through whatever means we can.
And that lovingkindness starts with oneself, with alleviating our own suffering, in showing ourselves compassion, in being celebrating our moments of joy, and approaching life with equanimity. Only then can we adequately do the same for others.
There’s a lot more to this, but I think this hits the best balance of completeness and readability to the best of my knowledge and skill.If you’re skeptical, I understand. It probably sounds nuts. I thought it was all nuts. But as I gradually learned more intellectually and experientially, I’ve started to come around to it all. I don’t blame you if you think I’m deluded or brainwashed. I would encourage you to try to be open-minded; keep the healthy skepticism, but maybe try not to be cynical!
If you’re intrigued, I’d encourage you to see if it checks out with your experience. There’s a lot of scholarship out there, both philosophical and scientific, that goes into these realms, and I've found reading about it to be helpful. More importantly, though, I’d encourage you to try meditating. There’s a ton of different ways to start these days; it’s easier than ever. Different methods will appeal to different folks. If you’d like recommendations of books, recordings, apps, etc, please ask.Most importantly, don’t feel guilty or angry or bitter if you just think it’s stupid, overthinking things, woo-woo baloney, or anything along those lines. I don’t want to proselytize. I’m following what I think is a way to find greater peace and happiness for myself and those around me. If sharing this with you helps you do the same, I’d be delighted, but I really just want folks to have a basic understanding of what the hell kind of benefit I could get by spending so much time sitting on my ass doing nothing. I hope I’ve accomplished that to some degree, and if I’ve only furthered your bafflement, please accept my sincere apology.
Follow your own path towards your own happiness and the happiness of the rest of the world. That’s the most that I would ask.
The other day, I was giving a lesson on writing music to picture, and I came across an analogy that I thought illustrated well the way that I approach scoring games & films.
When scoring a game or film, the picture/gameplay/dialogue/sound effects are essentially your "melody." Most of the time, those aspects, rather than the music, will be in the foreground of the audience's attention. Your job is to write musical accompaniment for that "melody," almost like a concerto or a sonata for soloist and piano. Of course, a soloist will take breaks to breathe for a couple of moments throughout any such work, and at this point the orchestra or piano can come to the front and really sing out for a bit, responding to and/or foreshadowing the action. Most of the time however, it needs to play a support role.
There's still a virtually infinite number of ways this process can be approached, of course. You can make your score more of a dialogue with what's happening on-screen. Perhaps it's enhancing what's present, or providing contrast. Perhaps for some time the role is reversed, and the on-screen action has to become more repetitive or contemplative, at which point you've now been given accompaniment and can write a "melody" to soar over the top of it.
But regardless, writing music to picture requires an awareness of how your music is functioning, of what your limitations are, of the pacing of a scene or of the gameplay. Music will affect this, either playing into it and enhancing it, or acting as a counterpoint to it, or both, or neither.
Awareness and intention, as in so many things, is crucial.
I've done a fair amount of work that I haven't been paid for. Some of this was work for friends that gave me great experience and that I'm grateful to have been a part of. Some, looking back, was blatantly exploitative and it's obvious now that those I was working for (not with) didn't value my time or expertise.
Now that I've got two degrees and a few years of paid experience under my belt, as a general rule, I'm not going to be doing free work anymore.
I spent almost seven years studying music full-time (and all the work and loans that go along with that). I've spent Carl Sagan-knows-how-much-money on computers, audio interfaces, keyboards, books, software, and thousands of hours of time learning to use all of that. At this point, I have enough projects and ideas and things I could be doing to move my career where I want it to be to fill my plate for the next year at least. The little free time I do have I'd rather be spending either relaxing with friends and loved ones or working on my own terms on whatever harebrained idea I come up with and call "music."
This isn't to say that if someone comes along with an awesome-looking game with no up-front budget that I'll tell them to screw themselves. I've had conversations recently about collaborations with friends that don't involve money at all. And I'm stoked about those, because they're with friends that I like spending time with in the first place. But my days of applying for rando online ads for unpaid work are done for me. That doesn't mean you won't get something out of them, but unless the stars align just right, I probably won't.
Music and sound are my area of expertise. I know more about them than the average person. It may be a crowded field where free labor is easy to find, but in this weird capitalist world, even The Sacred Invisible Hand of The Market has given me the stamp of approval of monetary value. My knowledge, skills, and experience are worth money. So pay me for my work, please, and do the same for others.
When someone you know gets a cool gig, there are a lot of emotions and thoughts that might come up and play through your mind. I could have gotten that gig, or on the flip-side, I'm never going to get a gig like that. Maybe something like They don't deserve that job any more than me, or The gig probably sucks anyways, or one of several other mental rabbit holes we can fall into.
It's obvious to tell looking at these thought patterns from a distance that they're not coming from a particularly wholesome place, and yet we sometimes experience them whether or not we want to; to deny their existence isn't exactly helpful, either. So how do we deal with them? Here are a few ideas that I've found helpful.
First, acknowledge these feelings when they come up. As I mentioned, denying feelings can, in my experience at least, tend to only push them down temporarily; they seem to have a habit of boomeranging back with even more force than when they initially appeared.
Instead of disregarding them, see them clearly, and perhaps even investigate them a little bit; why do I feel this way? Am I afraid I'm not ready? Am I insecure with where my skills are at? There may be practical ways to address some of the roots of these feelings, like taking online courses to fill gaps in your knowledge and skills.
It's also important not to condemn yourself for having that feeling; feeling jealous is a natural part of being a human (why do you think so many stories involve envy and jealousy?), and it's easy to imagine how it might have come in handy in the hunter-gatherer tribes of early humans. In modern society, though, it's not so beneficial, and so we have to find new ways of handling it. We can see them, acknowledge them, make note of them (I see you, jealousy.), address their root causes if need be, and move on.
Sometimes that can be enough, but more likely than not, those thoughts or similar ones will arise again. It would be very easy for, in a moment of idleness, these thoughts to begin playing out in the mind without being noticed, taking a hold of mood and intentions, gradually crafting a sense of bitterness and cynicism in the mind. But we can feed our minds an alternative narrative: one of sympathetic joy and abundance: we can feel happy at someone else's success, recognizing that there is more than enough happiness to go around as we realize that happiness is not a zero-sum game.
This can take a bit of effort and feel a little awkward at first, but think about it for a moment. Even from the most self-serving point of view, supporting friends will lead them to want to support us as well. If we foster a sense of possibility, abundance, and opportunity, and if we act from a mindset that takes this abundance to be true, we are more likely to find success in the future than if we are bitter towards our friends' success and act as if another's success is our own failure.
It's unfortunately true that, in a capitalist society, there is only so much money to go around, but we can still inspire each other to do greater work and find more success in each of our fields as a whole. There's just no use in treating it like a fight.
Of course, even after thinking through all this, those negative feelings might still be there; we still can't deny their existence. At this point I've found a lot of help in a certain style of meditation practice called metta, lovingkindness, or heartfulness meditation. It may sound a bit sappy, but it's a powerful tool. Traditionally, it basically involves mentally sending good vibes first to yourself (potentially easier said than done), then to a benefactor (e.g., a teacher you look upon fondly), then a good friend, then a neutral person (e.g., perhaps a bus driver you see often), then someone you have difficulties or even hostilities with, and finally to all living beings.
The specific mechanics of this can vary by teacher, tradition, etc (it may involve visualization of sunlight shining on the person making them happy, or the simple repetition of phrases like "May you be happy"), but I'd encourage you to give a few different versions of this a try and see if it improves your mindset (you can find some basic instructions here, or in a lot of other places if you search the web for "metta/lovingkindness meditation"). If it works even a little, keep it up; if you practice it for just a few minutes every day, you can slowly begin to replace any negative thought patterns of bitterness, cynicism, and ill will.
Habits of negative thoughts are a hard thing to overcome, because they're often deeply ingrained in us, having long dug their roots into our minds without our noticing. With attention and work, though, we can develop a more positive outlook on things, be more supportive friends, and be ready for when the right opportunity does come our way.
How do you deal with these sorts of feelings? Do you experience them at all (if you don't, I'm impressed and a bit jealous!)? Have any of these ideas been particularly helpful? Let me know in the comments!
Music is, in short, organized sound. The various methods used to organize sound produce predictable associations and connotations (the seeds of which are largely culturally drawn) in the mind of the listener. I believe that as a composer I must, at the very least, be mindful of these tendencies and attempt to predict the musical, cultural background of my intended audience(s). I can then utilize my knowledge of music theory, acoustics, appropriate/engaging pacing, and musical culture and legacy, combined with the aforementioned predictions, to play into and/or subvert the audience's expectations and to draw out emotional reactions.
Depending on context, music also has different secondary functions. In games, music can accentuate and adapt to the actions of the player, be semi-interactive, and sometimes assist in evoking a state of flow in the player. In film (and games), music can respond to what's onscreen and imply subtext otherwise unseen. In concert music, the music acts more on its own, but visuals still have a role in terms of the performers' physical behavior. In any music intended to be performed by humans (and even to a much lesser extent computers), I also need to consider the ability of the players to execute the musical instructions I am writing. A virtuosic performance of an especially difficult passage also tends to add its own excitement that is generally less present in "easier" and unperformed pieces of music.
Music is here to enrich our experiences and our lives. As a composer, I must always keep this and all of its contributing factors, functions, and effects in mind.
I recently resigned from my "day job" as Program Coordinator for the WWU Music Department. I'm now spending all my time trying to be a one-person music/sound-making business. This is scary as hell, and I don't know if it will be successful, but I have a lot of plans to at least make success a lot more likely in the long run, and I have a lot of folks that have been helping me out along the way. In some ways it's like I'm going back to school; training myself in a lot of details I never got a chance to learn while I was so busy in school and at work.
One thing I've been attempting to train myself in is the simple value of being a good person. Not just making oneself look good so people think I'm good, but actually living a good life, thinking about what I value and living those values, not compromising on the values I hold dearest, seeking justice in the world, standing up for what's right.
I have some deep convictions about what constitutes a good life, and yet also remain fairly open-minded about other people's paths toward a good life, and about how my own path might be improved. I like to think (or at least hope) that this sort of transparency, openness, vulnerability, and authenticity can be detected by others subconsciously at least. I don't know how much it will affect my future employability, and honestly I don't care too much... I am simply hoping that it matters, because it's not something I'm planning on throwing away anytime soon.
The word "networking" always brings to mind a certain sliminess that I really want no part in. "Making friends" is better, but can also easily be turned into something a little bit wicked; "friends" becomes shorthand for "people that can move me up the industry's social hierarchy." It's easy to get cynical about so many aspects of any industry where "who you know" is of such intrinsic value.
Making genuine connections with other people is what's ultimately most important to me. It's what I hope to do with my music; to foster those moments where one mind (or a group of minds) puts something out there into the world, and another mind picks it up and sees it exactly for what it is, with all the feelings and emotions and messiness that comes with it. Some sort of empathetical resonance. I'm hoping that by trying to be a good, authentic person, by being honest in what I put out there, maybe my music, words, and character will resonate with other good and authentic people, and maybe those connections will happen. Of course there's more to it than that; I need to be a skilled musician, I need to know how to effectively communicate, I need to know the technical aspects of digital audio. But I think authenticity will be of equal importance moving forward. This seems to be confirmed by the fact that I've met more kind, talented, intelligent, and, yes, authentic people in game audio and game development than I have in any other group of people.
Still, I say all this partially as wishful thinking; I'm sure there's an opportunist side of my subconscious mind that wants to connect to people to climb that social/industry/financial/whatever ladder. But if that's a side effect of trying to be a good person and have any kind of lasting positive impact, I think I'm okay with that. I'll need to continuously keep that part of me in check and acknowledge the possibility that that selfish side of me is there, waiting to take control of the ship. But as long as I'm mindfully trying to do well, to do better, to be better, I think it will all work out in one way or another.
After all, "good person" is not an end-state; "better person" is a continuously cultivated path.
The word "spiritual" has a really icky connotation among folks that consider themselves to be any matter of skeptical, agnostic, atheist, serious, secular, scientific, or even just modern. It conjures images of the those hippie stores that sell lots of Deepak Chopra books, New Age synth meditation soundtracks, and Chakra-improving tea; of upper middle class white people that say "Namaste" without a hint of self-awareness or irony; or of religious folk who wear said spirituality rather loudly on their sleeves.
But before modern times, before the 20th century cracked the whole concept of religion wide open (particularly in Europe), spirituality had a practical function as well. Spirituality was a practice that brought peace to people's lives in a way that their more "worldly" activities could not. We now know that many of the various religious systems of beliefs still widely held today around the world are deeply flawed and at odds with the scientific understanding of the universe. The general impression seems to be that science has supplanted religion. However, science and rationality alone have thus far largely failed to provide peace to a large portion of skeptics (or the more doubtful religious).
Spirituality does not require believing anything that cannot be proven. Indeed, renowned scientist, writer, noted skeptic, and public advocate for science has said: "Science is not only compatible with spirituality; it is a profound source of spirituality." When we understand the magnitude and mysteries of the vast universe and the lack of any evidence of a creator, with the right frame of mind, one can be filled with awe in a way similar to how a perceived divine miracle might.
And yet, science sometimes cannot yet be directly experienced in the same way that we actually physically experience our reality: the love for our family, friends, or partners; the awe felt when standing in front of the Grand Canyon or the Statue of Liberty; the chills one might feel at J.S. Bach's Crucifictus, Ludwig van Beethoven's 5th Symphony, Arvo Pärt's Cantus in Memoriam Benjamin Britten, Penderecki's Threnody for the Victims of Hiroshima, or Radiohead's "How to Disappear Completely" (...sorry, got carried away there); or, yes, the simple animal pleasures like eating food or having sex. Experiencing passionate works of art, complex & seemingly paradoxical webs of emotion, or intensely pleasurable activities is often far separated from what's happening "behind the scenes." Sure, as far as we can tell, it's just various patterns of neurons firing, and that knowledge is certainly valuable at times, but it's not the whole picture. We don't experience neurons firing. We experience love. Comfort. Anger. Horniness.
Finding any kind of meaning in all of this is quite possibly an entirely futile endeavor. Still, finding some sense of peace (or ease, or acceptance, or perhaps righteousness... or yes, even spirituality) seems to remain necessary in order to determine our best actions as we work our ways forward in this world. The past several months have been trying times in the USA (and elsewhere, I'm sure) for people that are not used to trying times. We don't have to find solace in religious dogmas, in unsustainable and untrue belief systems.
Perhaps the word "spiritual" is a bit doomed for now; it's still difficult to separate it from its woo-woo connotations. Perhaps it's high time the word be reclaimed. Or perhaps we can disregard the language and focus on what it points to.
Regardless, spirituality remains a very personal, even intimate endeavor. I don't blame anyone for getting a little bit cagey around the subject. I find it fascinating. You don't have to. You don't have to even pursue any kind of spiritual practice if it doesn't interest you, so long as you're trying to act well in the world. If it does interest you though, I encourage you to delve into whatever secular spiritual practices you can find.
Because I love book recommendations, here are a few books I've enjoyed on this topic:
Here are some books I haven't read but that I really look forward to reading:
Sidenote: I've generally found Buddhist practices (largely varying meditation practices) to be particularly enriching and would encourage you to try them out and experiment with them. Although many historical and modern Buddhist sects presuppose beliefs in metaphysical or supernatural phenomena, the practices themselves are largely free of this and have been some of the most helpful and fruitful activities I've partaken in over the past several years.
I've been in a rather massive creative rut since roughly the start of this year (though I think the problem's roots go back further than that). I don't know how useful it is to try to find the reason behind it; at a certain point it stops mattering, and all that matters is just doing something, ceasing the rumination and getting to work. It's so easy to look at the job ahead, be it creative task or sometimes even a simple widget-cranking, and get overwhelmed, particularly the bigger it gets. And it's easy then to realize how silly this is, but then instead of taking positive action, to end up in a downward spiral of guilt and self-pity.
One thing that's helped me move forward in these situations is to simply make the task as small as possible. Perhaps make the project smaller; what was once a giant symphony or double album might just become a single miniature. Forget the massive project list, the massive list of bugs. Let them be. Allow them to be completely forgotten (at least temporarily). And once the project itself is down to its tiniest form, focus on just the next possible physical action. Perhaps it's just "write a melodic phrase." It doesn't have to be a good phrase, even. Just a phrase. No harmony. Just a silly little line. Got that? Good. Now write another to make it a period. Now add some harmony. Again, it doesn't have to be good. Just do it.
And lo and behold, look at that. The beginnings of something. This something may now grow into its original, beastly size, but with the new-found armaments of Confidence and Perspective, the beast loses some of its power.
I've heard this advice many times in many forms from many sources, and yet it always seems to hit me in new ways just when I need it most. Try playing with the idea; see just how small the task at hand can become. What single tiny action can be taken that can move it forward.
The smallest wins can drill massive holes through the thickest creative walls, can shatter the illusion of a monstrous project and reveal its malleable, achievable nature.
Another course of action to defeat "writer's block" is to make as much space as possible to act in, to cut out all the unnecessary activities, leaving only what matters most. This is one thing I'm about to try as I quit my "day job" to take full responsibility of my time and how I spend it, starting this July. I'm excited and terrified, to say the least. I will be occasionally writing here about the transition into full-time freelancing, struggles and triumphs, in the hopes that I can 1.) work through challenges publicly, and hence hopefully more quickly and easily with the help of others, 2.) maintain an active presence and avoid shutting myself out from the outside world (as I might otherwise be prone to do), 3.) leave some kind of a trail through the woods of the freelance world for others to potentially follow (and improve upon) later.
I can't pretend to know how successful I'll be, but I can't remember ever feeling so sure that I was moving in the right direction.
When I wake up in the morning, I often find myself somewhat mindlessly picking up my phone and scrolling through social media. I don't think this is terribly uncommon. If I don't have a strict agenda, I can sometimes find myself doing this for over an hour. I tell myself I'm letting my mind wake up to the day, catching up... but I really don't think there's a lot of value to it. Sure, there are often small delights that friends share, or something particularly noteworthy, or a news story that I should be aware of. More often that not, it's just chatter. There is sometimes value to chatter, but I'm not sure it should be the first thing my waking mind experiences every morning.
Worst of all is the state of my mind while doing this though, or perhaps the lack of any mind state. It's usually completely mindless. I do it without really realizing it. Before long, I'll have spent a good portion of my morning reading the idle thoughts of others. At the same time I find myself rushing to find a spare moment to compose, to think, to relax, to do anything I care about.
The "social" in "social media" is a bit deceptive. Much of the time it's not very social at all; we simply litter a crowded platform with our thoughts. And that's when we're actually posting to it; at it's worst, we're consuming this mental litter, dumpster-diving for the dankest memes, the hottest takes, a bit of idle titillation as we go about our days. Occasionally it is really wonderful the kinds of emergent dialogues that develop, but I can't help but think there's a better place for these kinds of interactions.
I don't plan on giving up social media altogether as folks like Cal Newport recommend; as someone about to become full-time self-employed, it has too many benefits for reaching out to potential collaborators/clients/customers/fans/fellow-humans. Still, I can't help but think that we could become a bit more minimalist and mindful with the way we use social media (...and by "we" I most definitely mean "I"). This idea isn't particularly new or exciting, but for me at least, a reminder is well overdue.
So here's what I'm going to do, and I invite you to join me.
The next time you find yourself mindlessly browsing social media...
Do this for as long as you can as often as you can. See what you notice. Whatever small amount of mindfulness you can bring to your social media habits are bound to at least help you understand them a bit.
In the past, I've found it very difficult to speak openly about how much I appreciate people without my tongue planted firmly in my cheek. Call it a patriarchal, hypermasculinized upbringing in the modern world, call it a manifestation of a culture of unending irony... overcoming it is difficult, and I have a lot of work left to do.
This weekend I listened to an episode of the fascinating podcast On Being, a conversation between Krista Tippett and Alain de Botton about love. To say the least, it was a breath of fresh air. In listening I started thinking about all of the people I know--- the people I rarely see because they live over an hour away, the people I see several times a day, the people I haven't seen in years, and the people I pass by and with whom I share a few words (if that)--- every one of you is a goddamned miracle.
Consider, first of all, the billions of years of evolution of life it take to create each one of these walking emotional computers. Of the billions of people on this planet, you and I get to communicate with one another, and if we're lucky, we'll understand one another. Of course, the moments of miscommunication and a lack of understanding are immensely frustrating and even sometimes dangerous, but so often I forget to appreciate the little understandings that are so easily taken for granted: when I mention a game/movie/piece of music/whatever that I like, and you too have been through that exact same internal journey that the best works of art have the power to take us on; a shared caring for the suffering of hundreds of thousands of people neither of us ever have met or likely ever will meet; a mutual appreciation for a well-organized agenda; admitting mistakes; praising good decisions. I live for these little moments.
Of course, I still frequently feel like a weirdo. I wrote a blog discussing why I like synthwave, hoping with a kinda immature & childish glee that people would flock to it and nod their heads in agreement to the beat... but it was mostly ignored. I certainly don't blame anyone for that; I'm sure I could have written it better, or taken just a slightly different angle, or not written it at all. Those moments can hurt. But I've also been finding myself in awe of just how rad every one of the folks I consider a friend is.
So without naming anyone or making anyone uncomfortable (hopefully), but also without holding back with the mushy emotions, let me just flat out tell you about some of the moments that highlight what makes you, my dear friends, awesome:
...I'll stop there for now.
Every one of my friends is someone I could write an essay about. Some I know on a deeper level than others. I've seen different sides and depths and emotional crevices and pockets of wisdom in different friends. Other friends I know only on a fairly shallow surface level, but even that much is incredible.
By nature I'm a pretty shy introvert; I constantly worry about how I come off to friends, strangers, and acquaintances alike, especially in "casual" settings, and sometimes I just want to hang out at home by myself and recharge---sometimes I really do need that. I've gotten worlds better at recognizing when I need this and how to deal with it in the past several years, but I've never been comfortable with it, and I still fuck up immensely.
And sometimes I crave human connection despite all the difficulties, to break through the small-talk and the misunderstandings and get to what makes us human, that sense of understanding, that sense of sharing in this fucked-up reality of self-awareness, of being perhaps the only conscious living beings in a seemingly spiritually empty universe. Nothing more, nothing less.
And it doesn't take much. Just witnessing these little acts of love and defiance and humanity and I fall in love all over again. Not a romantic or sexual love; just the shortest of moments that confirms that neither of us are robots or lizard people.
And of course, I have to give extra-special attention to the one lady in the world who's decided to make the insane commitment to spend the rest of her life with me, despite all that entails. She's the realest and most hilarious and kindest person I know, the person I know best and who knows me best, to whom I've bared the shittiest parts of my soul, and who I feel has given me the privilege to see her most closely guarded but brightest and most uniquely beautiful hidden flames of wisdom and love. Neither of us are perfect, but we've both made a commitment to each other to help each other through this insane life as best we know how. I wouldn't have it any other way. She's my favorite person. I love you, Elie.
And to all of my friends, I love you. And maybe this is a weird and convoluted way to say so (my word count is currently over 1500, so signs point to "yes, yes this is a convoluted way to say so"), but "I love you" seems to fall so flat, sometimes. I'm not always the best communicator, though, so if this comes across as weird, just take it as a 1500 word "I love you." That's the best I got.
Lately I've been listening to ungodly amounts of synthwave. It's not something I expected to get really, really into, but now that I'm here in the midst of this obsession, I can start to trace where this all came from and why I'm digging it so much.
Different genres and subgenres of music tends to have different purposes, different stylistic tendencies, different strengths & weaknesses. Some music reaches out and demands your attention, which doesn't work so well when you want to get some work done. Music with angular melodies and awkward rhythms and rapid changes of texture, though they'll always have a special place in my heart and in my playlists, aren't exactly easy to grade exams to.
Synthwave, though, by and large, aims to set a mood and propel you forward. The lush pads set up a pillowy structure on which looping drums & arpeggiators & repetitive chordal structures & sparse melodic material move the listener through an inviting (but not overly so) space. The net effect ends up being very similar to minimalist classical music, though of course with a different instrumental palette and an array of other genre tendencies.
I was born less than a month before the 80s were over. My baby-boomer parents were what we would now call "classic rock" aficionados, purists of the Bay Area who knocked on disco and the emerging pop culture surrounding synthesizers and their users starting in the 70s and running through to the present. My sister listened to pop music, but by then it was the contemporary 90s pop-superstars. I grew up occasionally hearing tiny snippets 80s pop music, but only when followed immediately by a turn of the dial and a "Turn that crap off let's listen to some real music." I can't really fault them for that; in fact, I almost thank them; there is now an entire decade of music that feels alien & endlessly engaging to me.
For as long as I can remember I've had a fascination with technology. My first computer was a Windows 3.1 machine. I would dig around in the folders, exploring the structure of the machine just for fun. I didn't understand it, but it was like looking across an alien landscape.
Synthesizers hold that same kind of magic to me (even though I now have a much firmer (though still not complete) grasp on their inner workings than I did of Windows 3.1 as a 7-year-old), and they obviously hold a very central place in synthwave. The genre's very definition is centered around technology and the geekiness that comes along with it. But instead of the cutting-edge of digital sampling, audio recording, and synthesis technology, we go back to this more primal state, where the cutting edge meant instruments that (in an over-simplified matter) increased and decreased voltage on various submodules in a hyper-organized fashion to create music. Even if the tools used to create it are mere emulations of this primitive technology (as it often is today), the result is compelling.
You have to be sort of geeky to really appreciate outdated technology like that. It's related to the same reason I find Emacs & Vim interesting. They get their jobs done despite newer and shinier and more modern tools. And as we've seen with digital Eurorack modules (and, yes, with Emacs and Vim), you can even integrate newer and shinier technology into the more primitive technology without the primitive technology losing all of what makes it so special.
Synthwave, on the surface level, seems in some ways to be rather pointless. After all, if it's meant to imitate synth-based music of the 80s, why not just listen to the music from the 80s?
Well, as I just alluded to, there really have been improvements in the technology used to make music in the few decades. We can do what they did better and faster. We can make more of it. We can spin off in slightly different directions, utilizing the kinds of stylistic tendencies and technology that would not have existed even in the minds of anyone alive in the 80s.
Listening to synthwave is like entering an alternate version of the present, one where pop music went in a vastly different direction, and perhaps other parts of the world went with it. It evokes this false sense of invented nostalgia for something that never actually existed, or that at best that we never actually experienced (I'm going to guess that a big portion of synthwave's fanbase did not experience firsthand the era on which it's largely based). It's magical and surreal. It's escapist even. Maybe too much escapism can be unhealthy, but for now I'm going to keep riding this wave (zing!).
Today I came across this video.
Is he right? I mean, of course (to a certain extent); complaining/"crying" about one's problems won't directly solve them, and it can put the burden of responsibility on others.
My problem with it is that it's really fucking mean and unhelpful to those that need the most help.
I really believe reaching out to others for help (in the appropriate contexts) should be seen as a sign of humility and strength, not one of weakness, or the trait of a "loser" as he puts it. Of course this needs to be tempered with the wisdom to know the right people and time to reach out, but insulting people is laziest and most irredeemable style of motivational speech.
Apparently this style of motivation works for some people. Good for them. But having recently had glimpses of depression-like symptoms, I can easily see how much this kind of speech is just going to push people down rather than build them up. And what exactly is the point of that? To make you feel bigger and better and more successful? And what about down the road, when people who heard this advice come across genuine difficulty and need the help of others, only to remember this and decide that crying is for losers, pushing them further into despair or looking for solutions in the wrong places? Sometimes folks need to just sit and think things through, to process things, to bounce ideas off of others. To say that it's for losers is, quite frankly, complete and utter bullshit.
Or what about when there is literally nothing that can be done to change something on one's own, and you need the support of others? I don't feel like I should have to mention the Black Lives Matter movement and the amount of shit that has been thrown on them by white moderates, but the thickness of this speaker's skull has convinced me to do so anyways. And that is just one example of injustice in the world where "complaining" is the only way to spread the word and begin the long and arduous process of fixing things.
So if you actually want to help others and stop any potential self-sabotaging behavior, consider what is actually going to be helpful to them and stop trying to make yourself feel big. By pushing any one person down, you push the whole world down. You push down all the potential contributions, innovations, and other positive forces that person could bring into the world. You are actively making the world a worse place.
I don't want to dig in too much on this one guy, because he's far from the only one doing this. Apparently he's pretty successful. But there's a difference between a good life and a successful life, and I want no part in pursuing the latter at the expense of the former.
A series of reminders for myself that you may find helpful:
As I think I've mentioned before here, I struggle with ADHD, but have only had a formal diagnosis for just over a year. During that year, with the advising of my doctor, I've been taking a small dosage of venlafaxine (aka Effexor), which is usually used for treatment of anxiety and depression.
This seemed to be helping a little for awhile (with the strange but manageable side-effect of making my whole body itch intermittently), but its effectiveness seems to have waned over the past several months. At first we tried increasing the dosage, which didn't seem to help.
Ultimately I decided to double down on the non-medicative efforts I've developed/discovered over my undiagnosed life (namely, GTD, associated organization/productivity systems, and meditation) and get off medication completely. The doctor advised me to slowly lower my daily dosage over three weeks. Unfortunately, in my inexperience, I decided to take some good advice completely out of context and knock that time down to about 3 days.
Unsurprisingly, this was a huge mistake.
The last few days of my visits with family were plagued with headaches, fatigue, mood swings, extreme inattentiveness, nausea, chills... Great Fun, in other words. The fact that this was essentially my own fault didn't help my mental state. So once I got back home, I started drastically slowing the medication discontinuation process. Things got back to normal for awhile.
This past week, though, life got really difficult again. I thought I was coming off really slowly, but those symptoms returned in full force, especially during the past two days. Perhaps I just need to power through it, I thought. It can be hard to know what "normal" is in the middle of these episodes.
What made this even more difficult was the start of the year and some ambitious goals I had set for myself. When I started failing at these goals the first week of them, I got really hard on myself. When coupled with depression-like symptoms of medication discontinuation, my nights were... not enjoyable, to say the least. Which means I got less sleep. Which means symptoms got worse.
Because it was getting increasingly difficult to pretend everything was fine with me, that I had returned from my vacation well-rested, I decided to publicly post about my struggles on Facebook. I was pleasantly surprised by the amount of support I got from my friends and family. Several friends texted and messaged me. Even some fond acquaintances I don't see very often got in touch. It was incredibly encouraging to know that I wasn't as alone as I was starting to sometimes feel (which seems a little bit silly now, admittedly, but it's hard to realize those things when you temporarily sort of despise yourself).
Today I'm feeling quite a bit better. Honestly, I'm still not sure what the "right" thing to do is, but staying active and not being too hard on myself seems to be working okay. I am much more cautious, though, about my treatment of myself now that I'm really aware of just how fragile my mind is, and now I know that I have good friends and colleagues who support me and understand, many of whom have been where I am.
On a much more important note that I touched upon in my Facebook post: mental health still has such a heavy stigma. Even in popular culture mental health treatment is often seen as somewhat of a joke. But the brain is just like any other organ in the body. Our bodies are not perfect heavenly creations. They're miraculous, rusty, organic machines that have taken billions of years to come together, and even the most well-tuned machines screw up eventually. It's only logical that at some point, most of our brains will have issues that we'll need help dealing with, just like we need help dealing with our stomachs and the flu and any other illness, major or minor. We talk about coming down with the flu like we talk about the weather, but I've felt a deep sense of shame whenever I bring up the issues I've had with my health. Medication didn't seem to work for me, but for others I've heard it's the only thing that brings a much-needed sense of normalcy. There should not be any shame in that.
It's easy to forge a cool, semi-detached façade out of anxiety and insecurity, and to outsiders, it's easy to interpret this as everyone else being perfect. It's almost like a form of imposter syndrome. But the truth is, everyone has struggles. Everyone is just trying to figure out how to get through life. Sure, sometimes some people struggle more than others. But everyone gets their turn at the wheel of the struggle bus.
So let's all continue to support each other and break down the taboos surrounding the mental health and its treatment. Let's talk about our bipolar shifts like we talk about our allergies (if we want to). Let's build each other up rather than keeping ourselves and each other down. It will only make us all better.
As with every year, a lot of folks died in 2016. This year, however, the consensus seems to be that fate has been especially cruel in taking away beloved artists and other public figures from us. In addition, a lot of bad things happened throughout the world this year (first and foremost, the election of a completely reprehensible human being). A few thoughts come to mind about this that I'd like to share.
I wonder how much of this impression that this year has killed more people than usual is actually correct? How many more famous people have died this year than in previous years? My suspicion is that this is just confirmation bias; once "Fuck 2016" took hold, we started looking for proof that, yes, this year is out to get us and take away those people who have so affected our lives.
Indeed, this does seem to be the case.
In addition, we're more connected to "celebrities" than we ever have been, especially with platforms like Twitter. Therefore, I think, when they leave us, it's hitting us that much harder.
Folks (myself included) tend to appreciate others more in death than they did in life. I must admit that I was never a huge Carrie Fisher fan during her life. Not to say I disliked her at all---I didn't know much about her personal life, let alone her public life. She was "the lady who played Princess Leia (who is pretty cool) and I think a crazy ex-wife or something like that in The Blues Brothers." I thought she said some cool stuff this past year about our treatment of women in show business after she re-entered the limelight and people picked apart her appearance in The Force Awakens. Beyond that, though, my impression was a pretty big "meh."
When I heard the news that she'd had a heart attack, I brushed it off; people survive heart attacks all the time. When she died, then, it hit me... confusingly, for lack of a better term. I was a little sad only at first. As I started to read about her life, her advocacy for the normalization of mental illness and its treatment, her renowned skills as a script doctor in Hollywood, her wry wit and dry humor, her kindness, her humanity... I started to feel more and more like I'd lost someone dear, someone important, a friend or family member almost. Of course, she's not family, and I can only imagine how those who knew her personally feel. But she seems now to be the kind of person whose loss can be universally felt
I wonder, how many other people are out there whose sheer existence do we take for granted?
"Fuck 2016." This phrase has been bothering me the past several weeks.
The idea that somehow the unfortunate events of this year are fate, and we are throwing up our hands in defeat rather than stand in the face of misfortune and dare to make the most of our seemingly grim fates; to say that this year is somehow inherently evil despite the fact there is no other way it could have happened, that everything is a result of what's come before; to deny the cause and effect as a fact of life; it all seems hopelessly pessimistic and counter-productive.
First of all, I'll return to a point I made earlier: aside from the election, has 2016 really been that much worse than previous years? Denialism has been a slowly growing force for years if not decades. Events of this year are merely a culmination of these forces. The rise of the tea party and internet-based hate-groups fostering fear, discrimination, greed, and anti-humanism have practically been defining this decade. Maybe it's because I've been watching these forces grow and slowly gain mainstream traction for awhile now, but I don't really see how any of this is all that surprising.
"Fuck 2016" to me seems an excuse. A call to inaction. A collective "¯\_(ツ)_/¯" that fails to actually say or do anything actionable or take responsibility for where we went wrong or offer any productive direction to go from here.
However...
A friend of mine, Garrett Steele, offers an eloquent counterpoint in defense of the "fuck 2016" mindset that I think nicely closes out this post:
I mean yeah, a lot of things went into the year that I think (and hope!) people are learning about and learning from.
And a lot of good things happened! For all the scariness with the arctic ice shelf, the ozone layer is starting to heal. We've got an ebola vaccine. Suicide is down globally and literacy has skyrocketed and it's important to focus on these things as well.
And yes, time will continue to march on, and the personalities and authors and performers who have delighted and challenged us in our youths (and the youths of our parents and families) will continue to pass on.
But I think that there is something about cordoning off this weird experience that we are all having. There's something about taking this confusion and sorrow and putting it in a box and saying "this goes over here" that's useful, inasmuch as it gives people a line in the sand, a point at which to start dusting themselves off and getting ready for the years ahead. And honestly, we're all gonna need that. If drawing an arbitrary border helps folks gird up, move on, and get ready for the turbulent times ahead, then by all means, I'm going to let them have it.
A lot of this post is going to be me merely thinking on paper, or attempting to follow ideas to their logical conclusions, even though they may seem a bit wacky at first. I desperately encourage anyone with more knowledge or wisdom in these areas than me to chime in down in the comments section. I'll update the post with appropriate attribution if there are any important corrections or additions to be made.
The universe seems to be self-correcting. Not in an intelligent way; more like in a programmatic way. Like everything just happens to be the way it is, and although everything seems to be changing near-randomly, there is a practically-infinitely-complex equation that the universe operates on, and every action from the interaction of atoms to the collision of galactic super-clusters is merely a result of this continuous unfolding of the solution to that equation.
Human brains evolved to be more complex because, over many, many years, our ancestors that had more advanced brains tended to survive and make babies. And now we've evolved to be able to have complex ideas like in this blog, and to create machines on which to type them, and complicated infrastructures on which to power said machines and save and share this information and re-interpret it programmatically.
Neuroscientist/philosopher/writer Sam Harris has put forward the notion that free will is an elaborate illusion that is extremely difficult to break through. Our genes determine the basic blueprint of our bodies. From there, our mothers' behavior begins shaping us as we remain in the womb. Upon birth, our brains begin collecting information on the world around us, just as our genes and conditions in the womb have programmed into us. The way we are treated as children, the nature of our upbringing, affects how we behave as adults. We continue learning and putting into practice what we learn. "Original" and "creative" thoughts are connections between disparate parts of our brains, at times misfiring or merely freely associating based on patterns developed by conditioning in other parts of the brain. This equation of a human life is just a part of this universal equation. We cannot help but be any way but the way we are. If time were to rewind a billion times and all factors were exactly the same, the result would be exactly the same a billion times.
On a non-human scale, consider the weather. Wind is simply air moving from high pressure areas to low pressure areas, equalizing. And the various entities moving in the air and water and on the earth continuously through this equalization off-balance. Pressure below the earth builds up and eventually explodes in an earthquake or volcano. This causes ecological and social disruption.
I won't pretend to have much knowledge of the cosmos or of the micro-cosmos or quantum mechanics, but I imagine this pattern likely continues on those levels.
So what does this mean for our immediate, practical, day-to-day living?
For one, blaming others for anything becomes completely pointless. Everyone is merely acting out their genes and their conditioning. Even Donald Trump. Compassion is the only rational mode of interaction with others. It's, at present, impossible to figure out who is a "lost cause" and who can be helped when dealing with someone that's causing a great deal of harm. But knowledge of their conditioning can only help.
And what of God, or the meaning of life?
The idea for this post came about while reading Meditations by Marcus Aurelius in a drafty room while it rained outside. The Roman emperor was talking about the logos, and how everything that happens is meant to happen, and to attempt to work against it is folly. I think there are a few ways (some better than others) that one can interpret this idea, but more usefully, one can decide not to fight against "fate," to accept what they cannot control, to be aware of the effects of their genes and conditioning and those of others, to work hard to act to make things the best they can be for everyone with whom they interact, to remember these facts, to make the most net positive impact possible and limit the suffering that their existence causes.
As for God... nothing I've proposed here presupposes any kind of deity or all-powerful being or anything along those lines. The same questions that atheists and agnostics ask of theists remain just as necessary. I don't have any kind of "faith" in this idea; it's merely a mental model when it comes down to it. As I've said, I'm very open to corrections and alternatives. But I think it is a potentially useful model.
What do you think? I wholeheartedly invite your comments!
...and other questions I should ask myself more often.
How did you fail today?
What were you wrong about today?
How did you admit it?
How did you convey your own vulnerability today?
How did you practice compassion, especially towards those you enjoy least?
How were you defeated today?
I've been spending a lot of time letting my mind go off the rails and, for lack of a better term, just fiddle. By this I mean I spend time tweaking, attempting to improve systems and programs and preferences without much of a concrete goal or end state. This is something I've been doing for some time now, and it didn't start intentionally; it's more a symptom of my ADHD than anything, and at times it can be very difficult to control or even notice.
But lately, I've started to realize some benefits to this kind of activity. I'm learning a lot. Is all of it useful? Absolutely not! Let me just give you a brief list of what I've been doing the past couple of years in some of my spare time.
And with all of this, I've also spent hours tweaking preferences and themes and often, in the end, dumping it all and starting all over again. If you were to watch me while I was doing this (or, hell, if I was to watch myself doing this), I'm sure you would be banging your head against the wall with how much time I seem to be wasting.
And yet...
I don't think it's all a waste. It can be fun even! Learning about databases has taught me how data at my university job is structured (our school uses a giant database called Banner to store basically everything) (I now kinda wish I could use SQL to access said database, now that I understand it more). Trying out todo platforms have made me understand my own tendencies and process action items much more efficiently when the going gets going. Emacs, which has quite literally been around for 40 years, has taught me the power of sticking with one platform for the long term, and the power of open source communities and their work. I've learned to build my own website, like the one this very post is on, and I'm able to use my web development skills on my university department's website. I've learned how the web works (sort of!). Learning coding, terminal commands, and whatnot has given me marketable skills for the future and given my the ability to dig into the technology that drives our civilization and that many of us take for granted. Fiddling with themes has given me an appreciation for the effects of aesthetics on usability.
Above all, the more I fiddle, the more I simply feel more well-rounded and more understanding of the tools that I use every day. So it's something that I'll continue to do and enjoy, and just maybe it will lead to opportunities somewhere down the line. But if it doesn't I won't mind.
I am an abashed (no pun intended) fiddler—and I don't mean I play violin. Recently I've been tipping my toes into the water of open-source development for fun, learning basics of git, CLIs, SQL, Emacs Lisp, various bits and pieces of various programming languages and frameworks, along with music-related languages and frameworks like Csound, PureData (I'm fairly competent already with Cycling 74's Max, which shares a common ancestor with PD) and then, finally Lilypond.
It's, essentially, a niche tool and programming language for typesetting sheet music. In short, Lilypond:Music::Latex:Words. It's pretty difficult and slow to work with, though I'm wondering how much this has to do with my newbieness and how much is inherent with the tool. I could see myself potentially becoming fairly proficient and quick with it and using it as a legitimate compositional tool.
It does have its advantages. Because its documents are human-readable (by programming standards, mind you) plain text, it can utilize version controlling with Git. Alongside this, collaboration becomes a possibility, with public hosting on sites like Github. It's easy to imagine a sort of "open source symphony," where composers can collaborate on making a piece of music, musicians testing out passages and providing feedback on playability via Github issues, composers fixing these "bugs," outside composers suggesting changes via pull requests. One could do something similar to Lou Harrison and John Cage's Double Music, which would now be very easy to compile at the end.
For academia, this could also be an amazing tool for open sourcing textbooks. Imagine if learning Lilypond and contributing to a standardized music textbook were required for every DMA student. Imagine how much more quickly problems of standards could be solved using these kinds of tools, when musicologists could simply code out different possibilities and vote on which ones seem to work best. If a particular school decided something wasn't working for them, they could tweak the "standard textbook" or add some of their own examples to it, possibly proposing these additions to the core text after some refinement. Imagine how much money music undergrads could save if they could find their texts on Github rather than paying literally hundreds of dollars for texts that get new editions every couple of years. Imagine, also, how much more accessible this kind of knowledge would then be to those who don't have the money to attend college but want to study music.
Obviously this is a bit of a pipedream for the present, but the predecessors to these ideas already exist[1]. Unfortunately, however, the contributors are few and far between. This is one of the reasons that learning coding should be an absolute priority for all students. Folks like to think that this kind of knowledge simply doesn't apply to them, that they can't utilize these skills in their fields. Sorry to say, they're wrong. The possibilities are easy to imagine.
Being an active reader of literature on personal-development, productivity, Buddhism, and related fields, it's hard to avoid pieces that stray into topics that are unsubstantiated or pseudoscientific at best, through the 'metaphysical' middle, and down to being absolute horse-shit at worst. Even some of my favorite books will have great ideas about how to improve one's life before throwing in some statement of fact about reality that has absolutely no evidence. Tony Robbins (who I've talked about before) has even gone so far as to say that it doesn't matter whether or not something is true if it's helpful. This is essentially a version of Pascal's Wager and there are a lot of reasons not to buy it.
Sometimes these bits of pseudoscience are associated with good points, though, or they point at something helpful. So how can we get the most out of these texts without throwing the baby out with the metaphysical bath water?
I came to the realization recently that, through applying a healthy dose of critical diligence, one needs only to think of these bits of quackery as metaphors or analogies to get the benefits without having to lie to yourself or fall down a rabbit hole of nonsense.
Let me give a few examples. The "law of attraction" states that if you want something enough, you need only think about it intently, about your given goal, and it will naturally come to you. Books have been written and movies have been made about it. It's obviously baloney. However, it can be a useful reminder that our minds are, at least partially, in control of the way we view things, and our perceptions, be they deeply flawed or clear as crystal (a state arguably impossible to reach, but I digress), are largely responsible for our emotional wellbeing. So even though we may not be able to simply think our way out of a financial crisis, we can find the bright side in (or through) difficult situations. If we think that everyone is out to get us, then we are going to view everyone as coming out to get us; in a way, we attract them, if only in our minds.
Karma is another difficult topic to grasp. I've been very interested in Buddhist thought lately, and I don't think I've come to fully understand karma and its (tenuous, I think) relationship to reality. The idea is that whatever good or bad you do will come back around to you in this life or the next, and so presupposes a dogma of reincarnation. However, viewing karma as simply a rule of thumb, one can remember that most people don’t appreciate people that do unethical things, and for most people that aren’t sociopaths, people don’t particularly enjoy doing bad things. Ergo, if you do good things you’ll probably have a better time than if you do bad things. Karma!
This idea, of course, has its limits; not everything can be turned into a helpful metaphor. Recently I was listening to a podcast with a woman talking about the value of thinking about what we eat, where it comes from, the lifestyle it demands of its harvesters, and what it does to our bodies. Good stuff! Unfortunately, it very suddenly took a sharp left turn into crazy-town, and she started talking about how vegetables that are hard and grow in the ground like carrots have a grounding effect on our personalities, while potatoes start out that way but can be made soft (i.e. mashed potatoes), which will make our bodies soft and make us more emotionally sensitive. I stopped listening at this point, but I after that I can’t imagine that the rest of it had much of a resemblance to reality. So yes, let’s think about what we eat, but there’s no way carrots have a “grounding” effect on our psyches, especially not because they happen to grow in the ground. That is, again, pure baloney.
I don’t mean to just bash someone who views the world differently than me, but I think it’s crucial to be vigilantly skeptical when reading books in this field, as it’s all too easy to for an author to impart “wisdom” that proves to largely be a waste of time. However, once you’ve developed a sense for what’s bullshit, you can realize that even some gold nuggets can occasionally be found within.
(…Pardon the gross closing metaphor.)
Lately I’ve been much more interested in games that don’t feature dudes shooting guns and saving the world from evil bad guys (or on an extra special occasion, a lady will shoot guns and save the world!). This has been a slowly growing trend for me (as evidenced by my (sort of) recent Undertale blog). I’m well aware that there are a ton of great games out there with these sorts of stories (and indeed, there’s still some that I’m really looking forward to), but I’m just getting a tad worn out on the ubiquity of them.
Life is Strange is the perfect antidote to this disillusionment.
For those unfamiliar, the game follows Max Caulfield, a teenager living in a small Oregon town and attending a boarding school with a prominent photography program. Following a tragic event on an already strange day, Max realizes that she has somehow gained the power to rewind time.
https://www.youtube.com/watch?v=mpRhaXfvG_0
I know, it sounds pretty silly, and a lot of the time it is pretty silly. But amidst a tale of science-fiction and high school drama clichés, a truly powerful and touching story of friendship unfolds that simply wouldn’t (and won’t) have the same impact in any other medium.
One of the reasons I’ve been so in love with these sorts of games that stick the player in the middle of a deeply affecting story is the empathy it builds. Max’s best friend Chloe becomes my best friend, and I care about what happens to her. Max’s choices are my choices, and when that means that something humiliating happens to the petty-but-ultimately-just-naïve school drama queen, I feel guilty. Betrayal by characters I trusted stings and enrages. Feeling these kinds of emotions in a safe environment can be strangely freeing and gratifying.
The game captures what it’s like to be a teenager in a small west-coast town better than anything I’ve witnessed in possibly any medium (though that’s admittedly a pretty small niche of experience). The characters don’t just feel like high school archetypes; they feel like kids I could meet if I drove down the Oregon coast and walked into a mall. Sure, some of the modern teenage vernacular doesn’t really come off quite right (“Ready for the mosh-pit, shaka-brah”), but I think we forget just how awkward and weird high-schoolers can be. In a way, it even contributes to the game’s weird aura of timelessness. From a technical standpoint, as well, some of the animation looks pretty wonky, but I didn’t play this expecting to be blown away visually (…and yet I occasionally was!).
The less justifiable missteps of the game (when viewed on their own at least) are the narrative ones, namely the clichés and predictable twists it falls on at some key moments. However, there are many more moments where the game skillfully subverts these clichés and gives us some genuine shocks. Looked at overall, the story is more than engaging and expressive enough that it’s easy to forgive its occasional fumble, especially when the whole game is so earnest and endearing.
In the interest of avoiding spoiling the game for those that have yet to play it, I’ll forego any discussion of the ending, themes of sacrifice and letting go, and or the quality of the last episode (it happened to be my favorite!), but if you’ve played it, please let me know your thoughts on the game in the comments section below! And if you haven’t played it, go do it! It’s available for PC, Mac, Linux, PS3, PS4, Xbox 360, and Xbox One, and the first episode is free.
As a composer (and, I’m sure, in any creative endeavor), it’s extremely, deceptively easy to fall into a rhythm and habit and just write without thinking too much about it. We simply write what we think sounds like it should come next in that moment alone; where the melody “wants” to go. Often, this leads to a very natural, effortless style of music that flows out of the composer as if it just had to.
But sometimes, before too long, this begins to make the music stale, repetitious, generic. Like we’ve heard it before. Lifeless. Almost too easy. Lacking in energy. The audience can sense that there’s just something missing from the music, and the composer might feel conflicted—they felt so good composing it, and yet now that they’re hearing it in this context, something just isn’t working.
So how can a composer overcome this tendency to fall into habit?
The answer, I think, is mindfulness.
With every note a composer writes, they need to approach it with a sense of adventure, playfulness, openness. “Should this melody really descend now? Should it descend by this much? Should this note last for this long? Perhaps this needs to be a fermata? Perhaps this needs to be a measure of 7/16? Perhaps the harmony needs to begin a 12-tone row? Maybe this instrument needs to just sit out, and the accompaniment needs to just hold its note for a few more seconds while the drums pound out that ostinato?”
Certain paths can probably be discounted more quickly than others, but don’t rule anything out immediately. Give it a chance if you haven’t considered it before. And even if you have, maybe that idea in that other piece that you thought was absolute trash might bring the piece to life in this context. Maybe you don’t need to repeat this chord progression or this melody. Maybe your song doesn’t need a chorus. Maybe your piano sonata doesn’t need to recap the primary theme after the retransition.
Remember that section that just flowed out of you without a care in the world last week? Revisit it. Make sure it’s really what you want it to be. Maybe some of those arpeggios can be changed around? Maybe they don’t need to be arpeggios? Maybe the melody should leap up an octave right there? Maybe it’s perfect as it is, and it really was a magical fluke of a moment where you just spewed out this fantastic creation after weeks of tormented overthinking.
The point is, be aware of your creative tendencies. Allow them to be, but be aware of when they might not be actually helping you. Try pushing them in unexpected directions. This is how we grow as creative individuals—by allowing ourselves to leave our creative comfort zones and give some wild ideas a try. Even if they remain wild (or even outright horrendous), you never know when you’ll find something amazing in a pile of garbage.
A few months ago I finally gave in to peer pressure and bought & played Undertale.
In September 2015, Undertale seemed to appear out of nowhere. Suddenly on Twitter and Facebook, everyone seemed to be talking about this mysterious new game (at the time, I didn’t realize that it had actually been a big Kickstarter success two years earlier). “Don’t read anything about it,” they said. “Just play it. You’ll be glad.” Unfortunately, at the time I was getting acclimated to a new job, writing several pieces of music, and trying to maintain a social life with at least my wife. And then by the time I had a bit more time, I had just bought a Playstation 4 along with several games for that, soooooo yeah.
I unfortunately had the plot explained to me by a student’s paper in my Survey of Video Game Music course at WWU, but it only made me want to play it more. I finally got my hands on it in February, I think, and played it every day until I finished it.
It has quickly become one of my favorite games of all time.
Of course, it’s not without its faults; although the battle system did have its fair share of interesting and fun bits and framed a lot of the game’s funniest gags, the “bullet-hell”-esque system frequently became more obnoxious than fun. A few aspects of the game, however, push it above and beyond what few setbacks it has had and into the realm of Important Games.
The game is hilarious. It sets up expectations and turns them on their heads in fantastically clever ways. Within the first moments of the game, you meet a little flower who tells you about how one of the game’s systems works, only to turn its helpful smile into a horrific cackle and attempt to kill you. Characters have their own often-hysterically-stilted dialogue-progression sounds. And, without giving too much away, one of the final bosses completely throws away the entire game’s aesthetic in the most absurd and over-the-top fashion.
The game’s sense of humor reminds me a little of Rick and Morty or Bojack Horseman. Although Undertale is certainly more kid-friendly, it shares its sense of rapidly juxtaposing absurd situations with touching, tender moments, sometimes more subtly than others. It makes for an emotional roller-coaster that would put any Roller-Coaster Tycoon player to shame.
Like much of the game’s aesthetics, the music borrows freely from all sorts of retro sources, namely from SNES/Genesis-era games, especially Earthbound and Chrono Trigger. This sort wavetable synthesis aesthetic melds with more “traditional” chiptune sounds, but also with fairly straightforward orchestral, rock, and pop sounds into some kind of lovely, post-chiptune palette.
With this palette, creator/writer/designer/composer Toby Fox masterfully weaves a web of melodic motifs that reoccur all throughout the game in various forms, tugging on the heartstrings of the player. The music itself is often fairly simple and straightforward, and sometimes its implementation is just a tad bit janky, but it’s exactly what it needs to be. There are rough edges around many aspects of the game, and somehow it makes it even more endearing all around. The game is innocent, playful, and unafraid.
My favorite part of this game, though, is how it truly, uncompromisingly promotes a path of peace. As you would expect in any regular JRPG, the game gives you the option to fight and kill the monsters you encounter. However, this changes the entire game on a fundamental level, turning you into a destroyer of worlds, into the real monster. It makes you know and forces you to acknowledge that these are beings whose souls you are literally crushing. Should you instead choose the more honorable path, the game’s entire essence becomes a journey of making new friends, of forgiveness, of empathy, of learning to see the good in others even when they are trying to hurt you. It becomes a game about, against all odds, standing up for peace and harmony.
And what's more, this is a game that is accessible to people of all ages. This means, of course, that it's attracted some fans that can be, well... a bit immature (or even obnoxious) should they be encountered online, but the fact that it has had the opportunity to so profoundly touch the hearts of so many people and teach such important lessons is wonderful.
Yes, there are other games that have had, against the norms of the medium, had mechanics that didn’t involve violence, and there are other stories in other media that have, probably more elegantly, demonstrated this point. However, this is the first game with which I’m familiar that has given the player this level of choice in the matter, and that has made love such a huge part of its essence.
So, have you played Undertale? What did you think? Have the vast fan theories turned you off from it? Do you think it’s the Bestest Game Ever Like Oh My God? Is it Teh Suxorz? Do you find yourself humming “Dating Start!” while walking down the street? Let me know!
This is another post adapted from an extended Facebook post, this one from (I think) early December 2015. I think this was one of those rare, exceptionally lucid moments where I captured what I was feeling fairly well, and I stand by it now. I know two kinda political posts from me in a row is a little bit weird and probably slightly uncomfortable and maybe even not what you were looking for following this blog? But, you know, you get a little bit of everything with me, I suppose!
Sometimes I make the terrible decision of opening my eyes in the morning and, in an effort to wake up a bit more, pull out my phone and open up Facebook. This morning I saw people arguing about another recent shooting, stories about political candidates and their money, racism… We can do better. I realize Facebook is not the best place to gauge this kind of thing, but what really got me going was when I saw someone argue that humans, deep down, are animals that will just kill each other given the chance.
I refuse to believe that. And I’m not just saying that to stick my head in the sand or something; I’m saying that we have GOT to have higher standards than that. We can do better. We don’t have to live in a world where there are more mass shootings than there are days in a given year.
And I’m not just talking about stricter gun control, I’m talking about the kind of hatred we tolerate as a society, the kind of vitriol that people spew every goddamn day. We all let it happen. Every one of us. Not just the obviously racist threats people get arrested for on anonymous social media platforms, but the things we say about each other behind their backs. I’m certainly not innocent.
We can do better. We can hold ourselves up to higher standards. Consider each other’s perspectives every moment of every day. We always tell ourselves “So-and-so would never understand.” Well, no; when you’re just yelling at each other, neither of you will understand! Escalation and fighting and yelling and arguing and rabbling isn’t doing a lick of good.
Can we start putting some effort into acting like rational, capable human beings? Imagine what we could achieve if every single one of us put a little bit of effort into fostering higher standards of behavior for ourselves. Do one more nice thing today than you usually might, and one less tiny little kinda-mean thing than you usually might. Maybe don’t post that meme that makes fun of people’s spelling. Maybe hold the door open for someone who’s a little too awkwardly far away. Maybe don’t gossip about your cartoonishly clumsy coworker. Maybe give a few bucks to that homeless man you pass every day. Maybe just SMILE at him.
We can do better. We MUST do better. I must do better. YOU must do better. Right now.
Just as it was in 2014, 2015 was a year where things happened. Here’s a list of some of those things!
I have a lot of fun plans for 2016 as well. They include…
I won’t belabor the point. Happy (belated) Hanukkah, Happy Solstice (EMBRACE THE DARKNESS), Happy Christmas, Happy Kwanzaa, Happy Boxing Day, Happy New Year, and Happy Whatever Else Happens to be Happening Right Now. Cheers!
There is no such thing as sad music.
There is no such thing as happy music.
Music has no inherent emotional value or meaning.
Hearing this, some may at first react rather negatively and strongly, saying that this is reductive and takes all the fun out of music.
I disagree.
To realize that music itself does not have any meaning is freedom. It means that we don’t have to feel sadness when we hear music in a minor key, and we don’t have to feel pumped when we listen to music that’s big and fast and explosive. We can ascribe whatever meaning(s) we want to the music we hear and that we write.
This, of course, has a lot of interesting corollaries when it comes to music for visual media, and even to concert music with program notes. In fact, the way I tend to write concert music, it starts out just writing notes. I search for whatever sounds happens to appeal to me at the moment of writing, re-arranging notes and sections at the whim of the present. Usually, I’m not seeking to address some pent-up emotion or paint a specific picture. I’m just drawing musical shapes that I think look (ie, sound) cool to me, then and there, putting them in an arrangement that makes an interesting pattern.
As the forms start to coalesce, usually something happens to all these musical shapes. Landscapes pop out. Faces appear. It’s like Google’s Deep Dream machine applied to white noise. Sometimes I’ll then purposely shape the sounds to more resemble the newly born figures, but often the abstract representations end up being more interesting than the whatever my mind might be interpreting from the images.
Sometimes at this point I’ll keep my title and “program” for the piece of music vague, a faint impression of what seems to be the most dominant feeling that it gives me, and/or that others might be able to take away from it, or to guide the listeners towards certain shapes in the sounds. Other times the title and/or program notes will be a little bit more specific, but virtually never outright label the individual shapes.
Maybe the triangles are pyramids. Maybe they’re the tips of spears.
Maybe that sound is like a siren. Maybe it’s like a whale-call.
Music for film is obviously a different beast. The preset time constraints and images force one to compose with a pre-conceived image in mind, whether they work with it, against it, or in spite of it.
Video games also provide a different challenge, since the non-linearity leaves one open to make more decisions, but the visuals still provide a point of reference that one should keep in mind when making any musical decisions.
But I digress.
The point is, the music, when separated from the image, out of context of the image or program note or lyrics or title or what-have-you, doesn’t have a meaning. It’s just sound. Just air vibrating at complexly varying, interlocking frequencies. Sound patterns. You don’t have to be afraid of atonal tone clusters. You don’t have to think vast Nordic landscapes when you hear Sibelius. John Cage doesn’t have to be “just noise.” Metal doesn’t have to be satanic.
Music is only what you make of it.
But I also think there’s something to be said in finding happiness exactly where you are, in really, truly getting to know your own area and finding comfort there, in becoming a long-lasting part of a community, letting your roots run deep into it. I think it goes beyond just staying where you happen to feel safe and comfortable, although that certainly is admittedly part of it. Staying in one place for awhile and letting yourself become a part of a place is a different kind of pleasure, I think, than that of the traveler.
I don’t mean to say that one way is any better than the other. I would definitely like to visit other places, but I wouldn’t call what I feel “wanderlust.” And if you feel that strong urge, and especially if you act on it, good on you. That’s great. I just… don’t feel that urge. And the longer I stay where I am, the happier I realize I am right here, sinking my roots into this fertile soil.
As of today, I’ve officially started work as the Program Coordinator of Western Washington University’s Department of Music. After a long summer of waiting for the position to open up, applying, interviewing, and finally being offered the position, I’m extremely excited to be able to serve the very people that I was among just last year, helping young musicians find their way in the department on a full-time basis.
Lots. The biggest and my favorite part of it is academic advising for students, telling them what classes they need to take, offering suggestions when things don’t line up perfectly (they often don’t), helping them graduate, etc.
I’d also like to take some of the subjects I’ve talked about on this blog (personal development, habits, mindfulness, etc) and offer that kind of advice for students who may be interested.
There’s also a lot of administrative work involved, tracking data, managing curriculum and scheduling, etc.
Honestly, this is kind of a dream job. Not long ago on Twitter I asked for suggestions of games that involved managing data on spreadsheets. That’s a pretty big part of my job now. Some would probably roll their eyes and get bored even thinking about that, but I am STOKED.
I’ll never stop composing. Writing music is a passion of mine. Honestly, I think this job will benefit my composition. When I have the time to compose—and I don’t think I’ll have as much of it—it will be more focused now.
In addition, because I don’t have to rely on my music for income, I can write the music that I truly want to write and that comes from my heart. Remember, Charles Ives was primarily an insurance salesman. He never made a significant living off of his music. And what I’m doing is even in a related field.
I get the opportunity to serve musicians now! Service is something that I think has been missing from my life for the past couple of years, and I’m really excited that it’s back again. I find it extremely intrinsically rewarding.
I’ll try to keep this blog and newsletter updated with stuff I think is interesting. It may fall by the wayside. Who knows! I’m just going to roll with the punches and see how things go. The future should be interesting, however it turns out.
I recently listened to an interview with Glenn Beck. As you might guess, I’m not a huge fan of the guy, and I was expecting that the interview wouldn’t change my perceptions of him.
Having attempted to listen deeply with an open mind, I can say with some degree of confidence that I still disagree with a lot of what he seems to think, and I think many of his views are misguided. In some ways, he still seems like kind of a jerk. However, I also now see him more as a human, trying his best in the world to make it a better place. In a few areas of his life, I even kind of like the guy.
Yes, you read that correctly: I think there are likeable, admirable aspects of Glenn Beck.
A similar experience happened listening to interviews with Arnold Schwarzeneggar, Tony Robbins, and just listening to Tim Ferriss himself, who until recently I had taken as no more than a self-absorbed, hedonistic jerk (based on what little I read from the beginning of his book The Four-Hour Workweek). I still don’t particularly like the guy, but I can see him and everyone else a little bit more clearly now. They’re all human beings, like me. They’re all just trying to make their way in the world, none of them seem particularly mean-spirited, and they all, like me, have a passion for making the world a better place. Our methods are very different, and even our vision of what exactly “better” means are different, but I think there are more commonalities amongst us all than difference.
It’s so easy to go through life and, in your interactions with others with whom you have a difficult time getting along, walk away at the end of the day and say “I don’t like them. They’re mean, full of themselves, annoying, they’re making my job harder, their expectations are too high.” But what if we could see beneath all of our vastly differing, sometimes-contentious exteriors and see each other how we all are, beneath the thick layers of upbringing and teachings and environments?
I’m reminded of the classic Flannery O’Connor short story, “A Good Man is Hard to Find” (I highly recommend it if you haven’t read it; it’s a quick read), particularly the end of the story, when the grandmother has her moment of clarity and suddenly exclaims to The Misfit, “Why you’re one of my babies. You’re one of my own children!” A few moments later, The Misfit says to his counterparts, “She would of been a good woman… if it had been somebody there to shoot her every minute of her life.”
Last year, at the suggestion of just about every productivity and self-development blog and book out there, I tried reading The 7 Habits of Highly Effective People by Stephen Covey. I couldn’t get more than a few pages in. I looked at the chapter titles (“Be Proactive,” “Put First Things First,” “Think Win/Win,” and worst of all… “Synergize”). I started reading about his Christian faith in the introduction, his apparently-perfect family life… I slammed the book shut and said “NOPE. This guy and I are just too different. There’s no way I can see eye-to-eye with what he’s trying to tell me.”
But I kept hearing all of these great things about the book. I thought, there must be something I missed in there. So I gave it another shot, and just finished it the other day. I think, in all honesty, it might become one of my favorite books. The language he uses is full of clichés and anachronisms and corporate lingo buzzword salad. But beneath all of that, his messages are something very special. They’re universal, yet at the same time unique, something I hadn’t quite seen anywhere else. To use some of Covey’s clichés, I had a “paradigm shift.” It was “an idea whose time had come.” He calls it “habits of highly effective people.” I might call it “ideas for living a fulfilling and purposeful life.” Or something. ¯\_(ツ)_/¯
Reading and listening to those we don’t think we like can only make us better people. There might be a tendency to fear that ideas can infect us, change us for the worse, like Communism was thought to do during the Red Scare.
But on the individual level, we can seek to simply understand each other. We can listen deeply and intently and truly, not just looking for that moment when one can reinforce their own views (“Ha! I knew Glenn Beck was a jerk!”), but to understand why people are the way they are, where they come from, what makes them who they are, what their concerns are.
It can only help us grow, as long as we hold on to our principles, whatever they may be, and keep a critical but open mind, refraining from any knee-jerk reactions. In practicing empathy, we can better understand each other and the world, and perhaps together help make it a better place.
PAX is one of my favorite times of year, primarily because up and until recently, it’s where all of my real-life interaction with game industry folks happened, and every year it manages to reinvigorate my love of games and the people that make them. This year was no different.
I won’t give you the play by play and painstaking details, but I’ll give you some of my favorite parts in roughly chronological order.
I think my favorite part of PAX this year is I’m starting to push myself out there enough and getting to know enough people that I’m starting to actually being able to hang out with people at PAX; in previous years, I was basically by myself the whole time. I don’t mind that so much when I’m browsing the games, but in the in-between time it was nice to continually run into friendly faces and recognize people.
My one regret: I didn’t take any pictures of the aforementioned lovely people. Next year, I’m going to have to get over my fear of public/group selfies and actually document the good times and the rad people.
Next stop: perhaps GDC…?
Inspired by Junkie XL’s intriguing video series on his studio and film work (especially on Mad Max: Fury Road), I decided to make some custom instruments using Native Instruments’ excellent industry-standard sampler Kontakt (don’t buy it at full price; it and Komplete go on sale at least once a year) and some drums I had access to; so far, one small bass drum at the nearby university, and one small bass drum at home that I got for free a few years back.
I’ve used these instruments in a few projects now, and I’m actually quite satisfied with how they turned out given the somewhat-impromptu recording setup and my meager experience with Kontakt. I thought I’d give a brief run-down of my process, and hopefully inspire someone else out there to add some unique, personalized sounds to their own music rather than only using presets and commercial libraries.
This was definitely the most amateur-y part of it all. I think I ended up using the source from my Zoom H1, which is not exactly the greatest field recorder out there. Somehow, though, it got a nicer sound than the other mics I tested out. It was also recorded in my apartment bedroom, which does not have a fantastic sound. Still, I’ve been recording in there for about 5 years now and I’ve gotten very accustomed to getting a decent sound out of the less-than-ideal space.
The recording itself was a bit tedious. I set up the mics, grabbed a drum stick, and hit the drum as softly as I could. I waited a few seconds to let the sound decay. I hit it again at a similar volume. I waited. I repeated this over and over, gradually getting louder. Not all the hits were great. It ended after I hit the drum as hard as I could a few times. Then I took another couple of passes at volumes where I didn’t feel like I had enough hits.
In the end, I had one long, boring audio file with a bunch of drum hits.
First, I listened through the long, boring audio file in Reaper (my DAW of choice) and got rid of any hits where I flammed or hit the drum in a weird spot or that otherwise didn’t fit in.
At this point, I normalized the file using Item properties: Normalize multiple items to common gain (shortcut: Shift-N) (since I had cut out sections of the original file) and then used the action Item: Dynamic split items... (shortcut: D) to split the items at their transients. I had to do quite a bit of tweaking and adjusting, but it was still faster than splitting everything manually.
Then, I made use of an action in the excellent SWS extensions for Reaper referred to under the action list SWS: Organize items by RMS (entire item) / peak / peak RMS (I can’t remember which of the 3 variations I used, but they all get pretty similar results; it might be worth trying out each of them). This made it so that the individual hits were ordered by volume from soft to loud. I could then listen through again and get rid of a couple of hits in any dynamic range where there were too many hits.
I then applied a bit of processing to the samples on the track; just some subtle compression and EQing to make it sound a bit cleaner. I used the action Markers: Insert regions for each selection item to create a separate region for each sample, and then rendered out each sample into a separate file named numerically by volume.
At this point I moved into Kontakt and made a new instrument (Files->New Instrument) and dragged the samples into the Mapping Editor. Contrary to how most samples are laid out (but in exactly the same way Junkie XL does it), I arranged the samples by volume across the keyboard. Most sampler instruments put one drum on one key and control volume with velocity, and often have some randomization involved to make it sound more human. But here, instead of the samples increasing in pitch as you move up the keyboard, they increase in volume. This way, I think, gives you a bit more control over the sound once you start using the instrument in a piece of music. I did decide, additionally, to keep velocity control of each sample, partly because it does that by default, but also just to have that extra level of control.
I adjusted the envelope of the drums, giving them a pretty long, natural sounding decay and release, and added a bit of Kontakt’s built-in reverb just to make it sound like it wasn’t recorded in an apartment bedroom. I tested the instrument out, playing each key up and down the keyboard to make sure it still sounded natural and was actually in order of loudness. I had to rearrange a couple of samples, but generally it worked really well.
I saved the new instrument to a folder near my other custom instruments I’ve accumulated (mostly some freebies I’ve found online) and that I’ve put it in Kontakt’s QuickLoad section so I have quick access to them (I really wish I could stick it with the commercial libraries… hint hint nudge nudge, Native Instruments…).
Now I have a nice, unique, personalized drum that I can use in my music projects. No one else has this instrument, and even though, yes, it’s a drum, and most people don’t notice when commercial drum libraries get re-used… you won’t hear this exact sound anywhere else. It’s subtle, but I think it helps my music stand out just a tad bit more.
You may also ask, why not just record the drum when you need it? For me, that means pulling out my drum, making sure there’s no noise outside my apartment or from neighbors, closing the doors, setting up the mics, and doing several takes of recording the part. Then, if I decide later I don’t like the part, I have to redo all that. This way, I just pull up Kontakt and play some stuff on my keyboard or program the MIDI part and add some humanization if it’s not particularly conducive to keyboard playing. Much quicker, which is vital for projects that are on tight deadlines.
So hopefully you got a little something out of that! As a special treat, I decided to record a little walkthrough/kinda-tutorial about the making of this instrument. Check it out if you’re interested and want a closer look!
For the past several months (years maybe?), I’ve been reading a lot (and attempting to implement) about how to better promote myself and market myself and network and all the weird Silcon-Valley-etc-online-entrepreneur mumbo-jumbo that goes along with that. Some of the folks that do it seem to get along fine with it but I’ve decided that I’m just… done with it. I’m not interested anymore in trying to convince others that what I’m doing is worthwhile.
I would much rather just write music and share it with my small group of friends and family, some of whom care and some of whom don’t. If opportunities come my way, of course I’ll take them, with all the gratitude and enthusiasm they deserve. But I’ve been struggling with the idea of self-promotion ever since I started really trying at it (and before that really). I haven’t even been trying that long, but I’m looking down the long road it’s taking me on, and the road doesn’t look all that enjoyable to me.
I don’t dislike people; on the contrary, I sincerely cherish the company of my friends. I don’t dislike meeting new people; meeting Matthew and Gordon from the Beards, Cats, and Indie Game Audio podcast was a really exciting moment for me, they seem like genuinely kind people, and I look forward to the next time I get to talk with them.
I do, however, have some pretty nasty social anxiety that can make getting out and going to events difficult, especially when I don’t really know anyone there. Even when I do know people it can be extremely overwhelming. I have a hard time remembering names. I don’t know what to say or what to ask. My mind goes blank. Sometimes I don’t even get that far and I get overwhelmed before I even have a chance to leave my house.
Whenever I’ve researched how to deal with this, the advice given is generally “Get over it.” Which is not particularly helpful. I’m all about self-improvement, but I’m pretty happy with the way I am when it comes to this, and I’m happy with where I am in my life. I don’t really want to “get over it.”
I’m a fan of comfort. I don’t go to parties often. I seldom go out to shows. While all my friends move to Seattle, I look forward to the day I can settle down and buy a house in Bellingham, what next adventurous day job will come my way. To many, I suspect that “adventurous day job” is simply not in their vocabulary; a day job would mean being held down, the death of adventure, a lifeless world of adulthood. But I cherish the idea of going to work and making the most of it and coming home and watching a movie with my wife, or reading our respective books together in bed, and, when I get the time, playing with friends, putting out some music, and seeing who connects with it.
This doesn’t mean I’m abandoning my dreams: I will continue to write music and put it out into the world in various forms, and I’ll continue to look out for opportunities to write music for other projects (games, films, other musicians). I’ll probably (maybe) (kind of) be trying my hand at other forms of art as I gain the confidence to do so, and I do hope that it connects on some level with those that interact with it. But I’m not going to play the self-promotion game anymore. Maybe someday I’ll have the resources to hire someone else to do it for me, but I realized recently that it’s not something that I enjoy, and the kind of “success” it brings is not something I’m particularly interested in.
In my mind, I liken it to a more desperate form of dating: going out and actively trying to find someone to marry. That’s what I’ve been doing. Some people enjoy playing the dating game. I’d much rather just do what I do, be myself, sometimes meet friends, but also be comfortable, happy, and guilt-free staying home with myself. If I happen to meet “Success” somewhere down the line, then so be it. But I’ll be happy and at peace, too, if I don’t.
Participation in music can sometimes get kind of a bad rap. My tendency is to associate it with somewhat patronizing practice of having the audience clap along or do some kind of call-and-response thing, which in practice usually only goes to show how little said audience remembers from the meager music education many of them probably had.
But this isn’t always what was meant by “audience participation” in music. Music being a participatory activity used to be somewhat of a given.

In the 19th century, if you wanted to listen to music, you had a couple of choices. You could wait for the next concert at the nearest concert hall and hear whatever they were playing (if you could afford it). You could make friends with someone who knew how to play whatever you wanted to hear. Or, you could learn it and play it yourself on your piano, which was much cheaper and more affordable to the rising merchant class than it had been only a hundred years ago due to technological advances and decreased production costs.
Learning to play the music yourself, you would get to know it intimately and develop a deeper appreciation for the especially intricate sonatas of composers like Brahms. You might perform it in a parlor with a sizeable group of friends and make a party of it, trading off Chopin preludes, perhaps some original compositions, and maybe an improvisation challenge or two.
Music wasn’t just background music at these parties. This is what they did for fun instead of movies and video games. It wasn’t something they listened to while they did homework on their headphones. They got together with friends and participated.
Today, the most common form of participating in any kind of musical activity is putting on a pair of headphones and zoning out. In classical music especially, a thick fourth wall divides the music-makers and the audience. Arcane rules about clapping between music and referring to the conductor as Maestro completely turn many audiences off even stepping into concert halls around the world. For most, the general impression of the classical community is one of stuffy, wine-drinking snobs.
It’s true that music education still costed a great deal of money and was reserved for the upper-middle class in previous centuries, but I would venture to bet (I’m definitely not a musicologist, so this is, indeed, a bet, not a claim) that a hell of a lot more upper-middle class folks were educated in music back then than are today.
I don’t want to sound alarmist. I love putting on a nice set of headphones and just letting music wash over me (though after 7 years of training it can be difficult to turn off my analytical brain…). I think modern technology and the experiences it brings with us are worth embracing. However, I also think it’s unfortunate that we’ve just about completely lost out on some of the widespread communal aspects of music-making.

A few things can (and most some cases already are) help(ing) bring back these participatory sides of music-making. You probably won’t be surprised to see me start with video games.
A game/app/thing for iOS was recently released for “Clapping Music,” an old piece by Steve Reich for two clappers featuring one iconic rhythm repeated and continuously shifted by one rhythmic unit until finishes. The app does a great job at teaching the piece and allowing players of all backgrounds to learn it and master it at their own pace.
We saw Guitar Hero and Rock Band, both of which are scheduled to perhaps make a comeback in the coming years, bringing participation back to music in their own way. Although players were, in most cases, not actually playing the music, players still engaged with the music more than if they were simply passively listening to it, and entire parties were had that revolved around participating in the play of making music with these games.
The DIY community has been vibrant for decades all around the world, and with the Internet anyone can sell their own music online now. YouTube has allowed people to share knowledge, wisdom, and skills with regards to making music on common instruments. Going forward this could become an even bigger deal, especially if the classical music community gets their act together with engaging with today’s audiences.
Perhaps most importantly, a quality music education is something that every single kid should have access to. Hell, I’ll go ahead and suggest that every child should get a basic comprehensive education in all the arts. And I don’t just mean you learn to fake your way through recorders in 3rd grade and never touch an instrument again. Basic music theory and keyboarding should be taught to all too (and whatever the visual art equivalent might be; perspective? ¯\_(ツ)_/¯), so everyone can go forward with an arsenal of ways they can interact with art and music.
I realize at this point I’m moving into a somewhat unrealistic idealistic vision of the future that aims to bring back a part of the past that can probably never be brought back. Everyone interacts with and reacts to music very differently. Outside the classical music scene, many prefer music that can be sung or danced to, which I suppose is its own way of participation even though it’s not generally my preferred method. Maybe things aren’t so bad after all? Perhaps I’m only selfishly wishing that others saw music the same way that I did.
But I also truly feel that there’s something absolutely wondrous and life-changing that many people are missing out on.
Art and capitalism have a very dubious relationship.
Art can be defined in countless ways; it’s commonly defined as something created solely to be admired for its own sake outside of any other purpose. This is why some types of music are sometimes not considered “art music”; dance music exists to get people moving their hips. Film music exists to accompany and accentuate a motion picture. They do not (primarily) exist to be studied and contemplated on their own terms.
Commercial pop music also is often not considered art by many afficionados. As opposed to art music, it’s crafted for mass commercial consumption, to reach the widest audience possible. Often it caters to the lowest common denominator, and is therefore, by definition, generic. That said, art can serve dual purposes. IDM, for example, tends to have a great deal of detailed, studiable thought put into it despite its existence primarily as commercial pop music (although it definitely appeals to a slightly more niche audience).
Art, on the other hand, tends to be produced for a smaller audience; it’s created primarily to appeal to the artist’s own sensibilities, and perhaps second to any patrons or performers. Often appealing to these people is very secondary, indeed; some of the most professional performers regularly perform music that they do not enjoy per se. On a personal note, I think this is quite a good thing; it means that composers and artists and their works that may be more challenging can say what they want or even on some level need to say. But even then, on some level, even if the artist is not appealing to someone else’s personal aesthetic, they would then be appealing to their patrons’ personal philosophies.
To say that art is not made for money would not only be a misguided statement, it would be outright false. Beethoven, Michaelangelo, James Joyce; they were all paid (a sizable sum of money) for the art they made. There are, of course, very notable artists who made little-to-no money from what they created: Vincent Van Gogh and Charles Ives to name a couple. But there are also people that create what some might call “art” even though its aesthetic falls more in line with what has generally been considered “pop.” What do we call this? Where do we place it?
Artists want to make a living somehow. Sometimes that living is supplemented by a day job, or by a modern aristocracy (as it was by Charles Ives and Giacinto Scelsi respectively). Some switch modes, sometimes making art that speaks directly from the creator’s heart, while supplementing this with more “commercial” work or “art” produced for some other purpose (eg, concept art, music for films/games). Some don’t worry about the “art” part so much and work more on making something that will appeal to the maximum number of people possible based on the latest trends and fads and sensibilities and general zeitgeist.
Others still try to find a compromise in there, getting as close as possible to their own hearts while trying to touch the hearts of others and put food on their plates. It’s a difficult and exhausting balancing act that few ever manage to pull off effectively, but it can be extremely rewarding when done well.
Art is hard. Excepting perhaps those that produce commercial art solely for maximum appeal and maximum cash, I don’t fault anyone for following whatever path on that spectrum they choose to follow when they’re working on their art. There’s a good deal of elitism in all artforms when it comes to the line (or lack thereof) between pop and art, between the commercial and the personal. Maybe we should worry less about whether it’s really art and worry more about speaking from the heart, making it great, and surviving.
Something similar happened just over a hundred years ago in Paris. The Ballet Russe was preparing its third ballet with the young hotshot Russian composer Igor Stravinsky, collaborating with one of the most respected dancers in ballet, Vaslav Nijinsky. The title of the ballet, Le Sacre du Printemps (The Rite of Spring) implied something beautiful, lush, and sonorous. The aristocratic art crowd of Paris likely expected a beautiful, rather traditional dance piece with clear, easy to follow music. What they got was something quite different.
The opening bassoon solo calls out in its as-of-then-unheard extreme high register. Guffaws of disgust at the sound echoed through the hall. The orchestration built up into a cacophonous mess, landing at last on an awkward, unresolved chord… which it repeated over and over again for the next few minutes. Around this time, the curtain rose, and instead of the beautiful, smooth lines of traditional ballet, the dancers were stilted, bent at awkward angles, and jumping around in the most unnatural ways. While reports of rioting that followed have been exaggerated ever since, to say that people got upset would be a drastic understatement.
Now, imagine both of these situations again, but instead, the audiences approached the events without any expectations. My friend arrived at his venue not expecting any jazz, just ready to take in whatever interesting sounds he heard. Paris ballet attendees were prepared for whatever sights and sounds might have assaulted their senses. Perhaps a “riot” would not have broken out. Perhaps I wouldn’t be here 100 years later constantly hearing people ask me why composers write such weird music (okay, maybe that conversation is for another article…).
Our perception of music (and of just about everything else, I’m sure) is shaped by our expectations. We can, however, with practice and the right mindset, train our minds to break away from expectations. Those familiar with Zen Buddhism might refer to this concept as “beginner’s mind”; approaching every experience as if for the first time, free of any preconceived storyline about the experience that might affect our perception and, hence, enjoyment of it. Expectations can certainly be useful at times (it’s nice to know that when you go to a Pixar movie, you aren’t seeing a terrifying horror film (unless you were unfortunate enough to be seeing it at this Ohio theater…)), but we’re more apt to see things in interesting, exciting, and (by definition) unexpected ways without them.
Take this example (see #3); a group of kids were played music by Edgard Varese, a noisy avant-gardist, and Maurice Ravel, a “friendly” post-romantic impressionist. I’d be willing to bet that most adult audiences would enjoy the Ravel more (in fact my brother-in-law asked me once why I was listening to a bunch noise one time I happened to be listening to Varese in his presence), but the kids actually preferred the Varese. It had more energy and crazy sounds. Free of expectations, that childlike energy stands out from the what-some-might-consider-“real” music of Ravel.
Another example: a group of Mbenzele Pygmies living in the Democratic Republic of Congo were played the theme to Psycho. Whereas most folks in the USA and Europe would identify the music as being tense and even off-putting, the Mbenzele didn’t hear it that way. They thought it was all fun, happy, playful even. Because that’s what music is to them. That’s what their expectations guide them to, whereas in most of modern Western culture, the sounds of Varese and other “modern” composers are associated with unpleasant industrial noise or horror movies.
Being a modern composer who enjoys writing and listening to strange music every now and then, I’m a bit biased on this subject, as I can directly benefit from audiences shedding a bit of their expectations. Subverting expectations can create interesting juxtapositions (see the now-somewhat-tired “sad music + violent scene” trope in action films and whatnot), but I don’t expect every movie and game score I work on to be experienced by an audience lacking any shred of expectation. However, I think as audiences we can all benefit from clearing away as many preconceived notions as possible when going into any new piece of art, or even any new experience in life; we’re far more likely to enjoy ourselves that way.
I’m not certain how common this specific mindset is, but I have a hunch that I’m not the only one who’s gone through it. Being a pretty typically angsty teenager, I had a fair amount of disdain for people that exuded false confidence, that talked themselves up or lied about how much they knew or just had an overwhelming sense of pride. So I told myself I would never become one of those people, for better or worse. I took that idea so far that when those periods of self-doubt and low self-esteem took hold, I would tell myself, “No, this is okay; this is good; at least you’re not an arrogant jerk.”
As you might imagine, this made a lot of activities pretty difficult, particularly in music. Although I would allow myself to believe in myself at times (and my family and friends and other supporters certainly helped), I’d frequently try to put myself in my place, telling myself that I wasn’t allowed to try something for which I wasn’t entirely prepared (like preparing an audition for Western’s music program as a freshman (as you’re, essentially, supposed to do), or even applying to other schools, or trying to make friends and act like a normal human being), or I didn’t deserve to be proud of myself when I accomplished something. This tendency lasted years, well into my time at university.
Last year I began meditating regularly, and this month I’ve been doing a series of meditations on self-esteem as part of Headspace (…I swear that’s less hippy-dippy than it might sound). During one of their podcasts on the topic, founder Andy Puddicombe mentioned something that I hadn’t realized before: low self-esteem and inflated sense of pride are both just forms of self-talk. With both, we’re focusing all our energy inwards and artificially creating a false environment. Neither attitudes are particularly sustainable, healthy, or helpful to ourselves or to others. The good news is, we don’t have to choose one or the other. We can catch ourselves getting caught up in this chatter in our heads, note it, and simply let it go and return to whatever we’re doing.
I wonder now what I could have accomplished had I realized this earlier; maybe I would have applied myself in high school a little bit more, rather than wallowing in self-hate? Maybe I would have applied for more scholarships and wouldn’t have had to take out an ungodly sum of loans and ? Maybe I would be a hot-shot AAA game composer already, totally comfortable talking to people? Really, none of that matters, because I’m here now, I’m working on getting better, and I’m content with that.
Moments of both low self-esteem and egotism happen with most people, and that’s okay; it’s part of being human. But we can notice these moments and not just let ourselves get caught up in them. Self-esteem isn’t simply a one-dimensional line; we can step away and back from the line of self-talk and watch the chatter come and go without letting it affect what we do. We don’t have to worry about a false spectrum between self-hatred and self-inflation. We can just be us, do our work, and watch what happens.
I write music. Some of it is more electronic, some of it is written for instruments, much of it is a combination of the two. Some of it is light-hearted and off-kilter, some of it dark and immersive. It kinda runs the gamut, for better or worse.
Genre can be really useful for finding music or describing it in a couple of words. If you like atmospheric, melodic, hazy, beat-driven electronic music, then sure, go ahead and Google “chillwave” and you’re likely to find some stuff that’s up your alley.
But one thing that’s been driving me crazy in the world of hopeful-game-and-film-composers is when someone says “here’s an epic orchestral track” or “here’s my latest chiptune” track. And it happens all the time. It’s like they have a checklist of the most trope-y kinds of portfolio tracks.
The problem with this is these genre descriptors don’t tell anyone else anything about what makes your music unique, and even worse, could tell me that your music is very generic. If I a game developer, I would be a bit turned off when I saw those kinds of broad, generic descriptors.
Let’s look at a practical example: the music of Disasterpeace, one of my favorite video game musicians in recent years, makes music that might be described as “chiptune” at times. But to call the Fez soundtrack a “chiptune” soundtrack is to do it a grave disservice; it’s atmospheric, seemingly organic yet synthetic and bitcrushed, entrancing, emotive, at times minimalist, even groovy at a couple of moments. There’s so many words you can use to describe the soundtrack without even saying the words “chip” or “tunes.”
It’s even worse when someone describes the kind of music they write using just one genre. “I write epic orchestral music” is kind of cringe-worthy. I mean, yes, famous, successful, fantastic composers like Jason Graves often write “epic orchestral” music (but it also always sounds undeniably like Jason Graves). But that’s kind of the point; anyone can (try to) write music that fits the “epic orchestral” tag (to varying degrees of success). But what makes your music special when compared to the other “epic orchestral” stuff out there?
I’d love to see more composers, developers, and directors describe their music using more specific, emotive descriptors and palette. I somewhat recently had a great experience working with film director John Portanova on Valley of the Sasquatch; he was fantastic at talking about the kind of music he was looking for. “A strong, memorable theme,” “a sense of scale and adventure.”2 He talked about the kinds of instruments he wanted to hear (and didn’t want). It gave me a vivid picture of the kind of movie he was making, and made it much easier to craft a score that fit his vision.
If the music you’re writing is generic, then maybe it’s time to take a look at the way you write and think about what you’re trying to accomplish. Instead of writing a generic Inception ripoff, think about what you want your music to accomplish (maybe convey scale, pounding-heart intensity and action), and think about some other ways you can accomplish those goals.
I certainly don’t mean to insult anyone that’s done this kind of thing; in fact, I’m all but certain I have at some point. But I think using more specific and individualized descriptions to talk about our music would help all of us composers stand out a little bit, show what we all have to offer as individuals, and say exactly what we want to say with our music, rather than getting lost in a crowd of “chiptunes” and “epic cinematic symphonic”-ness.3
I mean, I guess you could consider “electronic” a genre, but it’s kind of uselessly broad. ↩
I’m paraphrasing a bit, but I do remember he used at least most of those words! ↩
James Bordner wrote a very helpful couple of great, quick articles on Gamasutra about how non-musicians can talk about music in more helpful terms. I highly recommend them! ↩
I do have some mixed feelings on the music, though, and if you know me, you can probably predict at least partially why.
I’m sure you’ve heard it. Quick, staccato string ostinato. Impossibly huge drums. Glitchy electronic percussion. And most important of all, huge honking low brass sound (usually backed up by some digital synths).
This is the sound made (in)famous by Hans Zimmer and copied by most Hollywood composers working today.
I don’t blame any composer for writing in this style; this is the sound that so many directors ask for nowadays. Fortunately, usually there’s some unique bits that are grafted onto this sound, be it a big sweeping horn melody (…okay maybe not that unique), distorted cello in The Dark Knight…
But I’ve gotta be honest: it gets a little bit old. I can only hear so many fast string lines and low brass “BWOOOOMP”’s and big percussion-section “DOONNN! DOON DOONNN!!”’s before it starts to grate a little bit and become self-parody.
(Note: This is where I might usually post an example track, but I’d rather not risk insulting any fellow composers, because again, I don’t blame any specific person for this sound becoming so ubiquitous. Except maybe Hans Zimmer.)
Mad Max: Fury Road buys into this sound whole-heartedly. It’s got all the makings for a cliché-ridden mid-2010s Hollywood film score.
And yet somehow, it works… and to be honest, I kinda like it. I haven’t entirely worked out why, but I’ve got some ideas.
I’ve spoken about this concept before, but Fury Road does an impeccable job at blurring the line between diegetic or source sound (that is, sound that emanates from within the world itself; e.g., car sounds, gun shots, dialogue) and score. One aspect in particular stands out, though. Yep, it’s everyone’s favorite onesie-wearing flamethrower-guitarist.
THE DOOF WARRIOR.
This absurd monstrosity, along with a legion of drummers on the truck’s back, acts as a sort of demented, post-apocalyptic war cry throughout the film. And their sound blends seemlessly into the score, to the point where, most of the time, it’s difficult to determine what’s supposed to be being played by on-screen characters and what’s supposed to be “score.” This feeling is enhanced by filtering and stereo/surround effects in the theater, where the sound actually follows where the performers are visually, especially with regards to the guitar.
(see 2:14 for what I’m talking about with the guitar)
On top of this more obvious blurring of the line between reality and mind (also enhanced by Max’s hallucinations throughout the film), between diegesis and score, is the fact that Junkie XL utilized tons of sound design elements in his score, from growling animals to car engines to just hitting pieces of metal.
This performance adds to the general impression of a fever dream that pervades the entire film, of heightened reality and insanity. It’s definitely a fitting piece of the puzzle of what makes this movie so damn effective.
This might sound a little melodramatic, but I think it’s also just logical that the music in this film is what, if the world were to end next week, post-apocalyptic warlords would want their war-parties to be playing, having heard them in all the latest Hollywood action flicks and EDM from the world before the apocalypse. After all, the aforementioned “Doof Warrior” is apparently named after an Australian slang term for a desert rave. George Miller has gone on record saying that chronology in his universe is fluid; every Mad Max movie takes place a couple decades after the apocalypse that happens next week.
So big honking drums and pieces of metal and blaring distorted guitars and even the weird synths that come in when the canyon motorcycle gang enters the picture just kinda works in this world. It’s insane and stupid, but that’s what Max’s Australian wasteland demands.
I get down on ear candy fairly frequently. By ear candy I mean music and sound that sounds cool and is usually really well-produced and effective, but also full of clichés or just very surface-level without much depth. I tend to prefer my music to have a lot of depth.
But I’ve been realizing lately that, most of the time, film scores with too much depth aren’t what movies need, at least not in the same way that stand-alone pieces of music can have. And in a film like Mad Max, where there’s already a lot to take in at once sensorially, it helps that the music is in-your-face, demanding, and relatively transparent. It’s not difficult to figure out what it’s trying to say. It’s pure unadulterated insanity most of the time, and when it gets quieter, it gets more Mahler-ian, which, again, the audience can understand, and from interviews with Junkie XL, it seems that’s exactly what director George Miller wanted. It also helps that the score is extremely well-produced and just sounds amazing for what it is. I’ve listened to it several times over the past few days just because I love the sound of the production, it gets my heart pumping, it’s enjoyable at a surface level. And even though the movie is very artfully done, at its core it’s still entertainment.
I mean, dang, some of the drum fills in the climactic track “Chapter Doof” sound straight out of a Phil Collins song.
Part of me does wish it was a little bit weirder and more subtle and less cliché at points, sure, and after several listens it starts to lose its effect a bit (but really few pieces can break through that tendency). I think in twenty years the score might be the most dated aspect of the film. But despite all that, as a film score of its time, I think it’s very effective.
Since deciding to become a composer my sophomore year of college, back in 2010, composing consistently has been a pretty constant struggle for me. Every break, be it spring, winter, or the several-month summer break (even long weekends), I’d tell myself, “Alright, this break I’m going to compose more than ever!”
Spoiler alert: it never worked.
Breaks are supposed to be times of rest. They’re there to take some time off to be with family and friends, lay off the gas pedal of life a little, and give your head a little space before getting back to the grindstone of work and/or school. And taking breaks is good and necessary! But they’re not so conducive to getting work done. Work and breaks are mutually exclusive.
Even not counting the breaks, trying to write music every day can be an enormous challenge. Traditional goal-setting will tell you to aim for the skies; I’ve heard of folks that have successfully written a piece of music every day for a year (we’ll get back to that one later…). To uber-positive motivational speakers, that’s proof right there: if they can do it, so can you! They say that even if you aim for the moon, you’ll land amongst the stars. But I say it’s also possible you’ll explode before even leaving the atmosphere.
So how do we, as composers, set up a habit of composing every day when the traditional advice doesn’t work? Well, it might seem counter-intuitive at first.
I recently read a book entitled Mini-Habits by Stephen Guise. It’s one of those silly “productivity” books I like to read and write about. This one was a little bit different, though. It didn’t take itself so seriously, it used some good data to back its ideas up, and most importantly, its methods actually work.
The basic tenet is this: if you want to form a habit, start with a stupid-small version of that habit and commit to doing it every day. For me, that stupid-small habit was writing one measure of music every day.
I know what you’re thinking. “One measure of music? How will you ever make progress on your sonata/symphony/thesis/dissertation/film score?! Won’t it sound really disjunct if each measure was written a different day? What good is that?” I asked the same questions myself, but as I read, I realized there might be some substance to this seemingly mad idea.
To make a long story short, committing to just one measure of music makes the barrier to entry much, much easier than setting a huge goal. Let’s do a little thought experiment as an example: imagine your worst day ever. You fall in a puddle of mud on the way to your school/office. Your sandwich has moldy bread. You fail your latest report/exam. Your significant other breaks up with you. You twist your ankle walking home. And to top it all off, the vending machine outside your apartment complex is broken. Also your wifi is down.
Now imagine you’ve still gotta write an entire piano sonatina. Not. Gonna. Happen.
But what if you just had to write one measure of music? Piece. Of. Cake.
Even on your worst day, the stupid-small version is easy to get done. You could be in bed ready to fall asleep and realize you haven’t done it yet, grab some sheet music, compose a simple little 1-measure ditty, and head back to sleep. And committing to doing this every day sets up a routine of taking small action on your goals every day, which strengthens it as a habit, strengthens your willpower, and brings you a little bit closer to finishing your big piece.
Not only that, but it’s so easy, on the good days especially (and even on some of the bad days!), to start by writing one measure of music, and before you know it, that entire transition section that had been giving you so much trouble is finished, and you’ve written closer to 200 measures of music and spent several hours composing.
Personally, whenever I would take a break from composing over a long weekend, or get caught up in other responsibilities and not make the time for writing, it would always take me a lot longer to get back into the piece I was writing than if I had composed even a little the day before. That guy who wrote 365 pieces in a year? I’m betting he had already built up a habit over time of writing a little bit every day before he got up to writing an entire, complete piece of music every day.
So now, I challenge you: commit to writing one measure of music every day. Or, if you don’t write music, whatever other habit you want to develop, start with the smallest possible unit you can finish in roughly a minute. Don’t commit to more than that. Don’t commit to one measure on paper, but in your head imagine you’ll write 30 measures every day. This will only make “failure” that much harder when it inevitably comes. Give this a shot for a month! Chances are good you’ll come out a better, more consistent composer.
I’d like to think I also improved my work ethic significantly. Getting things completed has always been difficult for me, but I think I made significant strides especially since September (partially out of necessity with the intense schedule of writing music for a full-length movie!). I think going forward this will make my work better and more efficient.
There were a fair number of things that didn’t work out this year that I was hoping would, some of which I’m still hoping will eventually see the light of day, but those things happen.
This year will see me finishing my master’s degree (DONE WITH COLLEGE AT LAST… until my [possible] doctorate), hopefully working on more games and films, the premiere of Valley of the Sasquatch, the release of my first solo album in five years, moving into a new home, getting a cat, and much more I’m sure. You can never predict these things, but I’m looking forward to whatever challenges and opportunities come my way.
That said, the game is not without its flaws, and a number of them have to do with pacing.
Hallways sometimes seem to go on a touch too long without much happening. It’s a strange feeling, getting bored by a horror game. Luckily, several times I felt boredom creeping in, something terrifically terrifying would happen and put my ass right back on the edge of my seat where it belonged. Perhaps these moments would not have been as effective without the moments of calm, but I feel that the game would have been more, not less, effective if a few of the rooms or hallways would have been cut.
That’s not to say that all of the “pointless” rooms should be cut. It’s kind of wonderful to be walking down what seems like a cookie-cutter hallway, enter a side-room, and find some new, unexpected, grotesque scene that, while serving no clear ludic function, ramps up the chills significantly and sinisterly.
By the end of the game, though, the player will likely become a little desensitized to blood and guts intended to convey scenes of horror. At the beginning, the bright sight of red blood against the beautifully pencilled black and white is a shock and reminds us that not all is well in this mansion. By the end of the game, there seems to be more red than white at times, and it becomes just another part of the macabre scenery.
Don’t get me wrong, though; throughout the game, the gore is often used to wonderful effect, throwing guts in our face when we least expect it. While I’m not usually a fan of cheap jump-scares, this game does have a few, and they don’t at all feel cheap. In fact, I think each and every one was superbly executed.
The art, overall, is quite nice. The way the seemingly-hand-drawn shadows scribbled their way over the light is a very cool effect and works well in the nightmarish context. The set-pieces and enemies in particular have frightening designs, especially as the game descends deeper into madness.
One enemy, though, just wasn’t particularly scary or fun to deal with. Luckily it doesn’t show up very often, but a few of its behaviors seemed to be a bit out of place with the rest of the games. While another, later enemy hit the sense of adolescent terror just right, this one was more obnoxious and almost silly. Other than the one, though, the enemies overall were extremely effective from both a gameplay and horror perspective.

The sound design and music are quite lovely, if you can call it that… aside from a few background ambience that sounds like someone doing an impression of a tortured Droopy Dog. I appreciated the way the music would evolve over the course of the different nightmares and a “new” nightmare’s beginning would be indicated by a shift in the music as much as the narrative.
The endings (the two of three that I’ve played, at least), while narratively intriguing in their own rights, seem to happen just a tad too quickly and feel like they lack the weight I think they deserve. With this kind of short game I don’t feel necessarily like closure is what’s needed, but the credits seemed to start rolling quite quickly and suddenly, as if someone came into a movie theatre after the last words had been spoken and hurried the audience out so they could make room for the next group or something. That said, I like that the endings don’t necessarily agree with each other. They all come from the same background, but come to different conclusions of varying depth that indicate what’s really going on here, and I think that was a smart and cool decision.
Overall, there’s a lot to praise in this game. It’s at its best when it embraces nightmarish weirdness to try to throw the player off track, changing placement of rooms and playing with expectations. The lead developer, Matt Gilgenbach, has referenced Metal Gear Solid and Silent Hill as influences, and in this way they both shine through, but without becoming overbearing. If there’s an ultimate takeaway, it should be that this game is a unique experience that I highly recommend. Also, supporting this game means supporting future titles from the developer, and personally I’m very excited to see what Gilgenbach and Infinitap does next.
What did you think of the game? Let me know in the comments below.
A simple meditation timer that gets out of the way
Papanca is a meditation timer designed to support your practice without getting in the way. No accounts, no subscriptions, no distractions — just a simple, focused tool for sitting.
Most meditation apps want to guide you, track you, or sell you something. Papanca takes a different approach: it gives you the tools you need for self-guided practice and then steps aside.
Set a duration or meditate open-ended. Add interval bells if you want gentle reminders. Save presets for your regular sits. That's it.
Papanca stores all data locally on your device. There are no accounts, no analytics, and no tracking. Your practice is your own.
Read the full privacy policy →



Last updated: January 31, 2026
Papanca stores all data locally on your device. We don't collect, transmit, or sell your personal information. There are no accounts, no analytics, and no tracking.
Stored on your device only:
This data never leaves your device unless you explicitly choose to back up your device through iCloud or iTunes.
Papanca can optionally write Mindful Minutes to Apple Health. This feature:
We have no access to your HealthKit data. It stays on your device and in your iCloud Health data (if you have that enabled with Apple).
Papanca contains no:
We don't share data with anyone because we don't have access to it.
Papanca does not collect personal information from anyone, including children under 13.
If this policy changes, we'll update the "Last updated" date above. Significant changes will be noted in app update release notes.
Questions about this privacy policy:
Complex app ecosystem for factory management across iOS, iPadOS, and AppleTV platforms
Redzone is a complex app ecosystem used for management, productivity, and several other facets of work in factories. I've worked on many parts of it across platforms (namely iOS, iPadOS, and AppleTV), from UI to networking to architecture to dependency management to algorithms and developer tools.



A command-line memento mori that displays progress through time periods as visual progress bars
WhatProgress is a command-line tool that helps you visualize the passage of time. Inspired by the memento mori tradition, it displays progress bars showing how far along you are through various time periods—today, this week, this month, this year, or your entire life.
Run whatprogress -p life -b 1990-01-15 to see a sobering (or motivating) view of where you stand. It also supports custom ranges for tracking any numeric progress.
A Swift 6 library for managing concurrent async tasks by key with configurable duplicate handling
Swift Task Store provides a clean API for managing unstructured async tasks in Swift applications. Store tasks by key, query their running state, and configure what happens when you add a task for a key that's already running—cancel the old one, ignore the new one, or let them run in parallel.
I wrote a three-part series exploring the problems this library solves and how it works under the hood.
A collection of functional programming utilities and ergonomic extensions for Swift 6
Swift Fun is a grab-bag of micro-libraries I kept rewriting across projects. It includes an Either sum type with full non-copyable support, a SequenceBuilder for declarative collection construction, extensions to the standard library, and various async utilities.
Each module is independent—import only what you need.
Personal website migrated from Jekyll to Zola with Apollo theme
I made this very website you're now reading! This was originally built with Jekyll, but has been completely migrated to Zola with the Apollo theme. The migration involved converting 67 blog posts from YAML to TOML frontmatter, migrating all assets, and enhancing the site structure with better navigation and taxonomy. The site is now faster, more maintainable, and features improved content organization.
Matrix-style digital rain effect for the terminal, written in Swift
Swift Digital Rain renders the iconic Matrix "digital rain" effect directly in your terminal. It's a fun little project that demonstrates terminal graphics with ANSI escape sequences in Swift.
Install it as rain and run it whenever you need to feel like a hacker.
Model Context Protocol server for tarot card readings, providing reliable randomness for AI assistants
Swift Tarot MCP is a Model Context Protocol server that enables AI assistants and other programs to perform tarot card readings with reliable randomness. Built to address the problem of poor randomness in Large Language Models, this server provides proper random number generation for personal tarot readings.
This project evolved from my earlier WhatTarotCLI tool, expanding the concept into a full MCP server. It supports both single card draws and multi-card spreads up to the full 78 cards of the traditional Rider-Waite-Smith tarot deck. It includes comprehensive tests and robust input validation, making it a reliable tool for integrating tarot functionality into AI workflows.
My first iOS app, a simple countdown tracker (no longer available)
Note: This app is no longer available on the App Store.
My first "real" iOS app, Super Countdown Tracker began life as a Lambda School build week project, and I liked it so much I decided to polish it up and put it on the App Store. It was a simple, lightweight app that did one thing well—telling you how much time was left until the events you added.





An instrumental track with ambient, post-rock, and game soundtrack influences.
Single featuring bass, vocals, and other contributions.
A single where I provided bass, vocals, and other musical contributions. This track represents part of my collaborative work with Jostaberry during several months of live and studio performances.
EP featuring bass, vocals, and other contributions.
An EP compiled from live singles released throughout the previous year, where I contributed bass, vocals, and various other elements both in live performances and studio recordings. Working with Jostaberry over several months was an enriching collaborative experience that explored new musical territories.
Covers of "Here Comes a Thought" from Steven Universe and "How to Disappear Completely" by Radiohead.
These covers were a fun exploration of two very different but equally meaningful songs. "Here Comes a Thought" from Steven Universe captures the beautiful complexity of mindfulness and emotional awareness, while Radiohead's "How to Disappear Completely" explores themes of dissociation and escape.
Read more about the project in this blog post.
Instrumental electronic music for a non-existent game.
Electronic rock-pop album with John Von Volkli.
Electronic rock-pop album with John Von Volkli.
A split EP of battle themes with Megan Carnes.
Eclectic electronic/rock/pop/folk music with vocals.
Electroacoustic music that tends towards the strange and unsavory.
A folk cover for charity.

an unfinished, unreleased side-scrolling action platformer

a Gameboy-themed demake of Naughty Dog's Uncharted

a rhythm-based rail-flyer

a (sadly unfinished) exploration/puzzle-based Gameboy-styled platformer made for Gameboy Jam ’14

a multi-dimensional puzzle platformer made for Global Game Jam ’14

an endless runner made for Global Game Jam '15

a mini-submarine exploration game
voted 1st place in Bacon Game Jam 9
A group of high school friends reunite on a camping trip full of fun, stories, marshmallows, and dismemberment.
Batman and Spiderman are in Bellingham, WA, and they may have bitten off more than they can chew...
A fractured family battles a tribe of angry Sasquatch.
(formerly known as Valley of the Sasquatch)
Follow the adventures of dorky Frank as he tries to survive the onslaught of pranks from his coworkers throughout the day.
A stand-up comedian on the verge of breakout success must make a terrible choice when he discovers a murderer on the loose in the theater where he's about to perform his biggest show.
This was the first piece I completed for WWU’s composition program after my acceptance. While I’d rather forget most of the rest of my pieces from that year, I’m still rather proud of this one, and it has continued to see many performances after its premiere, from its use in a dance choreography to a flute master-class focused on contemporary techniques and repertoire.
“Nocturne” comes from the same Latin word, nocturnus, as the word nocturnal, referring most commonly to creatures whose activities are limited primarily to the night. The nocturne is a musical work made perhaps most famous by Romantic piano composers such as Frédéric Chopin, who wrote 21 of them. The pieces are generally solemn and tranquil, often gloomy. This piece takes this contemplative approach and intersperses it with hints of dreamy terror and triumph.
I pledge allegiance to my Flag and the Republic for which it stands, one nation indivisible, with liberty and justice for all.
Thus read the original text written by Francis Bellamy, a Christian socialist and Baptist minister in 1892. The text has since gone through several transformations to reach its modern form, including the most famous addition of the phrase “under God,” which began to see usage in the late 40s and early 50s.
The current text of the Pledge, and the version you (more or less) hear here, reads as such:
I pledge allegiance to the Flag of the United States of America, and to the Republic for which it stands, one Nation under God, indivisible, with liberty and justice for all.
“Via lactea” was the original Latin term for our galaxy and the origin of the term “Milky Way.” Of course, when the galaxy was given this title it was unclear exactly what that band of light in the night sky was. This music touches upon many of my own feelings about the cosmos: quiet, personal, contemplative awe; wondrous, excited discovery; precise scientific calculations; the horror of our short-lived mortality; and my own hopeful (but often skeptical) belief in the triumph of the human spirit on this small speck of dust in the enormous, magnificent galaxy we call home.
Night Sketches is a set of five miniatures for solo piano. Each short piece varies greatly stylistically, but they are joined conceptually by their portrayal of particular night time scenes. Specific images are left to the imagination of the audience, in favor of a more impressionistic interpretation.
In Spring 2014, Matt Birmingham of the Equus Sax Quartet approached me about writing a piece for the ensemble, and I jumped at the opportunity without hesitation. I sought to write something that would play to the players’ specific strengths & abilities and of the unique timbral qualities & techniques of the saxophone. A special thanks is owed to Soren, Matt, David H., and David D., who played through many drafts and helped guide the direction of the piece.
An oratorio has historically been essentially a (usually) unstaged opera on a religious narrative. While this work certainly doesn’t contain any vocalists or specific biblical story, with the music and the titles I hoped to evoke feelings of sacredness and spiritual contemplation that one might experience with a traditional oratorio. The first movement depicts the internal workings of the mind during meditation, especially that of an inexperienced meditator; resistant, distracted, and trying, but still working to come to terms with the idea of stillness. The second movement depicts a spastic outburst of the body and mind, as pent-up aggression and negativity boil over and explode outwards before finally dying down into the third movement, a series of solemn, inquisitive recitatives and chorales, perhaps sermons or eulogies. The final movement is a freeform folk dance, having come to terms with (or at least accepted the lack of an answer to) the questions posed by the previous movements. I heartily encourage any more or less specific or alternative imagery that your ears might impose while you listen.
Evocations was an excessively fun piece to work on. Silas first approached me in Spring 2015 to write a piece for his senior recital the following year. In June he and David and I got together at Western Washington University’s band room with a field recorder and a whole bunch of percussion. I hit record and told them to make some noise, taking notes on sax multiphonics and extended techniques, various percussion effects, and combinations thereof. I spent the summer writing this piece based on those notes and the inspiration that came from that session.
As the piece began to take shape, it quickly became all about the rhythmic and spectral contrapuntal interplay between the saxophone and the percussion. As usual, Evocations doesn’t follow any sort of cohesive narrative arc, but when pressed to come up with something by David and Silas, I imagined an innocent child wandering through the forest, accidentally summoning monsters as he went, and… well, as the piece continuously ramps up in intensity to its pounding conclusion, let’s just say it doesn’t end well for the child.
A huge thanks goes out to Silas and David for asking me to write this piece for them, and also for pulling off the challenging licks I wrote with ease; I half-expected them to throw the music in my face and ask me to re-write it, but they’re beasts. Cheers.
Today technology aims to improve and extend our lives, but nothing so far has been able to suspend our inevitable deaths. We all try to leave our marks, but it seems that few of us succeed. Out of the billions of humans that have ever lived on this earth, perhaps a handful are remembered at all. The network of computer servers that make up the Internet have made sharing our stories easier, but we’ve become overwhelmed by the sheer number of stories out there. Furthermore, these servers won’t last forever. Someday all of our stories will disappear. Humans have invented this miraculous technology that can hold astronomically large amounts of data, but it still is finite; it still will someday die. If you look hard enough, though, there is beauty to be found in the dissonance of our short existence in this vast universe.
Machinations is an indirect musical reflection on these thoughts. Our stories build up more and more, fragmenting throughout the web of information and machines until finally, it comes to a breaking point, and all we have are the stories of old, themselves fragmented, pleading to be heard, to have some place in the short history of the human race, and ultimately quietly themselves to their sad, beautiful fates.
The finale of this piece contains a musical quote from the closing of “Occhi del mio cor vita” from Carlo Gesualdo’s fifth book of madrigals (1566). The text pleads, “mirate almen ch’io moro” (“at least see me dying”). Although the madrigal is more about unrequited love, the line’s sentiments mirror those of this piece.
In 2015, Emily Pontén asked me to write a piece for her senior recital, leaving lots of the specifics up to me. Based on a couple of chance conversations and brainstorming, I decided to include her partner Silas and his instrument of choice (percussion), piano, and Myriaha Maxwell & her oboe. It just so happened that Myriaha’s partner, Ben Seavello, happened to be a pianist, so the pair of couples turned out to be a great fit for the piece, and the title (and some moments in the music) essentially wrote itself.
This is probably one of the more “traditionally” tonal concert works I’ve written in recent years. As the name implies, it is written in a loose, short sonata form. The exposition’s themes are lifted from a one-minute piano solo I wrote for Hayk Arsenyan and his 15 Minutes of Fame concert entitled “A Short, Lonely Dance.” The expansion of the music and its instrumentation has, I think, transformed it into something sweeter, less lonely, and more romantic.
The genesis for Specimens came through a serendipitous Twitter connection, the details of which I no longer recall, but the end result is that I ended up responding to Rachel Hacker's open call for new pieces for Kingma System® flute, optionally with glissando headjoint. This opportunity was a dream come true, and I leapt at it with great enthusiasm.
The following months (late 2016 leading into early 2017) turned out to be far more tumultuous than I anticipated— personally, politically, and otherwise. Attempting to force the music out was not bearing any satisfying fruit. With a missed deadline behind me, the piece was finally eventually written in an outpouring of creative energy to finally reveal what the piece wanted and needed to be: four eclectic little cybernetic beasts for flute and electronics.
Despite making extensive use of the microtonal capabilities of the Kingma flute and glissando headjoint, the harmonic language of the suite is largely tonally derived, but embodied in a very loose fashion. I treated the composition as essentially a trio between the flute, percussive electronic sounds, and tonal electronic sounds, each of which takes the forefront at various points, but with the flautist clearly acting as a bandleader of sorts.
I'm now principal iOS engineer at my place of work. Woohoo!
Denny the dog is going to be doing a train and board program soon. He really hates getting into kennels; very anxious lil' boy. Hopefully this will help, and we can all have an easier and chiller and more sustainable time. ❤️
I've fallen a bit off the bandwagon again with side-projects, but I'm not worrying about it. Trying to focus on just living well.
I wrote a short story. I'm happy with it! I hope folks enjoy reading it. I also hope I write more. It's liberating to just be able to type words, something I'm pretty good at doing, and that be my act of creation, rather than having to deal with compilers or DAWs or musical instruments or sheet music or pencils or anything else. It's the ultimate obliteration of any barrier to entry. I love it.
I FINALLY released my meditation timer app Papanca. This has been years in the making (mostly starting over and starting over again). I should have a new version coming out soon with some enhancements.
The Spring Equinox has passed and the days are getting longer. Something I am very happy about.
I recently had to have some work done on my house to clean up some mold in the attic. More to be done to improve ventilation.
Denny was sick for almost two months; started out as kennel cough, turned into lingering bronchitis that just would not go away. He's finally fully mended, we think, and so happy to be able to hang out with his fluffy buddies again.
Just been chilling. Trying to live. Trying not to despair at the state of the world. Trying to do what good I can. Trying to do and be better. Trying to take care of my 3 furry daughters and 2 furry sons and hang out with my fiancée.
But also! I'm actually working on music. Something will be out by the end of the month. Mark my words.
Also, I've released some open source code! Finally! A couple of Swift libraries. Find them on Github if you're so inclined.
I'm also closer than ever to releasing the first app I've built solo in years. It's a meditation timer.
I bought a white noise machine and it's glorious. I put my phone across the room. I'm reading books on my Kindle. I should have done this so long ago.
I'm trying to make this a year of substantiality. Spending my time on substantial things that have substance. Not idling my days away. Reading books and spending less time on my phone. In a way that's actually sustainable. I think I'm doing pretty well so far! Definitely better than I have other times I've tried similar things. Also bullet journaling. Highly recommend all of these.
Tori and I adopted two kittens. I went to PetSmart to pick up some supplies, and walked by the cat adoption window... and gosh, they were just so cute. Their names are Daria and Sylvie. I was planning to adopt just one of them... but looking online, there's just such a high need, with the local humane society being overwhelmed. We have the ability to care for them. So they're getting cozy and living their best lives, biting my nose and kneecap and running all over me while I try to sleep. I love them dearly. Check out my Bluesky for some photos.
I started writing poetry. I needed a creative outlet that was easy, some words I could throw on the page in 10 minutes and call it good. It's been very fun and freeing. I'm not worried about it being "good". I don't even really know what makes a poem good! I have a rough idea, just from having studied music; certain things carry over in different creative realms, especially with the kind of creativity I tend towards. More than anything else, just getting back in touch with that sense of creative play has been lovely. I don't have to worry about words making sense together according to "laws". It's just fiddling around and having fun.
Remember when I said I thought I was nearing completion with one of my side projects? Yeah scratch that!
I'm back on the Omnifocus train. A recent version added "planned dates" on top of due dates and defer dates. A minor game changer I thought they'd never introduce, honestly. I'm still holding out hopes for explicit task dependencies; now I feel like maybe it could actually happen!
Oh also thinking fascism is bad is terrorism now.
Look at me writing another update after only two months!
Denny is now just about a year old. We got him when he was about 10 weeks, and we're not really certain of his birthday; probably makes more sense to celebrate his "gotcha" day than his birthday. He loves going to the dog park & Paws for a Beer (the local dog bar), meeting new friends (humans and animals), chewing on sticks, wrestling, tug-o-war, running in circles around the yard, and stealing things and running away from us. He hates getting baths, having the things he stole taken away, going on a walk without stopping to smell everything, and not being the center of attention.
By the way, did you know that "bully sticks" are BULL DICKS?! Because I didn't until recently. What kind of euphemism is "pizzle"?!
The honeymoon era with AI coding tools is over for now. I still use it most days, but I'm done trying to coax it into doing things when it'd be faster to do myself. For explanations, research, parallel tasks, boilerplate, debugging, and some other tasks it's great. For targeted work, it's honestly best to just do it with my own fingies.
Continuing the Rob Burbea imaginal/soulmaking dharma rabbit-hole. I might write about it at some point, but I haven't really gotten enough of a chance to actually practice with it, and I'd like to do that before I talk out of my butt about it.
But now that I think more about it... I think I do want to write a post about Rob at some point. I never met him, but to say the effect he has had on my life is profound would be an enormous understatement. I'll leave more for another day...
Oh yeah, I got engaged! Tori and I will likely be getting married sometime next year.
I'm nearing completion of one of my side project apps. I think. I've thought this before. I'll write more when I feel like a public beta is a good idea.
It has once again been two years since my last update. Oof, what has happened in the past two years?
I have a dog now! His name is Denny (from both Grey's Anatomy and The Room). He is a lot, but I love him. I haven't had a dog since I was 4 years old. I used to be afraid of dogs. It's been an enormous learning experience, but he's my best buddy now. He's almost 11 months old! The cats are learning to love him. They want to be friends with each other and they don't really know how.
I'm now an engineering manager at Redzone. It was really hard at first, but I finally feel like I've somewhat gotten the hang of it and am enjoying it.
Lately, I've been playing around with dictation software. I always kind of poo-pooed it for anyone other than those who really needed it, but honestly it's been pretty cool and fun. I'm also using Claude Code a lot, and it helped me a ton with this website revamp. I've been really happy with the results. It obviously has its limitations, but as long as you're aware of those, I can't recommend it enough.
I've been listening to a lot of Rob Burbea talks. Right now, I'm going through his series on Sila and Soul. It's a really interesting and inspiring set of talks.
Well look at that, it's been almost two years since I updated this thing!
Still have my day job and various side-projects that have barely moved, and honestly, don't count on any of them moving! It's just not where my priorities are.
After almost two years of barely meditating, I'm finally getting back into that. It's been refreshing!
A little over a year ago my cat Cougar died. It was really hard. Shortly thereafter I got two other cats, Vincent and Hazel. They're a handful!
A lot more has obviously happened over the past two years, but I don't really feel the need to share any of it here. If you're really curious, you can find me on social media or email me or something.
Not a ton has changed since the last update. Still working at Redzone. Still working on some side-projects. There's been a bit more movement on at least one of them.
I've been playing Nier Replicant. It's fantastic. I played the first hour or two of the original when it first came out, but for some reason never finished it. I seem to remember it being a little bit janky, but this new version at least feels quite polished.
The weather is getting sunnier! Enough for me to get a pretty gnarly sunburn on my legs (after a lifetime of not wearing shorts it was easy to forget just how prone to sunburns I am). My hair's getting longer (it's been probably a year and a half since it got cut).
I've been feeling the pull of doing a meditation retreat. I tried to do an at-home one once or twice, but... it's difficult to maintain "retreat mode" in the midst of the familiar comforts of home. I may need to rent an AirBnB if I'm not able to make it to Cloud Mountain sometime soon. We'll see!
Finally updating this website to reflect my new name!
So as I mentioned on a Twitter thread recently, I'm transgender. Figuring this out has been a strange journey, and it still hasn't fully settled in. I feel like I decided to "come out" with this earlier than many folks in this situation do, but I was really aching to get it over with. I'm glad I did. I've gotten a ton of support, and I'm very grateful for all of the loving friends that I have.
I've now been at Redzone Software for about a month, and I've been loving it. It's been challenging (in a good way), I like my coworkers a lot, and I feel like I'm doing great work. The tech norm of jumping from company to company every year or two never appealed to me, and it seems like many folks on the team are of the same mindset. I hope I can stick around for quite awhile!
Side projects continue to move slowly but surely. I'm not sure I should even continue to bring them up until there's something to show.
I've been watching tons and tons of [PointFree][http://pointfree.co] videos. I believe I've now watched every video they have at least twice, some more than that. I've also been playing a lot with their Composable Architecture, and I have to say I really love working with it. It's very strange conceptually coming from the standard MVC way of building iOS apps, but... well, you can check out the repo and their videos to hear all about its benefits.
Last night Elie and I watched Return to Oz. What a weird movie.
I recently replayed Undertale and Deltarune. I really love those games. Although the former's combat system can be really enraging at times. Deltarune really improves on it a ton.
I've also been playing way too much isowords and Mini Motorways.
I want to play through another game, but I'm not sure what...
Things have been pretty great.
This week I began working as an iOS Developer at Redzone Software. They make a suite of software suite for managing production at factories. All of my new coworkers have been incredibly welcoming. I have a lot to learn, but I'm really excited to be working there.
The USA finally has a president of whom I'm not completely and utterly ashamed.
I've been slowly making my way through The Last of Us pt. 2 again on Grounded Mode. Gotta get that trophy. Also slowly working through the hilarious and mind-bending Baba is You. Considering starting Stardew Valley, which for some reason I never got into (I think the warnings from friends of its addictive qualities steered me away...).
Also, not changing this formally anywhere just yet (well, except Github), but I'm changing my name to June and using they/them pronouns now. More on that later.
It's been awhile since I last wrote an update here.
I'm currently working as a contractor at a company that specializes in vehicle recognition software. It's really interesting technology, I have a lot of autonomy in my work, and I really like the folks that I work with day to day. Naturally, given the events of the past year with regards to police behavior, I had some questions about the ethics of the software when I started. I still have some concerns, but I've been happy to see that others there share some of my concerns and have been guiding the products in what I believe to be an ethically-sound direction. That's about as much as I'm able to discuss here at the moment!
I've been picking away at some side-projects slowly. I don't think I'll be talking about them publicly until they're ready for release, as I don't want to say I'm working on something that never sees the light of day.
I recently did part of a meditation retreat. It came at a difficult time (new job, malfunctioning sump pump, some other stressful situations), but I'm glad I gave it a shot. I don't think I'll try again to do the half-retreat-half-work thing again anytime soon. I mostly just ended up feeling sort of crazy and ineffective at both activities. If I do another "remote retreat," I think I'll rent an AirBnb or something and let myself completely check out.
This was the first Thanksgiving I've ever had without my parents and sister. What a bizarre year. We made deep dish pizza, apple crisp, pumpkin pie, and some roasted veggies.
I've been watching Game of Thrones for the first time. I have mixed feelings about it.
His Dark Materials, on the other hand, is just about everything I could have hoped for in an adaptation. They're really leaning into the weirdness of it. Lyra's world also has a really unique design to it; much more unique than the Golden Compass adaptation, which was pretty run-of-the-mill steampunk. The new show of course has some of that, but also combines it with a more modern-esque brutalism that really sets it apart.
I bought a Nintendo Switch and started playing Breath of the Wild. I'm not very far, but I, again, have some mixed feelings. I just am not a fan of that sort of open-world game design (shared with Assassin's Creed, Horizon Zero Dawn, the recent Batman games, Final Fantasy XV...). I find it to be pretty boring, despite my best efforts.
Outer Wilds, on the other hand, completely blew my mind out of the water. It's now one of my favorite games of all time. I'm very sad that I can't find non-bootleg merch for the game.
I just wrapped up my last day of Lambda School and am going to be celebrating with deep dish pizza and gelato ordered out from a couple of my favorite local places. These two places used to be my wife and my usual date night destinations, along with the bookstore, and sadly it will probably be quite some time until we can comfortably go out to eat. But this will definitely do for now. I'm happy to be finished, and excited to really dig into the job search on Monday.
I just started playing Outer Wilds and am loving it. Being stuck in a time loop in a tiny universe trying to study history to prevent the world from ending just seems like such an apt theme for the time we live in.
Also, Sufjan Stevens and Fleet Foxes both released new albums. Haven't listened to the latter yet, but the new Sufjan is incredible. I wasn't a huge fan of his last album, but this new one seems to be somewhat of a return to his more electronic side, which I'm pretty stoked about. I still I play music myself occasionally, too.
Side projects went on hold for awhile so I could focus on finishing school, but they'll be my main focus until I get a job now (along with applying to jobs). I'd love to put out a demo at least of a game before the end of the year, but that might be overly ambitious.
I have a 10-day online meditation retreat coming up in just over a month. It was going to be in-person, but, you know.
I've completed the Computer Science curriculum at Lambda School, and so have moved on to Labs, where I'll be creating an iOS app with two teammates for a non-profit, EcoSoapBank. From what I've seen, it seems like a very cool organization, so I'm quite happy about that. Most of the apps I've built at Lambda School have been solo, so it's been really good for me to work on a team, as well. For both of my iOS teammates, I served as their Team Lead when they were going through the iOS curriculum (meaning I reviewed their code and assignments and helped troubleshoot issues), so it's been refreshing being on more "equal" footing with them.
I've started work on yet another side project: a meditation-related website. I'm hoping to have it up and running before the end of the year. For the time-being, I'm working on it with John Sundell's Publish, a static site generator built using Swift. It's a little low on documentation at the moment so it's a bit tough to navigate, but I really love the idea and the execution seems very clean. It will just take some time to wrap my head around its way of doing things. Once I've got it more thoroughly figured out, I might write a blog about it.
Speaking of blogs (do people still even call them "blogs"?), I've been writing blog posts more regularly lately. Hopefully saying that doesn't jinx it.
I've been slowly going through my second playthrough of The Last of Us pt. 2. It really is a fantastic game. It's a shame that it was made under such horrendous working conditions, and that this seems to be all-too-common amongst these giant AAA games. I would gladly have this game be shorter, less polished, and less detailed if it meant that the people working on it were treated like human beings rather than disposable cogs in a pretty-game-making machine.
I haven't been reading nearly as much as I'd like to. I'm hoping that admitting that publicly will make Me-at-the-Times-I-Could-Be-Reading want to do it more.
I just finished the first half of the Computer Science curriculum at Lambda School. I really enjoyed it; algorithms, data structures, and design patterns have been interests of mine since I first began wading into the world of programming, so it was fun and rewarding to have some structured time dedicated to the topics.
The "build week" of the unit involved implementing Conway's Game of Life, which I talked about in a recent blog post. I'm planning to clean it up a bit and put it on my portfolio. I probably won't put it on the App Store, which is already inundated with Game of Life implementations, but at least I'll make it look nicer and get a better video of it. I may try to implement the Hashlife algorithm... but I also have other, more interesting (to me) projects to get back to.
Next month, after the 1-week break that I'm currently on, we'll go over hash tables, graphs, and computer architecture. So far I've managed to stay a week ahead in the curriculum, which I have to admit I'm rather proud of. I've heard graphs week is one of the hardest in the curriculum, which I'm actually rather excited for. A lot of folks seem not to really enjoy Computer Science, but I've really been loving its puzzle-esque nature.
WWDC happened last week, which brought a lot of exciting API updates for iOS 14 and macOS 11 (!). I especially enjoyed the SwiftUI updates. I must admit I'm sad that it still can't do everything that UIKit can, but I think it will take awhile to get to that point, if that is the goal at all.
I'm still holding out hope that at some point in the next couple of years, some updates will come to Swift that will make it appealing to other platforms and areas. For example, I'd love to make a game engine with Swift, but it's unlikely it'd be able to run on anything but Apple platforms without some stronger support for Linux, Android, and Windows, as well as language-native async support. This "memory ownership manifesto" also provides an interesting look at the potential (hopefully actual) future of Swift. I'm very excited. Having heard the concept compared to Rust, it also has me interested in learning more about that language...
Work continues slowly on side-projects. I keep coming up with other ideas as well and have had to force myself to put them on my "someday/maybe" list. Meditation continues. The pandemic continues, despite the wishful thinking of many. Life continues.
I've finished my tenure as a Team Lead at Lambda School, and am now back to being a regular student for the next few months as I finish up the coursework. Tomorrow (Monday, June 1st), I'll begin the Computer Science curriculum, to which I am eagerly looking forward. This unit is one of the main reasons I chose Lambda School over other options; most "code boot camps" stick to what's "practical" and avoid theory, which I think is a shame, as even if it doesn't show up on the job as obviously on the surface, I think if you take the lessons to heart, they can inform every single decision you make as a software developer. Working with Python will be a bit wild after spending so much time with Swift.
In the meantime, I'm still slowly chipping away at a number of projects, including the productivity app and game (using Godot game engine with my friend Simon) I mentioned last update. More to come on those when they're at a shareable place.
I'm still meditating a ton, and for the next two weekends I'll be participating in all-day meditation retreats Friday evening through Sunday evening. I'd love to talk more about how huge of an effect meditation has had on my life, but there's not really a place on this website that it fits well. I may revive the "spirit" of my old website Skeptical Spirit (though I don't like that name so much anymore), but that's a project for another day...
I've been playing Bloodborne once again lately; I'm currently attempting to get the platinum trophy, which has been proving rather difficult. Once I accomplish this, I'll be moving on at last. I plan to replay the Uncharted series and finally get to Uncharted 4, as well as replay The Last of Us in anticipation of the upcoming release of its sequel. Eventually I'll make my way back to Dark Souls III and Sekiro.
Recently there has been a wave of protests in response to the murderous violence of police officers towards black men. I feel it would be cowardly not to express my support for these protests and their message here. There's a bit of worry in my mind that some company would decide not to hire me if they saw some of my opinions, since I do not hold police, wealth, capitalism, or many aspects of the dominant tech culture in particularly high regard. If that were to happen, though, I think I would be dodging a proverbial bullet (as opposed to the literal bullets that I don't need to worry so much about due to the color of my skin). I do not want to work for a company that does not respect human life and dignity, the privacy of its customers, its employees' justice-&-equity-positive opinions, or the facts of institutional racism, patriarchy, and other forms of discrimination.
Compared to March, April seemed to whizz by. As I said to my wife, March felt like 8 years, while April just felt like a month.
Not much has changed since last month; I'm still an iOS Team Lead for Lambda School for 4 more weeks as of this writing, after which I'll move onto Labs. I'm excited to become peers of the students I've been leading the past 3 months.
I'm still working on a game using the Godot game engine with my pal Simon. We have a game design doc and a title and everything. Our first goal is to make a proof-of-concept/vertical-slice/demo of sorts, which we'll probably release and say more about when it's done. We might go on after that. We'll see.
I've also been slowly working on a task management app using MVVM architecture, the coordinator pattern, and Swift's Combine framework. I had planned on finishing a working MVP relatively quickly, but its main benefit has ended up being educational much more than practical. I've since decided to abandon the original code, which has turned into near-unusable spaghetti. I haven't decided yet how to proceed; I may focus on the Godot game, or I may make a simpler habit-tracking app (another app category that's a dime-a-dozen... but nothing quite fits my needs!).
I've been meditating a lot more the past month, which has made life a lot better. I got into a weird place with meditation for awhile, where I didn't have as much time for it, I wasn't enjoying it that much, so I wasn't doing it as much, so I wasn't getting as much benefit out of it, so I wasn't making as much time for it... and down that spiral went. Lately I've been checking in more with a teacher, I've attended a couple of online weekend retreats, been keeping in touch with meditator friends, and reading more Dharma literature, all of which has gotten me a lot more jazzed about practicing and led to seeing more of those "off the cushion" benefits again.
I played through the Final Fantasy 7 Remake. My feelings were a bit mixed, but overall I enjoyed it and am looking forward to the next entry.
I convinced Elie to start playing Minecraft with me. She has been enjoying exploring and farming.
I don't think I've ever lived a month where things have changed so drastically so quickly.
My activities really haven't changed that much. I never went out much; I just go out even less now.
I'm still serving as a TL for Lambda School's iOS program. This will last until the end of May, at which point I'll move onto Labs, an 8-week cross-platform team project where we build a functioning app from scratch. After that will be the 6-week Computer Science portion of the curriculum, and then "Lambda X," which is essentially portfolio polish and job search.
I've recently started playing with Godot game engine. I want to actually finish a game with it. So far I can make the player character move and rotate with the camera. I also made a couple of neat little sprites!

I've been playing some games with friends when I can; Bloodborne, Minecraft... hoping to buy a Jackbox game and play that sometime with more friends. Also been doing some random streaming on Twitch, playing games, programming... maybe meditating?
Still playing music for fun. Started playing guitar more often lately, sometimes with Elie on clarinet.