tag:world.hey.com,2005:/maciej/feedMaciej Gryka2021-05-03T19:08:01Ztag:world.hey.com,2005:World::Post/108022021-05-03T19:08:01Z2021-05-03T19:08:01ZBuilding Secretwords<div class="trix-content">
<div>As <a href="https://world.hey.com/maciej/secretwords-7e54715f">mentioned previously</a>, I recently built a simple online game while learning the Elixir/Phoenix web stack. This post describes what I built and how. This was a fun learning experience for me and hopefully useful for you too. The finished project is here: <a href="https://github.com/maciejgryka/secretwords">https://github.com/maciejgryka/secretwords</a><br><br></div><h1>Requirements</h1><div>So first, what are we building? Let's see:</div><ul><li>It should be a web-based game, with the same rules are <a href="https://codenamesgame.com/">Codenames</a>.</li><li>It should use <a href="https://elixir-lang.org/">Elixir</a>, <a href="https://www.phoenixframework.org/">Phoenix</a> and maybe <a href="https://github.com/phoenixframework/phoenix_live_view">LiveView</a> (importantly, it should be server-side-rendered as much as possible with minimal Javascript). While tech stacks mostly don't belong in requirements, I'm doing this to learn a specific stack, so there.</li><li>It should work in real-time. In other words, the state of the game needs to be kept in sync and auto-updated for everyone at the same time.</li><li>It's OK to keep everything in memory, even if it's lost on restarts. No persistence, no databases.</li><li>No logins, authentication - anyone can connect and play immediately without an account.</li></ul><div><br>That's pretty much it! I think - writing these down retrospectively is not ideal, but this should covers my initial intentions.<br><br></div><h1>Setup</h1><div>Great, now we have a starting point - let's take the first step to having a working app. Before installing Elixir and Phoenix, I recommend setting up <a href="https://asdf-vm.com/">asdf</a> - it will make your life easy down the line. Follow <a href="https://asdf-vm.com/#/core-manage-asdf?id=install">installation instructions</a> from there and then add <a href="https://github.com/asdf-vm/asdf-erlang">erlang</a>, <a href="https://github.com/asdf-vm/asdf-elixir">elixir</a> and <a href="https://github.com/asdf-vm/asdf-nodejs">nodejs</a> (don't panic! node is needed in development, but we won't be using it directly).<br><br></div><pre>asdf plugin add erlang
asdf plugin add elixir
asdf plugin add nodejs</pre><div><br>Once you do that, you can create a new phoenix project with</div><pre>mix archive.install hex phx_new
mix phx.new secretwords --no-ecto --live</pre><div><br>The `--live` flag sets up LiveView and `--no-ecto` means no PostgreSQL support will be added; we won't be using a database.<br><br>Now you have a start of the project (if you need more details, follow the <a href="https://hexdocs.pm/phoenix/up_and_running.html">official Phoenix tutorial</a>). Let's skip to the fun part: how does it feel to write Elixir code?<br><br></div><h1>Writing Elixir code</h1><div>I don't know where it was, but the first time I've heard about Phoenix was something like "Oh Elixir is just a nice language, which compiles to Erlang and Phoenix is a Rails-like web framework for Elixir". Let me tell you, the second half of that sentence is a <strong>lie</strong>.<br><br>Elixir <em>is</em> a nice language, it <em>does</em> compile to Erlang and Phoenix <em>is</em> a web framework for Elixir. But if you know Rails, abandon any hope of knowing anything about Phoenix, other than "how the web works". Writing Elixir is a totally different experience to Ruby and nothing really carries over in terms of how to structure your app, how to think about solving problems etc. While authors of both Elixir and Phoenix came from the Rails community and you can see some inheritance if you look closely enough, it felt like an alien world to me. I was excited to visit and this world feels increasingly like home - but it is different. And I feel it's important to know this, because I struggled for a while unnecessarily trying to fit what I was learning into what I already knew. Only once I realized it was a completely different beast and made space in my head for that fact, was I able to learn and appreciate everything properly.<br><br>Probably the clearest two differences are functional programming (instead of object-oriented) and immutable data structures. I was so used to OO and mutability that it took some real effort to switch mindsets. Let's go through a quick example: I store the state in a data structure I call, imaginatively, `GameState`. It has a bunch of attributes: id, the grid of words on the "table", who's on which team, how many points each team has etc. In the Rails world, it would probably be a class with a bunch of attributes and methods to modify them. For instance a method to join a team might be something like:<br><br></div><pre>def join(color, user_id) do
@game.teams[color].add(user_id)
@game.ensure_leaders()
end</pre><div><br>While my Elixir implementation is:</div><pre>def join(game, color, user_id) do
updated_teams = %{game.teams | color => MapSet.put(game.teams[color], user_id)}
game
|> Map.put(:teams, updated_teams)
|> ensure_leaders()
end</pre><div><br>Keep in mind that this is kind of the worst case scenario for Elixir - modifying deeply-nested, immutable data structures is challenging in the functional world. So much so, that <a href="https://twitter.com/josevalim/status/1379771275627921409">José Valim himself recently asked for crowd-sourced opinions about the best way of solving such problems in different languages</a> (and <a href="https://nickjanetakis.com/blog/100-ways-to-solve-a-specific-programming-problem-in-50-languages">this blog post</a> has more background info).<br><br>To spell it out, what I want to do is updating a single field in a 3-deep map. To do this, I basically need to hand-update 3 different maps:</div><ul><li>update the list of team members by calling `MapSet.put`,</li><li>update the `teams` map with the resulting list of team members,</li><li>finally update the game itself with the new `teams`.</li></ul><div><br>Other than than, there are a few things going on here:</div><ul><li>There is no `self` or anything similar - we're purely operating on arguments and we just accept `game` struct (not "object") as one of our arguments.</li><li>We also return an updated `GameState` struct, though it's a different one (as in, it's stored at a different memory location) than the one we accepted. Since everything is immutable and we want to make changes, we create new structs.</li><li>In rails we can assume e.g. `@game.teams[color]` is a `Set` and we can just call its `add` method, which is pretty convenient. In Elixir, we can use <a href="https://hexdocs.pm/elixir/MapSet.html">`MapSet`</a>, however, since there are no classes or objects, we call `MapSet.put` explicitly, so it's a bit more verbose. I'm on the fence about the relative importance of being succinct vs. explicit here.</li><li>In both cases we call `ensure_leaders()`, which just makes sure that some rule constraints are satisfied after every update.</li><li>The pipe operator `|>` might seem strange at first, but you get used to it very quickly.</li></ul><div><br>There are other differences, not obvious from this snippet. An important one is having no early return, but instead gaining great pattern matching on function arguments itself. As a result you end up writing many smaller functions with fewer branches, which I feel is is a readability win. For example, I have a log_activity function, which takes either one message or a list of messages and appends them to some field:<br><br></div><pre>def log_activity(game, messages) when is_list(messages) do
%{game | activity: messages ++ game.activity}
end
def log_activity(game, message) do
%{game | activity: [message | game.activity]}
end</pre><div><br>This is nice! I have two functions instead of one and they're all very clear. I have the freedom to name second argument in singular or plural. Compare to the equivalent in Python:<br><br></div><pre>def log_activity(self, messages) do
if isinstance(messages, list):
self.activity += messages
else:
self.activity.append(messages)
end</pre><div><br>It's still a small function, but feels less satisfying. I'd probably not write it like this at all and just have a single version, which always takes lists and I'd call it like `game.log_activity(['some message'])` when there's only a single message.<br><br>In the end every language has trade-offs and I'm really enjoying the ones Elixir makes.</div><div><br></div><h1>Persistence</h1><div>I knew up-front there will be no need to permanently store any data. Still, we do need to remember the state of the game while it’s in progress in a way that’s accessible to all players. Generally a way to store some state in Elixir is a <a href="https://hexdocs.pm/elixir/GenServer.html">GenServer</a>, which can store things and let other processes read and write them. However, I also wanted to see what <a href="https://elixir-lang.org/getting-started/mix-otp/ets.html">ETS</a> is all about so I threw it into the mix as a learning experience.<br><br>What I ended up with is not ideal - I have a single GenServer, called `GameStore`, responsible for reading from and writing to ETS tables. It works, but it's not isolated - if anything goes wrong with a game it will affect all the others too. It would've been better, probably, to isolate them so each game would have its own GenServer. I might experiment with doing that later.<br><br>One cool thing about ETS, though, is that it should be pretty easy to swap with <a href="https://erlang.org/doc/man/dets.html">DETS</a>, which is disk-based. I thought trying to preserve some state across restarts might be an interesting experiment. Losing the state of all games on restart (e.g. when I deploy) is definitely not ideal - but I never got to solving it.<br><br>The way it all works is that I pass `GameState` structs around, each one representing a single game. The `GameStore` has functions to retrieve and update existing game states as well as a convenience `get_or_create` function, which returns a game if it already exists or creates a new one if it doesn't. All games are identified by an ID, which is a random string.<br><br>There's a similar thing going on with users - while there's no logins, I wanted to both be able to identify players throughout a session and let them set a nice, human-friendly nickname. The `UserStore` just maps a random ID to a username - otherwise it's very similar to the `GameStore` (and also uses ETS).</div><div><br></div><h1>Broadcasting changes</h1><div>When someone changes their nickname, we want all the other players to know about it. Since each player basically has a process with their own copy of the game state, this doesn't happen automatically. I.e. if Bob changes his username to Ben, Alice would have to reload the page to see that update. Not ideal - and luckily it's very easy to change.<br><br>The way to deal with this is using a PubSub - there are basically two lines of code to add:</div><ul><li>`PubSub.broadcast!` whenever a change is made,</li><li>`PubSub.subscribe` inside the LiveView `mount` function to force re-rendering when anything is broadcast.</li></ul><div><br>With these additions (once for `GameStore` and once for `UserStore`) everything works smoothly - I'm still amazed at how easy this is to do.<br><br></div><h1>Plugs</h1><div>Another neat abstraction in Phoenix are plugs - if you've ever used something like Django, you can think of plugs as middlware: they take a request, modify it and pass it along. I've added two plugs: one to set the user_id and one to make sure the user is assigned to some team in the current game.<br><br>The first plug does two things: it makes sure that the conn struct (which represents the current user's session) has a user_id, crating it if necessary and it also makes sure that the map user_id -> username exists in ETS. While user_ids are unique and static, the username mapping is used to display the names in human-readable form. It's possible that multiple users will end up with the same name - this is fine!<br><br>The other plug only takes effect once a user joins some game and makes sure that the user is assigned to one of the teams. This is strictly not necessary, I could've allowed users to join a game without joining a team, but I thought having this constraint would make things easier. If the current user does not belong to any team, we assign them at random.<br><br></div><h1>Testing</h1><div>Testing is important to me (I wouldn't be working at <a href="https://www.rainforestqa.com/">RainforestQA</a> if it wasn't), but I always struggle to find a satisfying balance on side projects. I definitely want to have some harness to make sure things don't break without having to manually check all the features at every release. At the same time, the real value of testing comes after some time and I'm never sure I'll be working on any side project long enough to get that pay-off.<br><br>However, the more projects I work on, the more I realize the payoff from testing (and also documentation) comes sooner than I expect.<br><br>Most of the pieces were very easy to test, scoring another point for functional style and immutable data. Both these traits guide you towards writing many small functions with no side effects and these are really nice to write tests for. For this reason testing the `GameState` module representing the core logic was a breeze. Tests for the LiveView itself were nothing to write home about - not bad, but not amazingly convenient either and I'm still not sure how much I need them given the other layers.<br><br>The "top of the testing pyramid", the functional tests, were fun to figure out. I had no idea how to write functional tests for multiplayer scenarios! The game has a bunch of constrains, among them the minimum number of players. For instance some interface elements only show up after the game is started and you can only start it with at least 4 players present.<br><br>Luckily this turned out to be pretty easy to do technically with <a href="https://github.com/elixir-wallaby/wallaby">wallaby</a> - but it still requires quite a bit of management, so I'm sure a better solution is possible. Each feature test can accept multiple sessions, each representing a player. From there it's just a matter of making sure all the interactions happen in the right order. It looks something like this<br><br></div><pre>@sessions 4
feature "four players can start", %{sessions: [player1, player2, player3, player4]} do
game_path = Routes.live_path(@endpoint, SecretwordsWeb.GameLive, 'game_id')
player1
|> visit(game_path)
|> assert(...)
player2
|> visit(game_path)
|> assert(...)
player3
|> visit(game_path)
|> assert(...)
player4
|> visit(game_path)
|> assert(...)
end</pre><div><br>It does the job, but is a bit of a pain to manage - just like any Selenium-like testing framework. I've had a couple of problems, which were very obvious visually (e.g. Tailwind issues, see below), but were not caught by my integration tests. They mostly fell into the category of "important, but impossible to specify by XPath selectors" and are the main reason we're not fans of such tests at Rainforest (AJ wrote a <a href="https://www.rainforestqa.com/blog/the-downfall-of-dom-and-the-rise-of-ui-testing">comprehensive blog post</a> about that). However, I couldn't easily use Rainforest, because we don't yet have a great multiplayer testing story.<br><br>Finally, I get lots of pleasure from my code being buttoned-up, so I've also added code formatting (using the built-in `mix format --check-formatted` command) and style checks (using <a href="https://github.com/rrrene/credo">credo</a>) to my standard test command.<br><br>I also started write type specs for my code and really wanted to set up dialyzer to perform analysis each time I deploy. However, it takes quite a long time to run from scratch I had some trouble with caching, so I couldn't find a practical way to use it. I'm sure it's doable and not too tricky - but it's something I'll have to figure out later.<br><br>Finally, I wanted a CI/CD pipeline, because I really believe having it forces you into better habits. I got some way there! I got CI (Continuous Integration), but didn't build out the CD (Continuous Deployment) part. In other words, I use GitHub Actions to run all the tests mentioned above on every commit to every branch - but I have to run a couple commands locally to deploy the code to production. The <a href="https://github.com/erlef/setup-beam">setup-beam</a> GH Action from the Erlang Ecosystem Foundation makes things pretty easy.<br><br></div><h1>Tailwind</h1><div><a href="https://tailwindcss.com/">TailwindCSS</a> is all the hype these days, so I wanted to try it out. Half-way through building, <a href="https://blog.tailwindcss.com/just-in-time-the-next-generation-of-tailwind-css">the JIT</a> was announced so I gave that a shot and it worked pretty well. Besides some flailing around with the initial setup, the only bump in the road I hit was <a href="https://world.hey.com/maciej/tailiwind-purging-dynamic-classes-af459385">using dynamically-generated classes, which I covered before</a>. Since then I reverted back to just using more verbose conditionals, which output full class names instead of partial strings. It's not perfect, but doesn't bother me too much.<br><br>Other than that, I also played with TailwindUI, but I think I'll need some more time with it to make it feel really useful - the designs are beautiful, but I end up customizing them so much that they lose their charm.<br><br></div><h1>Deployment</h1><div>The last piece of the puzzle is deployment. There are a bunch of options, but since this was mostly about learning, I wanted to set up a server from scratch. I've done this a couple of time in the past for Python projects and Elixir is easier generally to deploy: the compiled release is just a bunch of binary files you can pretty much drop onto a server and expect to work.<br><br>The specifics are kinda boring: set up a plain Ubuntu server with automatic updates, ufw, nginx. The part, which was new to me this time was setting up systemd to make sure the server process always runs - it was still pretty straightforward, though.<br><br>Finally, I wrote 3 scripts inspired by <a href="https://github.com/github/scripts-to-rule-them-all">scripts-to-rule-them-all</a>: server, test, and deploy.<br><br></div><div>This was a fun project and I learned a lot!</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/89772021-04-09T19:41:38Z2021-04-09T19:41:55ZSecretwords<div class="trix-content">
<div>Over the past months’ evenings and weekends I’ve been having fun building side projects. I’ve been fascinated by the <a href="https://elixir-lang.org">Elixir</a> ecosystem and wanted to learn more about it.<br><br>As is common during the pandemic I really wanted to play some board games with friends online. As is common among parents, I almost never have reliable, uninterrupted time to hang out with friends, even online.<br><br>What’s the next best thing if I can’t play a game with friends? Making one by myself! Memories of late night board game sessions at our company offsites with <a href="https://rainforestqa.com">Rainforest</a> came flashing back and I decided to build a simple version of Codenames.<br><br>Turns out Elixir with LiveView is a pretty great fit for something like this. The game is now online at <a href="https://words.gryka.net">https://words.gryka.net</a><br><br><br></div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/89762021-04-09T19:34:13Z2021-04-09T19:34:13ZHow much money should people make <div class="trix-content">
<div>A while ago the Polish president, Andrzej Duda, gave some kind of recognition to Robert Lewandowski, who seems to play football well. I don’t follow these things closely, but that’s not the point.<br><br>The event caused a bunch of commotion, for a few reasons. Mainly, the current Polish government, including the president, is pretty terrible, so any reason to criticize them is taken up vigorously. Also, we’re in the middle of the pandemic and Poland generally managed it pretty badly (looking at the excess deaths compared with the past). Celebrating a super-rich and ultra-privileged football player instead of, for example, healthcare workers seems like a poor choice.<br><br>One of the widely-discussed topics was also how rich Lewandowski is, especially in comparison to other people, who seem to fulfill more important societal functions. Someone pointed out the watch he wore at the ceremony costs as much as a flat and, unsurprisingly, the discussion was pretty polarized. One question in particular caught my attention, because it comes up frequently and feels like the essence of the issue:<br><br>“Why do footballers earn so unimaginably more than nurses?”<br><br>There are many levels of discussing it (and, as is often the case, many people seem to discuss these questions with each other, while thinking about different levels and the results are not pretty).<br><br>First of all, there’s the question of why this happens in the mechanical sense, i.e. what are the mechanisms that lead to this outcome? This is not very interesting, but probably worth covering just to make sure we’re on the same page. Simplifying, in our current economic reality, people are collectively willing to pay Lewandowski more for his entertainment than they are willing to pay any given nurse for her healthcare.<br><br>Then there’s the fact that there are many football players and not all are paid as much. Also there are many nurses! Maybe as a society we spend more on healthcare overall than on football overall? I have no idea how many football players there are compared to nurses and how much both groups are paid in aggregate and that’s besides the point.<br><br>Most people seem to ask the question rhetorically, with the implied judgement of “it is so unjust for a footballer to earn so much more than a nurse, capitalism is broken and needs to be abolished”. (At least in my head and I’m mostly arguing with myself over here.)<br><br>Here’s how I think about this. Nobody designed our current economic system with this outcome in mind, nobody sat down saying “let’s come up with free market ideas so that future athletes can earn obscene amounts of money, while nurses barely scrape by”. Seems pretty obvious that the current situation is a consequence, not the most important one, of a bunch of upstream mechanisms. Maybe it points to some upstream fact about how the system is set up, but it’s not very enlightening because it’s just a consequence. Nobody set out to create a system to operate this way.<br><br>So sure, it’s useful to point out absurdities and that’s how we can fix things. But let’s not fix systematic problems by tackling consequences - we have to address the upstream problems.</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/60632021-03-14T15:00:00Z2021-03-14T15:00:00ZTailiwind, purging & dynamic classes<div class="trix-content">
<div>When working with TailiwindCSS, be careful to not generate CSS classes dynamically - if you do, some of your CSS classes might be missing in production as <a href="https://tailwindcss.com/docs/optimizing-for-production#writing-purgeable-html">explained in the docs</a>. The reason for this is Tailwind using PurgeCSS to delete unused classes and PurgeCSS being intentionally naive about how it detects which classes are used.<br><br>For example, if you do something like </div><pre><div class="text-<%= team %>-600">...</div></pre><div>you might expect that the text will be red if team = "red". But it probably won't be, because PurgeCSS doesn't know what `<%= team %>` evaluates to and doesn't see `text-red-600` anywhere in your code, so it removes it. I say "probably", because you might be using `text-red-600` somewhere else, in which case this class will be included and that particular style will work, making it all a little tricky to debug.<br><br>There are a couple of workarounds. You can do what the docs suggest and just conditionally output entire class names, like this</div><pre><div class="<%= if team == 'red' do %>team-red-600<% else %>team-blue-600<% end %>">...</div></pre><div><br>This is fine, I guess, but gets unwieldy if you have a bunch of distinct possibilities, not just two. Maybe you have a component, say "product" and it can have one of 4 states: coming-soon, available, sold-out, deprecated. Having a 4-way `if` in your templates is a pain; it'd be nicer if you could just generate the class name dynamically.<br><br>The best way I found for this is to define all these variants as Tailwind components and then <a href="https://tailwindcss.com/docs/optimizing-for-production#purging-specific-layers">preventing Tailwind from purging any components</a>. It's not ideal - you might end up with unnecessary CSS classes in your production bundle.<br><br>Is there a better way?</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/49082021-03-09T20:46:57Z2021-03-09T20:46:57ZFree markets<div class="trix-content">
<div>Should we strive towards wild & unrestricted capitalism, or planned economy?<br><br>Of course, when you put it like that, it's obvious the answer is "neither". Without getting into whether any of the extremes is worse than the other, it's clear that neither is optimal. Over centuries and decades we've arrived at various places on that spectrum, depending on where in the world you are.<br><br>Planned economy never worked in practice and doesn't seem feasible, while being open to abuse. Unrestricted capitalism results in monopolies, eats itself and goes to shit as well.<br><br>All that seems very obvious, but why do you never hear people position discussions that way? Here's our range of choices from one terrible option to another. It's clear we don't want any of the absolutes, so let's try to figure out where we should land. We disagree about specifics? Great, let's discuss them with the understanding that we both know the extremes are sub-optimal.<br><br>Maybe I just didn't find the right places and discussions, but most arguments I've seen are back-and-forth between "communism is bad" and "capitalists are greedy". Sure, so what? That's not the interesting question here.<br><br>And it would be nice to see public figures acknowledge this too. It'd be nice for AOC or Bernie to say that regulated free markets are wonderfully efficient, maybe we just need to regulate them more (if they did say that, my bad, I don't follow very closely). And it'd be nice for...<br><br>...I was trying to think of stereotypical free-market capitalists, imagining they could say something like "monopolies are not good". But then I thought of Jeff Bezos and Bill Gates and, hey, turns out they both made monopolies. So maybe this whole thing is not as obvious as it seems?<br><br>I know that like Peter Thiel's whole thing is "monopolies are good, because you can make a lot of money and be a philanthropist", which, yes, I'm sure you can, but please pay taxes and compete in the marketplace instead. And the whole blitzscaling thing is basically "how to become a monopoly". But surely, anyone, who thinks about the entire economic system for more than 5 minutes must agree that monopolies = bad, competition = good? It's totally possible to think monopolies are generally bad, while building a monopoly, because money, but we're talking about principles here.<br><br>So, I don't know where this leaves us. Should we be talking more about how to regulate capitalism and less about how communism is bad?</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/46222021-03-08T21:05:21Z2021-03-08T21:05:21ZChoice, anxiety <div class="trix-content">
<div>Small break from writing for me today, but you should read this <a href="https://zeynep.substack.com/p/is-choice-always-worth-the-anxiety">https://zeynep.substack.com/p/is-choice-always-worth-the-anxiety</a></div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/42702021-03-07T20:12:43Z2021-03-07T20:12:43ZNaive rationalism is irrational<div class="trix-content">
<div>There's a long history of people believing they are better humans when they are rational.<br><br>It's a dangerous path to go down, however, because in practice it often leaves important data out of the equation. Most obviously, human emotions are often discarded as if they don't exist.<br><br>If you have a choice to make and you're weighing pros and cons of each of your options, you might try to write them all down. Each item you write down is a judgement: is this particular thing important enough to include in your analysis? You might consider things like money, opportunity cost, difficulty etc. Being rational, at least in the naive understanding I'm setting up as the straw man, means mostly not writing down things like "how will this choice make me feel" or "what will others think".<br><br>If you want to make a good decision, you need to take all the information available to you into account. Purposefully ignoring your feelings and built-in irrationality is itself irrational - your feelings are likely to persist whether you acknowledge them or not and they will affect you.<br><br>In the same way, you ignore others' feelings at your peril. Not accounting for how you make others feel and just doing whatever you thing is "rational" blinds you to important information.<br><br>Think about emotions to be rational.</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/39322021-03-06T20:04:13Z2021-03-06T20:04:13ZHardcore & softshell<div class="trix-content">
<div>If you think of yourself as a sphere, with different densities on the inside and the outside, how are you built? Are you hard on the outside and soft inside, or do you have a squishy outside and a hard core?<br><br>If you have a hard shell, many things bounce right off of you. It's not easy to affect you. Your shell protects you from many things thrown your way and your squishy core can rest easy. Until a really big-enough force comes along and manages to penetrate your hard shell. When that happens, your core, which gets softer the deeper you go, is very vulnerable.<br><br>If your shell is soft, on the other hand, you will be affected by many things. Every speckle thrown your way will get under your skin at least a little bit. It's difficult for you to ignore things and you're visibly affected by them. While you look scarred, however, your core is pretty much intact. Your density increases the deeper you get and the core is difficult to reach, let alone break.<br><br>This very-thinly-veiled metaphor about one's character has been stuck in my head since I was very young. I'm not sure what triggered it, but it feels like an early memory, which has been nevertheless vividly present in my head all this time.<br><br>In your arguments and discussions you can either be stubborn and aggressive in the moment, but long-term-variable, or you can be easily-persuaded in the moment, but stable over long periods of time.<br><br>I recently realized it's all similar to the question of designing robust systems. The most obvious, but mostly wrong, instinct we have is to put a lot of effort into preventing problems from happening. What seems to work out better in practice, however, is to accept that problems will happen and put more effort into developing ways of dealing with them.<br><br>Your shell will give in, sooner or later. Spend your density on your core.</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/33412021-03-05T19:11:10Z2021-03-05T19:11:10ZThinking probabilistically<div class="trix-content">
<div>The last year has been an interesting way to uncover patterns in our collective thinking.<br><br>One thing I noticed is that it's difficult for societies to think probabilistically. "Masks don't work" vs. "it's OK, I'm standing 2 meters away" are both symptoms of that. It seems obvious that both wearing a mask and keeping your distance is a way to <em>minimize</em> risk, not remove it, but that’s sometimes hard to operationalize.<br><br>People don't seem comfortable with new ways of risk mitigation, unless they are absolute. (Different to <em>established</em> risk mitigation strategies: wearing seatbelts is understood to not make you absolutely safe in a crash, but it increases your chances and somehow that’s understood.) Wearing a mask and keeping your distance both lower your risk, but are not guarantees and it took a while to become accepted in a reasonable way.<br><br>At least in my corner of the world, masks were at first frowned upon (I got some downright hostile looks when I was wearing a mask before it was an enforced norm). Distancing was the opposite: people seemed to believe that if they stayed 2 meters away from each other, there would be absolutely nothing to worry about.<br><br>Another connected symptom is how we evaluate our past decisions. It’s the difference between evaluating them based on the outcome (tempting, but wrong) vs. based on the information we had at the time (unsatisfying and can feel like making excuses, but the only reasonable way).<br><br>I keep thinking this has to be trainable. My totally untested pet theory is that people, who like playing games (either video-, board- or card-games) probably find this easier. Can we use this to make probabilistic thinking more universal?</div>
</div>
Maciej Grykamaciej@hey.comtag:world.hey.com,2005:World::Post/15832021-03-04T20:40:18Z2021-03-04T20:40:18ZUncertainty over wrongness<div class="trix-content">
<div>If you see yourself as a rational person, you probably try to retrospect occasionally and evaluate whether what you’re doing is right.<br><br>This is difficult for many reasons, among those what is “right”? Also, how do you avoid your own biases and look at things as they are, rather than making yourself feel good (or bad) unfairly?<br><br>The same extends to organizations, trying to make data-driven decisions and measure success objectively. “You can’t improve what you don’t measure” is the mantra.<br><br>It’s a noble pursuit generally, but often taken too far. Some of the most important things are impossible to measure. Doing good in the world. Empowering others. Living true to yourself. There are no numbers to track there.<br><br>You can track things, which you think are correlated with the thing you care about. However, you have to keep in mind three complications:</div><ul><li>you can’t be sure they’re correlated (the only way to be sure is to have data, but we’re already saying the important thing can’t be measured directly),</li><li>even if they are correlated under some conditions, it’s likely not universal,</li><li>there are probably multiple metrics correlated with your goal and it’s hard to know which to pick.</li></ul><div><br>You have to choose based on gut feel, I.e. subjectively, however hard you try to be objective.<br><br>If you measure a proxy, you won’t know when you’ve taken it beyond a point it’s divorced from your true goal and you’re gaming the metric instead of making an impact.<br><br>If you measure a single proxy you will miss the bigger picture. If you measure multiple, you will have a hard time deciding what to do when they start to tell you different stories.<br><br></div><div>All this is not to say that you shouldn’t track any data, I think you should to guide your gut and learn. But it’s futile to strive for absolute objectivity and subjective judgements are extremely useful.<br><br>It’s better to be uncertain about being right than certain and wrong.</div>
</div>
Maciej Grykamaciej@hey.com