Luminescent Dreams

Some Ramblings for November 24th, 2019

November 24, 2019
_DSC3525.web

Welcome back to another week of my ramblings.

Autumn here is quite thoroughly over, with Boston having gotten a couple of light freezes and a lot of dreary, watery weather. This picture is from more than a month ago, right around the time I was starting to get sick. Chances are, going on a hike and “taking the fresh mountain air” did not actually help my health at all, seeing as I then spent two weeks sick and several additional weeks with the dregs of illness.

Mental Health

This has been a struggle of a week. My mental health has cratered severly, and I have gotten into the range of not leaving the house because of the risk that I might have to interact with strangers.

Almost all of my trouble is work-related. I despair of ever getting to do work that I like, while I’m simultaneously having to maintain professionalism while dealing with complete disasters, and undisciplined men who are not actually interested in my experience (where by experience, I mean “hey, I’ve done that before and everything I touched exploded into flame”).

It is hard for me to take a mental health day primarily because I know that I will have to return to work to face whatever problems I was unable to solve before. This would not be a huge problem if I actually worked on interesting problems, but instead the problems I face are things like “the state model of this application is uncontrolled and getting worse” or “javascript is flagging errors in places where there cannot possibly errors”.

Let’s get something really clear: My career advanced as far as it did because I was viewed as a man until I reached the 32 years of age. My career has been backsliding ever since, with my options gradually shrinking, even though I have relentlessly expanded my technical skills and my project management skills.

Mental health, and the attendant suicidality that I manage, all revolve around hope. I basically don’t have any.

Obviously, this is not a healthy way to live. Where my co-workers and my clients see how I bring intense passion and skill to my work, the reality is that I actually just cannot disconnect from my work. I have never learned the trick of shutting work out of my mind when I am not working, and so problems at work always spill out into the rest of my life.

Rust, GUIs, and notifications

On my mental health day, I took the time to start getting familiar with building applicatinos in GTK again. I follow a development philosophy in which I have an “application” which is basically a headless object on which all operations can be performed, and the visible GUI is just a thin skin over that.

It turns out, React + Redux applications follow this model almost precisely. We all know that we can store our application state in Redux, using our actions and reducers to update the state, and using React to display the state.

So, on Thursday I took up the challenge of figuring out some version of that with GTK in Rust, and with some help I succeeded.

example main.rs

GTK inherently follows a “push” model of state updates, where React + Redux is made to feel like a pull update. React components exist and will pull any new updates whenever a state update occurs. With GTK, though, I have to build some of that infrastructure myself. I opted to use a listener model, in which I will register an update function for every component that I want to update in response to a state change. That update function receives the updated information and manually updates the component. The state manager (AppContext in the link above) calls all listeners (which really means just executing the registered functions) whenever a state update occurs. Functions that update the state are responsible for ensuring that this dispatch actually happens.

While this is conceptually straightforward, Rust’s very strict memory checking made this rather challenging to figure out.

Pay close attention to the closures on line 73 and 77. connect_clicked requires an Fn, not an FnOnce, implying that the closures will be executed more than once. Less obvious, though, the closures must outlast the closure passed to connect_activate on line 57, because the connect_activate call is specifically for setting up widgets. These widgets will inherently outlast the function call. So, dec_ctx and inc_ctx must both be cloned off of ctx, and ownership of them moved into the closures that get registered as callback functions.

It is not clear to me, however, why dec_ctx and inc_ctx, once moved, outlast an individual invocation of the enclosed functions. I can only assume that both variables (and, technically, ctx in the larger function) and the code fo the closure are bound together into a context that persists much longer than any particular invocation.

In writing this, I also look ta line 67 and wonder about the lifetime of the label, the label clone, and the update listener. In particular, I wonder what happens if the box containing the original counter_label gets destroyed. I suspect that while counter_label itself gets dropped, label_clone and the listener function continue to work as expected, but with no visual effects because no widget actually contians the label_clone.

For my next challenge, I will be converting the AppContext into something that keeps the handle to the underlying trax database, and which observes changes to the currently selecting range of data to show.

Immediate plans include full editing and record creation, plus better formatting. But I still want to get timezone, display units, and translations into the application as quickly as I can.

Games

In the midst of the depression, I have done a lot of self-distraction. First, I bought and played through Sayonara Wild Hearts on the Switch. This is more of a music game, racing through lots of neon scenes at breakneck speeds, and putting together an impression of a story of a broken heart attached to astral highways. But, the game is intensely positive, the protagonist is hella trans, and there are furries, androgynes, and lesbians scattered around as shattered fragments that the protagonist has to reclaim.

Second, today I got Steam working correctly on my NixOS machine and then fired up “Torment: Tides of Numenera”. I bought the game while I was sick last December, but I was unable to play it because so many of the scenes broke on my Macbook’s graphics hardware. I find it horribly ironic that a Linux machine runs games better than a Macbook pro, but that is the world we live in.

As a sidenote, this is what it takes to get Steam working on a NixOS machine. Install these packages:

    unstable.libva
    unstable.steam

Next, enable this in your system’s configuration.nix:

  hardware.opengl.driSupport32Bit = true;

That’s it for this week.

The upcoming week will involve me dedicating most of my spare time to baking until Thanksgiving arrives, and then I will re-approach fitnesstrax late Thursday and for most of the day on Friday.

With any luck, things at work will go well enough that I can stop stressing.

16 Years

January 01, 0001

Welcome to 2017!

In a few days I move away from Austin, likely never to return. I grew up in Round Rock. I went away for a few years, and then I returned here to begin my career and my adult life right during the dot-com crash. I’m actually a lot older than I look. Many people upon meeting me seem to assume that I’m 27 or so, when in fact I turned 38 late this year. “Wait, how old are you?”

I keenly feel the passage of time. I feel that I have not begun to approach what I wanted to accomplish by now. But, realistically, I have between 40 and 60 years left. The amount of time that I spent here… three more times. And a lot can change in 16 years.

16 years ago, I thought I was a straight man. I was married. I voted for Bush and thought the Republicans could run the country well. I was Catholic and believed the anti-abortion rhetoric, yet I somehow rejected the anti-gay rhetoric. Go figure. Though we knew a few gay men in college, it was shortly after we moved to Austin that my wife and I noticed for the first time pairs of men openly holding hands at formal “respectable” events. We began to feel a relief that this was the kind of safe city that we never really recognized we sought.

15 years ago, my wife and I decided to have a polyamorous relationship. She said that I had suggested it years earlier while we were dating. I did not remember saying that, but it felt like the kind of thing I might have. It was shortly after this, as I thought about love, romance, and relationships, that I began to believe that it was tragic that I was straight and not bisexual. I can remember being apologetic as I (very occasionally) turned a man down. And it was shortly after this that I understood that I was parting ways with the Catholic church… and I did not particularly regret that.

14 years ago, I found out how infidelity felt. Infidelity in a polyamorous relationship looks different than in a monogamous relationship, but it hurts the same. It cuts through hearts, rips out rugs, and crushes dreams.

I also learned that maybe the Republican party was actually made up of a bunch of chronic liars, and became a Democrat. Later I started to understand how violent and hateful Republicans could be. How did I never see this before? And maybe, just maybe, I shouldn’t hold the reproductive health doctrines of men who want to ban abortion but also ban all other forms of contraception and all forms of sex that carry no chance of pregnancy while simultaneously starting a war and lying to me about weapons of mass destruction!

12 years ago I joined a company that became my career for the better part of a decade. They weren’t great… in fact sometimes they were downright awful, but over time my authority became vast, as did my knowledge of everything about the business… except what was in the best interest of the business. Ya know, sometimes we techies need to be informed of the big business direction so we can make decisions intelligently.

10 years ago, with the onset of Saturn Returns, I finally figured out that I was not a man. That moment has lead me through so many changes and to so many of the people that I find so important in my life now. As a man, I would never have made any of the connections I have as an androgyne. This realization sometimes keeps me awake at night, knowing that it is by the grace of but a few words that I have in my life the love that I experience now. More rationally, a few of my current friends would have been my friends anyway, and they would have noticed my egg tendencies, and they would have aided in my hatching. I may have ended up exactly where I am now, on a different schedule.

Letting go of my own gender also let me release my expectations about my sexual orientation. Reparative therapy, especially religious-based “therapy”, is bullshit. We know this. And yet, I successfully “prayed the straight away”!

I also gave up on “til death do we part” and let my marriage end.

Five years ago, I learned photography, and I changed how I see the world. Always watching for that perfect moment. Seeing textures. Analyzing light. Understanding focus and freezing motion. The speckled shadow beneath a canopy. The shimmer of a cobweb five meters up and at least that far away.

Three years ago, I talked myself out of my first suicide attempt. In the aftermath, I evaluated my life. I saw clearly how I was wasting it on my employer’s amazingly small dreams, and I chose to spend some time quite alone. I loved living out in the woods. I hated having to drive for twenty minutes to reach the closest decent internet connection, and for an hour to reach any of my friends. But there is a lot to be said for the peace of the forest, for stars so bright as to light the ground, for rain on the metal roof a mere meter from my lofted bed… and for really cheap rent paid in cash under the table. Oh, and did I mention that my landlady also covered electricty? Pretty epic, especially since the cabin wasn’t well insulated and I had to run 2.5kW of heating that winter.

In the last two years, I have truly started to learn how black lives matter, and how little I understood my own racism in the past. I have learned about social justice, and become keenly aware of my failings. I have gained true confidence in my skills, and become comfortable in my body for the first time in my life. I have felt my socialist/anarchist heart begin to blossom as I notice the Democratic party repeatedly snatch defeat from the jaws of victory.

And, shortly after my birthday in 2014, I met the woman who has become the love of my life. She had to exercise both persistence and patience. I was wounded and avoiding romance, sex, dating. She had to convince me that a lesbian, even a trans-friendly lesbian, could be interested in an androgyne who still had and wasn’t particularly inclined to get rid of eir penis. But, she exercised that persistence, and she waited patiently, while over the course of months I fell in love and I healed. Now we talk of our sixty year plan.

I will miss Austin. I will miss the people here. I will miss all of my bike routes and the restaurants and the events. I will miss the familiarity. And I feel guilt, leaving all of you to stay and stand against the legislature.

But for this woman, where she goes my heart shall follow.

52-Week Photography Challenge

January 01, 0001

Welcome to 2019!

In this new year, there are several different things that I am trying to do. Not to worry, as I actually started on most of my resolutions on my birthday back in October.

But here is a new one that I’ll be doing in public. The Dogwood 52-Week Photography Challenge.

I will get my first photo published over the weekend, and keep an index of the photos here.

Week 1 Self-Portrait
Week 2 Composition: Rule of Thirds Motion You already know what the rule of thirds is, now is the time to use it. Use Rule of Thirds to show motion in your picture.
Week 3 Inspiration: Black and White Your inspiration this week is to simply take an amazing Black and White photograph of any subject you want.
Week 4 Story Telling: Warmth Tell a story that makes us feel warm inside.
Week 5 Composition: Symmetry Landscape Landscape is one of the most practiced type of photography. Use Symmetry in a Landscape to create a new viewpoint for this week's image.
Week 6 Inspiration: #NoFilter No limit on what you shoot this week, as long as the image is pure. No filters, presets or other edits. Basic exposure corrections only this week.

About

January 01, 0001
Dreamer, Shaper, Seeker, Maker, Cyberpunk, Betrayal-Driven Developer, Cyclist, Photographer, Alchemist, queer as in fuck you. I live in Providence, Rhode Island, with my partner and three glorious felines, hanging out with queers, and trying to be a helpful, productive human being.
  • Pronouns: ey/em, she/her
  • Honorifics: Mx, Mir, Miss, Ma'am

Contact

Interests

  • Functional Programming: Rust, Haskell, Purescript, Ocaml, Elm, Clojure
  • Photography
  • Cycling
  • Fitness
  • Linux
  • Space-age user interfaces

My Favorite Books and Short Stories

  • *Anathem*, Neil Stephenson
  • *Metatropolis*, John Scalzi et. al.
  • *Solitude*, Ursula Le Guin
  • *Coming of Age in Karhide*, Ursula Le Guin
  • *Angel Tech*, Antero Ali
  • *On Becoming an Alchemist*, Katherine MacCoun

Abstracting the Monad Stack, Part 1

January 01, 0001

As a sidenote, my previous articles were all in terms of a fictitious image processing program. I am actually very interested in image processing, but that is not the code I have on hand, so for my examples, I am switching over to a health-tracking application that I’ve been working on for a while. I’ll probably change the previous articles to reflect it. I am making this change, though, primarily because there are so many real examples for me to draw from.

Here is the application in question, and it is under active development. I have some layer refactoring to do, but the code is stable and I am mostly focusing my efforts on additional features.

Building a library monad

Code for this section

I previously talked about application monads, but today I will talk about a library monad.

Fortunately for everyone, they are almost identical to application monads, but with one twist that I want to lead you to.

In my health application, I have a library that forms the “application” and that library is meant to be wrapped around interface layers, such as the web API or a GUI. The application library is basically a set of functions that make a complete API that I can execute in the REPL.

So, assume a monad like this one:

data AppContext = App { weightSeries       :: TimeSeries Weight
                      , timeDistanceSeries :: TimeSeries TimeDistance
                      , stepSeries         :: TimeSeries Steps
                      }

-- TODO: handle time series exceptions. Make this less absurd.
data HealthException = TimeSeriesExc SeriesExc
                     | UnknownException String
                     deriving (Eq, Show)

newtype HealthM a = HealthM (ReaderT AppContext (ExceptT HealthException IO) a)
    deriving (Functor, Applicative, Monad, MonadIO, MonadError HealthException, MonadReader AppContext)

runHealthM :: AppContext -> HealthM a -> IO (Either HealthException a)
runHealthM ctx (HealthM act) = runExceptT (runReaderT act ctx)

(yes, that is a real TODO item in the code)

On it’s own, this isn’t bad. But the pain lies in that this is a library, and thus will likely end up in a different monad stack. As it is written, I would need to unroll this stack into IO and then re-roll it into my web stack. This is not horrible, but it is annoying. In the health application, I would do the re-rolling to glue these functions into my Web application monad, and it would look like this:

data Config = Config
data WebContext = WebContext { config :: Config, app :: AppContext }

newtype WebM a = WebM (ReaderT WebContext (ExceptT WebExc IO) a)
    deriving (Functor, Applicative, Monad, MonadIO, MonadError WebExc, MonadReader WebContext)

handleSaveTimeDistance :: Maybe SampleID -> SetTimeDistanceParameters -> WebM (Sample TimeDistance)
handleSaveTimeDistance sampleId params =
    let workoutFromParams = undefined
        workout = workoutFromParams params
    in do
    WebContext{..} <- ask
    res <- liftIO $ runHealthM app $ saveTimeDistance sampleId workout
    case res of
        Left err -> throwError $ AppExc err
        Right val -> return val

saveTimeDistance :: Maybe SampleID -> TimeDistance -> HealthM (Sample TimeDistance)

Again, this is not awful, but it is tedious. It can also become awful if I want to perform multiple operations from the library interleaved with operations from my webapp. For example, what if I want to query every series that I am storing?

handleGetHistory :: Interval UTCTime -> WebM ([Sample Weight], [Sample TimeDistance], [Sample Steps])
handleGetHistory interval = do
    WebContext{..} <- ask
    weightRes <- liftIO $ runHealthM app $ getWeights interval
    timeDistanceRes <- liftIO $ runHealthM app $ getTimeDistance interval
    stepRes <- liftIO $ runHealthM app $ getSteps interval

    case (weightRes, timeDistanceRes, stepRes) of
        (Left err, _, _) -> throwError $ AppExc err
        (_, Left err, _) -> throwError $ AppExc err
        (_, _, Left err) -> throwError $ AppExc err
        (Right weights, Right timeDistances, Right steps) -> pure (weights, timeDistances, steps)

getWeights :: Interval UTCTime -> HealthM [Sample Weight]
getTimeDistance :: Interval UTCTime -> HealthM [Sample TimeDistance]
getSteps :: Interval UTCTime -> HealthM [Sample Steps]

handleGetHistory already becomes tedious.

Rewrapping the context

Code for this section

The first, most obvious solution, is a helper function to re-wrap:

wrapEitherIO :: (exc -> WebExc) -> IO (Either exc a) -> WebM a
wrapEitherIO excTr act =
    liftIO act >>= either (throwError . excTr) pure

handleGetHistory :: Interval UTCTime -> WebM ([Sample Weight], [Sample TimeDistance], [Sample Steps])
handleGetHistory interval = do
    WebContext{..} <- ask
    weights <- wrapEitherIO AppExc $ runHealthM app $ getWeights interval
    timeDistances <- wrapEitherIO AppExc $ runHealthM app $ getTimeDistance interval
    steps <- wrapEitherIO AppExc $ runHealthM app $ getSteps interval
    pure (weights, timeDistances, steps)

And then, probably even one step further with a utility function to do the re-wrapping.

wrapEitherIO :: (exc -> WebExc) -> IO (Either exc a) -> WebM a
wrapEitherIO excTr act =
    liftIO act >>= either (throwError . excTr) pure

runHealthMInWebM :: (HealthException -> WebExc) -> AppContext -> HealthM a -> WebM a
runHealthMInWebM handler app = wrapEitherIO handler . runHealthM app

handleGetHistory :: Interval UTCTime -> WebM ([Sample Weight], [Sample TimeDistance], [Sample Steps])
handleGetHistory interval = do
    WebContext{..} <- ask
    weights <- runHealthMInWebM AppExc app $ getWeights interval
    timeDistances <- runHealthMInWebM AppExc app $ getTimeDistance interval
    steps <- runHealthMInWebM AppExc app $ getSteps interval
    pure (weights, timeDistances, steps)

This alone makes life much nicer. All of the exception checking boilerplate gets encapsulated into wrapEitherIO, and so every step of handleGetHistory gets to exist on the happy path. In many instances, I could just call this done.

Servant actually provides a typeclass for natural transformations which abstracts this away. It has a challenging type signature, but it is pretty nice and I recommend taking a look at it.

Type Constraints

Code for this section

I use type constraints as my preferred method for solving this problem. The idea behind it is that I try to have only one concrete monad stack anywhere in the application.

A “type constraint” is a mechanism by which I declare that a context must implement a particular typeclass, but that the context could be any context that implements that typeclass. A trivial example would be like this:

printSomeStuff :: (Show a, MonadIO m) => a -> m ()
printSomeStuff a = do
    liftIO $ putStrLn $ show a

This function will print out any value, so long as the value implements Show and so long as the function is called in any monad that implements MonadIO. For instance, all three of these calls to printSomeStuff are valid:

run1 :: IO ()
run1 = printSomeStuff "abcd"

run2 :: ExceptT String IO ()
run2 = printSomeStuff "abcd"

run3 :: MonadIO m => m ()
run3 = printSomeStuff "abcd"

Now, we build up on this concept, and to do so I’m going to repack all three of my get functions, this time starting from the simplest possible implementation.

saveTimeDistance :: Maybe SampleID -> TimeDistance -> AppContext -> IO (Either HealthException a)

handleSaveTimeDistance :: Maybe SampleID -> SetTimeDistanceParameters -> WebM (Sample TimeDistance)
handleSaveTimeDistance sampleId params =
    let workoutFromParams = undefined
        workout = workoutFromParams params
    in do
    WebContext{..} <- ask
    res <- liftIO $ saveTimeDistance sampleId workout app
    case res of
        Left err -> throwError $ AppExc err
        Right val -> pure val

saveTimeDistance can function in any monad that implements MonadIO, so the first thing I will do is to abstract that away:

saveTimeDistance :: (MonadIO m) => Maybe SampleId -> TimeDistance -> AppContext -> m (Either HealthException a)

handleSaveTimeDistance :: Maybe SampleID -> SetTimeDistanceParameters -> WebM (Sample TimeDistance)
handleSaveTimeDistance sampleId params =
    let workoutFromParams = undefined
        workout = workoutFromParams params
    in do
    WebContext{..} <- ask
    res <- saveTimeDistance sampleId workout app
    case res of
        Left err -> throwError $ AppExc err
        Right val -> pure val

This detaches me from a particular monad stack. This function can now be called as-is from any context that implements, hence in the above code, I no longer need to apply liftIO to saveTimeDistance. For bookkeeping, and because I am going to build upon this abstraction, I will give that type constraint a name:

type HealthM m = MonadIO m

saveTimeDistance :: HealthM m => Maybe SampleID -> TimeDistance -> AppContext -> m (Either HealthException a)

The next step requires a fairly large jump. I want to eliminate that AppContext parameter. It is required for every function in the health application, so it would be nice if I could pass it as part of a MonadReader. The naive solution would be to just do this:

type HealthM m = (MonadIO m, MonadReader AppContext m)

saveTimeDistance :: HealthM m => Maybe SampleID -> TimeDistance -> m (Either HealthException a)

Unfortunately, this actually is of detriment in the caller. If the caller has its own context in a MonadReader, that context is not likely to be the same as this one. The result is code that looks like this:

handleSaveTimeDistance :: Maybe SampleID -> SetTimeDistanceParameters -> WebM (Sample TimeDistance)
handleSaveTimeDistance sampleId params =
    let workoutFromParams = undefined
        workout = workoutFromParams params
    in do
    WebContext{..} <- ask
    res <- runReaderT (saveTimeDistance sampleId workout) app
    case res of
        Left err -> throwError $ AppExc err
        Right val -> pure val

I definitely do not want to be going in the direction of having to re-add a run function stack, but that is how this goes. The caller has to explicitely pull out the context for this call.

In order to get around this, I have to think a bit differently. I still want an implicit context of AppContext. But, really, the context could be larger so long as AppContext is present in it. So an alternate solution looks like this:

type HealthM r m = (MonadIO m, MonadReader WebContext m)

saveTimeDistance :: Health r m => Maybe SampleID -> TimeDistance -> m (Either HealthException a)
saveTimeDistance = undefined

handleSaveTimeDistance :: Maybe SampleID -> SetTimeDistanceParameters -> WebM (Sample TimeDistance)
handleSaveTimeDistance sampleId params =
    let workoutFromParams = undefined
        workout = workoutFromParams params
    in do
    res <- saveTimeDistance sampleId workout
    case res of
        Left err -> throwError $ AppExc err
        Right val -> pure val

In some ways, this looks better. The caller now can simply treat saveTimeDistance as part of WebM. But now saveTimeDistance becomes aware of WebContext, and so is beholden to a single caller. This is better, but not good enough.

What I want is a way to specify that saveTimeDistance can take any context, so long as that context provides me with a way to extract the AppContext. So, this is a constraint upon a constraint, and it ends up looking like this:

type HealthM = (MonadIO m, MonadReader r m, HasHealthContext r)

Basically, a HealthM function can take any MonadReader that provides r, so long as r “has a health context”.

My library gets to declare the HasHealthContext interface. The caller needs to implement that interface for its own context.

type Health r m = (MonadIO m, MonadReader r m, HasHealthContext r)
class HasHealthContext ctx where
    hasAppContext :: ctx -> AppContext

WebContext = WebContext { config :: Config, app :: AppContext }
instance HasHealthContext WebContext where
    hasAppContext WebContext{..} = app

saveTimeDistance :: Health r m => Maybe SampleID -> TimeDistance -> m (Either HealthException a)
saveTimeDistance _ _ = do
    appCtx <- hasAppContext <$> ask
    ...

With similar improvements made to getWeights, getTimeDistance, and getSteps, handleGetHistory also gets much nicer, and that demonstrates exactly what we wanted to begin with:

handleGetHistory :: Interval UTCTime -> WebM ([Sample Weight], [Sample TimeDistance], [Sample Steps])
handleGetHistory interval = do
    WebContext{..} <- ask
    weightRes <- getWeights interval
    timeDistanceRes <- getTimeDistance interval
    stepRes <- getSteps interval

    case (weightRes, timeDistanceRes, stepRes) of
        (Left err, _, _) -> throwError $ AppExc err
        (_, Left err, _) -> throwError $ AppExc err
        (_, _, Left err) -> throwError $ AppExc err
        (Right weights, Right timeDistances, Right steps) -> pure (weights, timeDistances, steps)

getWeights :: Health r m => Interval UTCTime -> m (Either HealthException a)
getWeights = undefined

getTimeDistance :: Health r m => Interval UTCTime -> m (Either HealthException a)
getTimeDistance = undefined

getSteps :: Health r m => Interval UTCTime -> m (Either HealthException a)
getSteps = undefined

Looking forward

Not quite there yet. We still have some tedium with exception handling to do. In this system, any thrown SeriesExc must be caught and then re-wrapped in the HealthException in order for the application to typecheck and for the exception to propogate upwards. This sort of tedium likely drove the creation of extensible IO exceptions, which I view as unchecked and undocumented parts of the type signature.

So, the next step will be to abstract the exception throwing mechanism.

Creative Commons License
Abstracting the Monad Stack, Part 1 by Savanni D’Gerinel is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.