Compare commits

...

15 Commits

Author SHA1 Message Date
Cadey Ratio f535bac942 edits
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 16:38:28 -04:00
Cadey Ratio ae78412edd nix flakes 4: WSL
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 16:38:28 -04:00
Cadey Ratio e03e1712b1 amount
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 14:26:49 -04:00
Cadey Ratio dacc7159d7 shitposting as a service
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 17:43:30 +00:00
Cadey Ratio 021f70fd90 bump nixpkgs
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-30 14:23:34 +00:00
dependabot[bot] f7aa184c20
build(deps): bump tower-http from 0.3.0 to 0.3.1 (#462)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.3.0...tower-http-0.3.1)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 21:55:44 -04:00
dependabot[bot] 1dfd47708f
build(deps): bump http from 0.2.6 to 0.2.7 (#461)
Bumps [http](https://github.com/hyperium/http) from 0.2.6 to 0.2.7.
- [Release notes](https://github.com/hyperium/http/releases)
- [Changelog](https://github.com/hyperium/http/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/http/compare/v0.2.6...v0.2.7)

---
updated-dependencies:
- dependency-name: http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 21:55:38 -04:00
dependabot[bot] 4d05bd7347
build(deps): bump axum from 0.5.0 to 0.5.4 (#459)
Bumps [axum](https://github.com/tokio-rs/axum) from 0.5.0 to 0.5.4.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.5.0...axum-v0.5.4)

---
updated-dependencies:
- dependency-name: axum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 20:05:04 -04:00
dependabot[bot] 22c29ce498
build(deps): bump eyre from 0.6.7 to 0.6.8 (#452)
Bumps [eyre](https://github.com/yaahc/eyre) from 0.6.7 to 0.6.8.
- [Release notes](https://github.com/yaahc/eyre/releases)
- [Changelog](https://github.com/yaahc/eyre/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yaahc/eyre/compare/v0.6.7...v0.6.8)

---
updated-dependencies:
- dependency-name: eyre
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 09:30:31 -04:00
dependabot[bot] 1019e42fa7
build(deps): bump tower-http from 0.2.5 to 0.3.0 (#458)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.2.5 to 0.3.0.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.2.5...tower-http-0.3.0)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 09:30:05 -04:00
dependabot[bot] 4170e3b78c
build(deps): bump axum-extra from 0.2.0 to 0.3.0 (#460)
Bumps [axum-extra](https://github.com/tokio-rs/axum) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-extra-v0.2.0...axum-extra-v0.3.0)

---
updated-dependencies:
- dependency-name: axum-extra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-27 09:06:44 -04:00
Cadey Ratio ad2f5c739f twitter, mastodon, rasengan
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-26 01:02:27 +00:00
Cadey Ratio 05135edcbe i am good at the computer boxes
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-25 06:03:36 -04:00
Cadey Ratio 9566b790bc gonads better
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-24 20:36:41 -04:00
Cadey Ratio 67de839da8 gonads
Signed-off-by: Xe Iaso <me@christine.website>
2022-04-25 00:17:52 +00:00
9 changed files with 1455 additions and 21 deletions

24
Cargo.lock generated
View File

@ -130,9 +130,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.5.0"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5611d4977882c5af1c0f7a34d51b5d87f784f86912bb543986b014ea4995ef93"
checksum = "f4af7447fc1214c1f3a1ace861d0216a6c8bb13965b64bbad9650f375b67689a"
dependencies = [
"async-trait",
"axum-core",
@ -161,9 +161,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.2.0"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95cd109b3e93c9541dcce5b0219dcf89169dcc58c1bebed65082808324258afb"
checksum = "3bdc19781b16e32f8a7200368a336fa4509d4b72ef15dd4e41df5290855ee1e6"
dependencies = [
"async-trait",
"bytes",
@ -175,9 +175,9 @@ dependencies = [
[[package]]
name = "axum-extra"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff3819ded1be91d7ee2cd9f0466aa345cc70ba0b0035ed47e3eac6427f83b81a"
checksum = "a00a7085c512df12d4e07a862b23a3b3bfe5326ecfc4185b49fb15c8850ba406"
dependencies = [
"axum",
"bytes",
@ -655,9 +655,9 @@ checksum = "649cfd341410b1f8906e8ca1b39e5534be9312fda9182edd770cec34dfbce8d7"
[[package]]
name = "eyre"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f"
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
dependencies = [
"indenter",
"once_cell",
@ -929,9 +929,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
dependencies = [
"bytes",
"fnv",
@ -2363,9 +2363,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.2.5"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8"
checksum = "f7755f984aa46a9d50829dc84b6a674b2f3b8b4de47c0708923953b090d48acc"
dependencies = [
"async-compression",
"base64",

View File

@ -11,7 +11,7 @@ repository = "https://github.com/Xe/site"
[dependencies]
axum = "0.5"
axum-macros = "0.2"
axum-extra = "0.2"
axum-extra = "0.3"
color-eyre = "0.6"
chrono = "0.4"
comrak = "0.12.1"
@ -55,7 +55,7 @@ version = "0.4"
features = [ "full" ]
[dependencies.tower-http]
version = "0.2"
version = "0.3"
features = [ "full" ]
# os-specific dependencies

View File

@ -0,0 +1,565 @@
---
title: Crimes with Go Generics
date: 2022-04-24
tags:
- cursed
- golang
- generics
vod:
twitch: https://www.twitch.tv/videos/1465727432
youtube: https://youtu.be/UiJtaKYQnzg
---
Go 1.18 added [generics](https://go.dev/doc/tutorial/generics) to the
language. This allows you to have your types take types as parameters
so that you can create composite types (types out of types). This lets
you get a lot of expressivity and clarity about how you use Go.
However, if you are looking for good ideas on how to use Go generics,
this is not the post for you. This is full of bad ideas. This post is
full of ways that you should not use Go generics in production. Do not
copy the examples in this post into production. By reading this post
you agree to not copy the examples in this post into production.
I have put my code for this article [on my git
server](https://tulpa.dev/internal/gonads). This repo has been
intentionally designed to be difficult to use in production by me
taking the following steps:
1. I have created it under a Gitea organization named `internal`. This
will make it impossible for you to import the package unless you
are using it from a repo on my Gitea server. Signups are disabled
on that Gitea server. See
[here](https://go.dev/doc/go1.4#internalpackages) for more
information about the internal package rule.
1. The package documentation contains a magic comment that will make
Staticcheck and other linters complain that you are using this
package even though it is deprecated.
<xeblog-conv name="Mara" mood="hmm">What is that package
name?</xeblog-conv>
<xeblog-conv name="Cadey" mood="enby">It's a reference to
Haskell's monads, but adapted to Go as a pun.</xeblog-conv>
<xeblog-conv name="Numa" mood="delet">A gonad is just a gonoid in the
category of endgofunctors. What's there to be confused
about?</xeblog-conv>
<xeblog-conv name="Cadey" mood="facepalm">\*sigh\*</xeblog-conv>
## `Queue[T]`
To start things out, let's show off a problem in computer science that
is normally difficult. Let's make a MPMS (multiple producer, multiple
subscriber) queue.
First we are going to need a struct to wrap everything around. It will
look like this:
```go
type Queue[T any] struct {
data chan T
}
```
This creates a type named `Queue` that takes a type argument `T`. This
`T` can be absolutely anything, but the only requirement is that the
data is a Go type.
You can create a little constructor for `Queue` instances with a
function like this:
```go
func NewQueue[T any](size int) Queue[T] {
return Queue[T]{
data: make(chan T, size),
}
}
```
Now let's make some methods on the `Queue` struct that will let us
push to the queue and pop from the queue. They could look like this:
```go
func (q Queue[T]) Push(val T) {
q.data <- val
}
func (q Queue[T]) Pop() T {
return <-q.data
}
```
These methods will let you put data at the end of the queue and then
pull it out from the beginning. You can use them like this:
```go
q := NewQueue[string](5)
q.Push("hi there")
str := q.Pop()
if str != "hi there" {
panic("string is wrong")
}
```
This is good, but the main problem comes from trying to pop from an
empty queue. It'll stay there forever doing nothing. We can use the
`select` statement to allow us to write a nonblocking version of the
`Pop` function:
```go
func (q Queue[T]) TryPop() (T, bool) {
select {
case val := <-q.data:
return val, true
default:
return nil, false
}
}
```
However when we try to compile this, we get an error:
```
cannot use nil as T value in return statement
```
In that code, `T` can be _anything_, including values that may not be
able to be `nil`. We can work around this by taking advantage of the
`var` statement, which makes a new variable and initializes it to the
zero value of that type:
```go
func Zero[T any]() T {
var zero T
return zero
}
```
When we run the `Zero` function like
[this](https://go.dev/play/p/Z5tRs1-aKBU):
```go
log.Printf("%q", Zero[string]())
log.Printf("%v", Zero[int]())
```
We get output that looks like this:
```
2009/11/10 23:00:00 ""
2009/11/10 23:00:00 0
```
So we can adapt the `default` branch of `TryPop` to this:
```go
func (q Queue[T]) TryPop() (T, bool) {
select {
case val := <-q.data:
return val, true
default:
var zero T
return zero, false
}
}
```
And finally write a test for good measure:
```go
func TestQueue(t *testing.T) {
q := NewQueue[int](5)
for i := range make([]struct{}, 5) {
q.Push(i)
}
for range make([]struct{}, 5) {
t.Log(q.Pop())
}
}
```
## `Option[T]`
In Go, people use pointer values for a number of reasons:
1. A pointer value may be `nil`, so this can signal that the value may
not exist.
1. A pointer value only stores the offset in memory, so passing around
the value causes Go to only copy the pointer instead of copying the
value being passed around.
1. A pointer value being passed to a function lets you mutate values
in the value being passed. Otherwise Go will copy the value and you
can mutate it all you want, but the changes you made will not
persist past that function call. You can sort of consider this to
be "immutable", but it's not as strict as something like passing
`&mut T` to functions in Rust.
This `Option[T]` type will help us model the first kind of constraint:
a value that may not exist. We can define it like this:
```go
type Option[T any] struct {
val *T
}
```
Then you can define a couple methods to use this container:
```go
var ErrOptionIsNone = errors.New("gonads: Option[T] has no value")
func (o Option[T]) Take() (T, error) {
if o.IsNone() {
var zero T
return zero, ErrOptionIsNone
}
return *o.val, nil
}
func (o *Option[T]) Set(val T) {
o.val = &val
}
func (o *Option[T]) Clear() {
o.val = nil
}
```
Some other functions that will be useful will be an `IsSome` function
to tell if the `Option` contains a value. We can use this to also
implement an `IsNone` function that will let you tell if that `Option`
_does not_ contain a value. They will look like this:
```go
func (o Option[T]) IsSome() bool {
return o.val != nil
}
func (o Option[T]) IsNone() bool {
return !o.IsSome()
}
```
We can say that if an Option does not have something in it, it has
nothing in it. This will let us use `IsSome` to implement `IsNone`.
Finally we can add all this up to a `Yank` function, which is similar
to
[`Option::unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html)
in Rust:
```go
func (o Option[T]) Yank() T {
if o.IsNone() {
panic("gonads: Yank on None Option")
}
return *o.val
}
```
This will all be verified in a Go test:
```go
func TestOption(t *testing.T) {
o := NewOption[string]()
val, err := o.Take()
if err == nil {
t.Fatalf("[unexpected] wanted no value out of Option[T], got: %v", val)
}
o.Set("hello friendos")
_, err = o.Take()
if err != nil {
t.Fatalf("[unexpected] wanted no value out of Option[T], got: %v", err)
}
o.Clear()
if o.IsSome() {
t.Fatal("Option should have none, but has some")
}
}
```
<xeblog-conv name="Mara" mood="hmm">I think that
<code>Option[T]</code> will be the most useful outside of this post.
It will need some work and generalization, but this may be something
that the Go team will have to make instead of some random
person.</xeblog-conv>
## `Thunk[T]`
In computer science we usually deal with values and computations.
Usually we deal with one or the other. Sometimes computations can be
treated as values, but this is very rare. It's even more rare to take
a partially completed computation and use it as a value.
A thunk is a partially evaluated computation that is stored as a
value. For an idea of what I'm talking about, let's consider this
JavaScript function:
```javascript
const add = (x, y) => x + y;
console.log(add(2, 2)); // 4
```
This creates a function called `add` that takes two arguments and
returns one argument. This is great in many cases, but it makes it
difficult for us to bind only one argument to the function and leave
the other as a variable input. What if computing the left hand side of
`add` is expensive and only needed once?
Instead we can write `add` like this:
```javascript
const add = (x) => (y) => x + y;
console.log(add(2)(2)); // 4
```
This also allows us to make partially evaluated forms of `add` like
`addTwo`:
```javascript
const addTwo = add(2);
console.log(addTwo(3)); // 5
```
This can also be used with functions that do not take arguments, so
you can pass around a value that isn't computed yet and then only
actually compute it when needed:
```javascript
const hypotenuse = (x, y) => Math.sqrt(x * x + y * y);
const thunk = () => hypot(3, 4);
```
You can then pass this thunk to functions _without having to evaluate
it_ until it is needed:
```javascript
dominateWorld(thunk); // thunk is passed as an unevaluated function
```
We can implement this in Go by using a type like the following:
```go
type Thunk[T any] struct {
doer func() T
}
```
And then force the thunk to evaluate with a function such as `Force`:
```go
func (t Thunk[T]) Force() T {
return t.doer()
}
```
This works, however we can also go one step further than we did with
the JavaScript example. We can take advantage of the `Thunk[T]`
container to cache the result of the `doer` function so that calling
it multiple times will only actually it once and return the same
result.
<xeblog-conv name="Mara" mood="hacker">Keep in mind that this will
only work for _pure functions_, or functions that don't modify the
outside world. This isn't just global variables either, but any
function that modifies any state anywhere, including network and
filesystem IO.</xeblog-conv>
This would make `Thunk[T]` be implemented like this:
```go
type Thunk[T any] struct {
doer func() T // action being thunked
o *Option[T] // cache for complete thunk data
}
func (t *Thunk[T]) Force() T {
if t.o.IsSome() {
return t.o.Yank()
}
t.o.Set(t.doer())
return t.o.Yank()
}
func NewThunk[T any](doer func() T) *Thunk[T] {
return &Thunk[T]{
doer: doer,
o: NewOption[T](),
}
}
```
Now, for an overcomplicated example you can use this to implement the
Fibonacci function. We can start out by writing a naiive Fibonacci
function like this:
```go
func Fib(n int) int {
if n <= 1 {
return n
}
return Fib(n-1) + Fib(n-2)
}
```
We can turn this into a Go test in order to see how long it takes for
it to work:
```go
func TestRecurFib(t *testing.T) {
t.Log(Fib(40))
}
```
Then when we run `go test`:
```console
$ go test -run RecurFib
=== RUN TestRecurFib
thunk_test.go:15: 102334155
--- PASS: TestRecurFib (0.36s)
```
However, we can make this a lot more complicated with the power of the
`Thunk[T]` type:
```go
func TestThunkFib(t *testing.T) {
cache := make([]*Thunk[int], 41)
var fib func(int) int
fib = func(n int) int {
if cache[n].o.IsSome() {
return *cache[n].o.val
}
return fib(n-1) + fib(n-2)
}
for i := range cache {
i := i
cache[i] = NewThunk(func() int { return fib(i) })
}
cache[0].o.Set(0)
cache[1].o.Set(1)
t.Log(cache[40].Force())
}
```
And then run the test:
```
=== RUN TestThunkFib
thunk_test.go:36: 102334155
--- PASS: TestThunkFib (0.60s)
```
<xeblog-conv name="Mara" mood="hmm">Why is this so much slower? This
should be caching the intermediate values. Maybe something like this
would be faster? This should complete near instantly,
right?</xeblog-conv>
```go
func TestMemoizedFib(t *testing.T) {
mem := map[int]int{
0: 0,
1: 1,
}
var fib func(int) int
fib = func(n int) int {
if result, ok := mem[n]; ok {
return result
}
result := fib(n-1) + fib(n-2)
mem[n] = result
return result
}
t.Log(fib(40))
}
```
```console
$ go test -run Memoized
=== RUN TestMemoizedFib
thunk_test.go:35: 102334155
--- PASS: TestMemoizedFib (0.00s)
```
<xeblog-conv name="Cadey" mood="enby">I'm not sure
either.</xeblog-conv>
If you change the `fib` function to this, it works, but it also steps
around the `Thunk[T]` type:
```go
fib = func(n int) int {
if cache[n].o.IsSome() {
return *cache[n].o.val
}
result := fib(n-1) + fib(n-2)
cache[n].o.Set(result)
return result
}
```
This completes instantly:
```
=== RUN TestThunkFib
thunk_test.go:59: 102334155
--- PASS: TestThunkFib (0.00s)
```
To be clear, this isn't the fault of Go generics. I'm almost certain
that my terrible code is causing this to be much slower.
<xeblog-conv name="Numa" mood="delet">This is the power of gonads:
making easy code complicated, harder to reason about and slower than
the naiive approach! Why see this as terrible code when it creates an
amazing opportunity for cloud providers to suggest that people use
gonads' `Thunk[T]` so that they use more CPU and then have to pay cloud
providers more money for CPU! Think about the children!</xeblog-conv>
---
<xeblog-conv name="Cadey" mood="coffee">EDIT(2022 M04 25 05:56): amscanne on
Hacker News pointed out that my code was in fact wrong. My `fib` function should
have been a lot simpler.</xeblog-conv>
```go
fib = func(n int) int {
return cache[n-1].Force() + cache[n-2].Force()
}
```
<xeblog-conv name="Cadey" mood="facepalm">Applying this also makes the code run
instantly as I'd expect. I knew _something_ was _very wrong_, but I never
expected something this stupid. Thanks amscanne!</xeblog-conv>
<xeblog-conv name="Numa" mood="happy">Hey, it makes for good surrealism. If that
isn't a success, what is?</xeblog-conv>
---
I'm glad that Go has added generics to the language. It's certainly
going to make a lot of things a lot easier and more expressive. I'm
worried that the process of learning how to use generics in Go is
going to create a lot of churn and toil as people get up to speed on
when and where they should be used. These should be used in specific
cases, not as a bread and butter tool.
I hope this was an interesting look into how you can use generics in
Go, but again please don't use these examples in production.

View File

@ -75,6 +75,9 @@ nix = {
Then rebuild your system and you can continue along with the article.
<xeblog-conv name="Mara" mood="hacker">EDIT: You can use WSL for this. See
[here](/blog/nix-flakes-4-wsl-2022-05-01) for more information.</xeblog-conv>
If you are not on NixOS, you will need to either edit `~/.config/nix/nix.conf`
or `/etc/nix/nix.conf` and add the following line to it:

View File

@ -0,0 +1,334 @@
---
title: "Nix Flakes on WSL"
date: 2022-05-04
series: nix-flakes
tags:
- nixos
- wsl
vod:
youtube: https://youtu.be/VzQ_NwFJObc
twitch: https://www.twitch.tv/videos/1464781566
---
About five years ago, Microsoft released the Windows Subsystem for Linux
([WSL](https://docs.microsoft.com/en-us/windows/wsl/)). This allows you to run
Linux programs on a Windows machine. When they released WSL version 2 in 2019,
this added support for things like Docker and systemd. As a result, this is
enough to run NixOS on Windows.
<xeblog-conv name="Mara" mood="hacker">This will give you an environment to run
Nix and Nix Flakes commands with. You can use this to follow along with this
series without having to install NixOS on a VM or cloud server. This is going to
retread a bunch of ground from the first article. If you have been following
along through this entire series, once you get to the point where you convert
the install to flakes there isn't much more new material here.
</xeblog-conv>
## Installation
Head to the NixOS-WSL [releases
page](https://github.com/nix-community/NixOS-WSL/releases/) and download the
`nixos-wsl-installer-fixed.tar.gz` file to your Downloads folder.
Then open Powershell and make a folder called `WSL`:
```powershell
New-Item -Path .\WSL -ItemType Directory
```
<xeblog-conv name="Mara" mood="hacker">It's worth noting that Powershell does
have a bunch of aliases for common coreutils commands to the appropriate
Powershell CMDlets. However these aliases are <b>NOT</b> flag-compatible and use
the Powershell semantics instead of the semantics of the command it is aliasing.
This will bite you when you use commands like <code>wget</code> out of instinct
to download things. In order to avoid your muscle memory betraying you, the
Powershell CMDlets are shown here in their full overly verbose glory.
</xeblog-conv>
Then enter the directory with `Set-Location`:
```powershell
Set-Location -Path .\WSL
```
<xeblog-conv name="Mara" mood="hacker">This directory is where the NixOS root
filesystem will live. If you want to put this somewhere else, feel free to.
Somewhere in `%APPDATA%` will work, just as long as it's on an NTFS volume
somewhere.
</xeblog-conv>
Make a folder for the NixOS filesystem:
```powershell
New-Item -Path .\NixOS -ItemType Directory
```
Then install the NixOS root image with the `wsl` command:
```powershell
wsl --import NixOS .\NixOS\ ..\Downloads\nixos-wsl-installer-fixed.tar.gz --version 2
```
And start NixOS once to have it install itself:
```powershell
wsl -d NixOS
```
Once that finishes, press control-D (or use the `exit` command) to exit out of
NixOS and restart the WSL virtual machine:
```powershell
exit
wsl --shutdown
wsl -d NixOS
```
And then you have yourself a working NixOS environment! It's very barebones, but
we can use it to test the `nix run` command against our gohello command:
```console
$ nix run github:Xe/gohello
Hello reader!
```
## Local Services
We can also use this NixOS environment to run a local nginx server. Open
`/etc/nixos/configuration.nix`:
```nix
{ lib, pkgs, config, modulesPath, ... }:
with lib;
let
nixos-wsl = import ./nixos-wsl;
in
{
imports = [
"${modulesPath}/profiles/minimal.nix"
nixos-wsl.nixosModules.wsl
];
wsl = {
enable = true;
automountPath = "/mnt";
defaultUser = "nixos";
startMenuLaunchers = true;
# Enable integration with Docker Desktop (needs to be installed)
# docker.enable = true;
};
# Enable nix flakes
nix.package = pkgs.nixFlakes;
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
}
```
Right after the `wsl` block, add this nginx configuration to the file:
```nix
services.nginx.enable = true;
services.nginx.virtualHosts."test.local.cetacean.club" = {
root = "/srv/http/test.local.cetacean.club";
};
```
This will create an nginx configuration that points the domain
`test.local.cetacean.club` to the contents of the folder `/srv/http/test.local.cetacean.club`.
<xeblog-conv name="Mara" mood="hacker">The <code>/srv</code> folder is set aside
for site-specific data, which is code for "do whatever you want with this
folder". In many cases people make a separate <code>/srv/http</code> folder and
put each static subdomain in its own folder under that, however I am also told
that it is idiomatic to put stuff in <code>/var/www</code>. Pick your poison.
</xeblog-conv>
Then you can test the web server with the `curl` command:
```console
$ curl http://test.local.cetacean.club
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
```
This is good! Nginx is running and since we haven't created the folder with our
website content yet, this 404 means that it can't find it! Let's create the
folder so that nginx has permission to it and we can modify things in it:
```
sudo mkdir -p /srv/http/test.local.cetacean.club
sudo chown nixos:nginx /srv/http/test.local.cetacean.club
```
Finally we can make an amazing website. Open
`/srv/http/test.local.cetacean.club/index.html` in nano:
```
nano /srv/http/test.local.cetacean.club/index.html
```
And paste in this HTML:
```html
<title>amazing website xD</title>
<h1>look at my AMAZING WEBSITE</h1>
It's so cool *twerks*
```
<xeblog-conv name="Mara" mood="hacker">This doesn't have to just be artisanal
handcrafted HTML in bespoke folders either. You can set the <code>root</code> of
a nginx virtual host to point to a Nix package as well. This will allow you to
automatically generate your website somehow and deploy it with the rest of the
system. Including being able to roll back changes.</xeblog-conv>
And then you can see it show up with `curl`:
```console
$ curl http://test.local.cetacean.club
<title>amazing website xD</title>
<h1>look at my AMAZING WEBSITE</h1>
It's so cool *twerks*
```
You can also check this out in a browser by clicking
[here](http://test.local.cetacean.club):
![a browser window titled "amazing website xD" with the header "look at my
AMAZING WEBSITE" and content of "It's so cool
\*twerks\*"](https://cdn.christine.website/file/christine-static/blog/Screenshot+2022-04-23+141937.png)
## Installing `gohello`
To install the `gohello` service, first we will need to convert this machine to
use NixOS flakes. We can do that really quick and easy by adding this file to
`/etc/nixos/flake.nix`:
<xeblog-conv name="Mara" mood="happy">Do this as root!</xeblog-conv>
```nix
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, ... }: {
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
# add things here
];
};
};
}
```
Then run `nix flake check` to make sure everything is okay:
```
sudo nix flake check /etc/nixos
```
And finally activate the new configuration with flakes:
```
sudo nixos-rebuild switch
```
<xeblog-conv name="Mara" mood="hmm">Why don't you have the <code>--flake</code>
flag here? Based on what I read in the documentation, I thought you had to have
it there.</xeblog-conv>
<xeblog-conv name="Cadey" mood="enby"><code>nixos-rebuild</code> will
auomatically detect flakes in <code>/etc/nixos</code>. The only major thing it
cares about is the hostname matching. If you want to customize the hostname of
the WSL VM, change the <code>nixos</code> in
<code>nixosConfigurations.nixos</code> above and set
<code>networking.hostName</code> to the value you want to use. To use flakes
explicitly, pass <code>--flake /etc/nixos#hostname</code> to your
<code>nixos-rebuild</code> call.
</xeblog-conv>
After it thinks for a bit, you should notice that nothing happened. This is
good, we have just converted the system over to using Nix flakes instead of the
classic `nix-channel` rebuild method.
To get `gohello` in the system, first we need to add `git` to the commands
available on the system in `configuration.nix`:
```nix
environment.systemPackages = with pkgs; [ git ];
```
Then we can add `gohello` to our system flake:
```nix
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
# XXX(Xe): this URL may change for you, such as github:Xe/gohello-http
gohello.url = "git+https://tulpa.dev/cadey/gohello-http?ref=main";
};
outputs = { self, nixpkgs, gohello, ... }: {
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
# add things here
gohello.nixosModule
({ pkgs, ... }: {
xeserv.services.gohello.enable = true;
})
];
};
};
}
```
And rebuild the system with `gohello` enabled:
```
sudo nixos-rebuild switch
```
Finally, poke it with `curl`:
```console
$ curl http://gohello.local.cetacean.club
hello world :)
```
To update it, update the flake inputs in `/etc/nixos` and run `nixos-rebuild`:
```
sudo nix flake update /etc/nixos
sudo nixos-rebuild switch
```
---
And from here you can do whatever you want with NixOS. You can use
[containers](https://nixos.org/manual/nixos/stable/#ch-containers), set up
arbitrary services, or plan for world domination as normal.
<xeblog-conv name="Numa" mood="delet">I thought it was "to save the world from
devastation", not "to plan for world domination". Who needs a monopoly on
violence for world domination when you have Nix expressions?</xeblog-conv>
<xeblog-conv name="Cadey" mood="coffee">Siiiiiiiiiiiiiiiiiigh.</xeblog-conv>
I will use this setup in future posts to make this more accessible and easy to
hack at without having to have a dedicated NixOS machine laying around.

View File

@ -0,0 +1,474 @@
---
title: "robocadey: Shitposting as a Service"
date: 2022-04-30
tags:
- gpt2
- machinelearning
- python
- golang
- art
vod:
twitch: https://www.twitch.tv/videos/1471211336
youtube: https://youtu.be/UAd-mWMG198
---
<noscript>
[Hey, you need to enable JavaScript for most of the embedded posts in this
article to work. Sorry about this, we are working on a better solution, but this
is what we have right now.](conversation://Mara/hacker)
</noscript>
What is art? Art is when you challenge the assumptions that people make about a
medium and use that conflict to help them change what they think about that
medium. Let's take "Comedian" by Maurizio Cattelan for example:
![A banana duct-taped to an artist's
canvas](https://cdn.christine.website/file/christine-static/blog/merlin_165616527_d76f38fc-e45d-4913-9780-1cc939750197-superJumbo.jpg)
By my arbitrary definition above, this is art. This takes assumptions that you
have about paintings (you know, that they use paint on the canvas) and discards
them. This lets you change what you think art is. Art is not about the medium or
the things in it. Art is the expression of these things in new and exiting ways.
<xeblog-conv name="Cadey" mood="coffee">Originally I was going to use some
Banksky art here, but for understandable reasons it's quite difficult to get
images of Banksky art.</xeblog-conv>
One of my favorite kinds of art is the "uncanny valley" of realism. Let's take
Death Stranding as an example of this. Death Stranding is a video game that was
released in 2019 for the PlayStation 4 and is one of my favorite games of all
time. The game has a very hyper-realistic art style that is firmly in the
centre of the uncanny valley:
![A picture of Death Stranding gameplay, showing the protagonist Sam Porter
Bridges attempting to climb a sheer cliff face using a rope that another player
left
behind](https://cdn.christine.website/file/christine-static/blog/20220202215156_3.jpg)
This game mixes very realistic scenery with a story about dead bodies turning
into antimatter and you being a UPS delivery person that saves America. This is
art to me. This transformed what a video game could be, even if the entire game
boils down to Kojima themed fetch quests. Oh and trying not to die even though
you can't die but when you die it's really bad.
I want to create this kind of art, and I think I have found a good medium to do
this with. I write a lot on this little independent site called Twitter. This is
one of the main things that I write on, and through the process of the last 8
years or so, I've written a shockingly large amount of things. I post a lot of
weird things there as well as a lot of boring/normal things.
However a lot of my posts boil down to creating a "stream of consciousness", or
using it as a way to help deal with intrusive thoughts. There's a certain art to
this, as it is a candid exchange between the author and the reader. The reader
doesn't get all the context (heck, I doubt that I have all the context lol), but
from there they get to put the pieces together.
So, when thinking about trying to get into the uncanny valley with this kind of
art medium, my mind goes back to the old days on IRC channels. Many IRC channels
run bots to help them run the channel or purely for amusement. One of my
favorite kinds of bots is a [Markov
chain](https://en.wikipedia.org/wiki/Markov_chain) bot. These kinds of bots
learn patterns in text and then try to repeat them at random. With enough
training data, it can be fairly convincing at first glance. However, you need _a
lot_ of training data to get there. More training data than I have ever tweeted.
This ends up creating a situation where the markov bot is right in the uncanny
valley of realism. At first glance it is something that isn't not plausibly
human. It looks like a bot, but it also looks like a human, but it also looks
like a bot. It appears to be in the middle. I like this from an artistic
standpoint because this challenges your assumptions that bots need to be
obviously bots and humans need to be obviously human.
In the past I have ran a service I call `cadeybot`. It took all of my Discord
messages, fed them into a Markov chain, and then attempted to create new
messages as a result. This worked pretty well, but we ran into an issue where it
would basically regurgitate its training data. So when people thought it was
being novel about roasting people, someone would search the chat and find out
that I said those exact words 2 years ago.
This isn't really exciting from an artistic point of view. You could get the
same result from randomly replying with old chat messages without any additional
data in the mix.
I haven't run `cadeybot` in some time because of this. It gets really boring
really fast.
However, I was looking at some DALL-E generated images and then inspiration
struck:
<xeblog-conv name="Mara" mood="hmm">What if I fed all those tweets into
[GPT-2](https://en.wikipedia.org/wiki/GPT-2)?</xeblog-conv>
So I did that. I made [@robocadey@botsin.space](https://botsin.space/@robocadey)
as a fediverse bot that generates new content based on everything I've ever
tweeted.
<iframe src="https://botsin.space/@robocadey/108219835651549836/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="500"
height="245" allowfullscreen="allowfullscreen"></iframe>
## Data
The first step of this is getting all of my tweet data out of Twitter. This
was a lot easier than I thought. All I had to do was submit a GDPR data request,
wait a few days for the cloud to think and then I got a 3 gigabyte zip file full
of everything I've ever tweeted. Cool!
Looking through the dump, I found a 45 megabyte file called `tweets.js`. This
looked like it could be important! So I grabbed it and looked at the first few
lines:
```javascript
$ head tweet.js
window.YTD.tweet.part0 = [
{
"tweet" : {
"retweeted" : false,
"source" : "<a href=\"http://www.bitlbee.org/\" rel=\"nofollow\">BitlBee</a>",
"entities" : {
"hashtags" : [ ],
"symbols" : [ ],
"user_mentions" : [
{
```
So it looks like most of this is really just a giant block of data that's
stuffed into JavaScript so that the embedded HTML can show off everything you've
ever tweeted. Neat, but I only need the tweet contents. We can strip off the
preamble with `sed`, and then grab the first entry out of `tweets.js` with a
command like this:
```json
$ cat tweet.js | sed 's/window.YTD.tweet.part0 = //' | jq .[0]
{
"tweet": {
"retweeted": false,
"source": "<a href=\"http://www.bitlbee.org/\" rel=\"nofollow\">BitlBee</a>",
"entities": {
"hashtags": [],
"symbols": [],
"user_mentions": [
{
"name": "@Lyude@queer.party🌹",
"screen_name": "_Lyude",
"indices": [
"0",
"7"
],
"id_str": "1568160860",
"id": "1568160860"
}
],
"urls": []
},
"display_text_range": [
"0",
"83"
],
"favorite_count": "0",
"in_reply_to_status_id_str": "481634023295709185",
"id_str": "481634194729488386",
"in_reply_to_user_id": "1568160860",
"truncated": false,
"retweet_count": "0",
"id": "481634194729488386",
"in_reply_to_status_id": "481634023295709185",
"created_at": "Wed Jun 25 03:05:15 +0000 2014",
"favorited": false,
"full_text": "@_Lyude but how many licks does it take to get to the centre of a tootsie roll pop?",
"lang": "en",
"in_reply_to_screen_name": "_Lyude",
"in_reply_to_user_id_str": "1568160860"
}
}
```
It looks like most of what I want is in `.tweet.full_text`, so let's make a
giant text file with everything in it:
```sh
sed 's/window.YTD.tweet.part0 = //' < tweets.js \
| jq '.[] | [ select(.tweet.retweeted == false) ] | .[].tweet.full_text' \
| sed -r 's/\s*\.?@[A-Za-z0-9_]+\s*//g' \
| grep -v 'RT:' \
| jq --slurp . \
| jq -r .[] \
| sed -e 's!http[s]\?://\S*!!g' \
| sed '/^$/d' \
> tweets.txt
```
This does a few things:
1. Removes that twitter preamble so jq is happy
2. Removes all at-mentions from the training data (so the bot doesn't go on a
mentioning massacre)
3. Removes the "retweet" prefixed tweets from the dataset
4. Removes all urls
5. Removes all blank lines
This should hopefully cut out all the irrelevant extra crap and let the machine
learning focus on my text, which is what I actually care about.
## Getting It Up
As a prototype, I fed this all into Markov chains. This is boring, but I was
able to graft together a few projects to get that prototype up quickly. After
some testing, I ended up with things like this:
<iframe src="https://botsin.space/@robocadey/108201675365283068/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="500"
height="225" allowfullscreen="allowfullscreen"></iframe>
This was probably the best thing to come out of the Markov chain testing phase,
the rest of it was regurgitating old tweets.
While I was doing this, I got GPT-2 training thanks to [this iPython
notebook](https://colab.research.google.com/github/sarthakmalik/GPT2.Training.Google.Colaboratory/blob/master/Train_a_GPT_2_Text_Generating_Model_w_GPU.ipynb).
I uploaded my 1.5 megabyte tweets.txt file and let the big pile of linear
algebra mix around for a bit.
Once it was done, I got a one gigabyte tarball that I extracted into a new
folder imaginatively named `gpt2`. Now I had the model, all I needed to do was
run it. So I wrote some Python:
```python
#!/usr/bin/env python3
import gpt_2_simple as gpt2
import json
import os
import socket
import sys
from datetime import datetime
sockpath = "/xe/gpt2/checkpoint/server.sock"
sess = gpt2.start_tf_sess()
gpt2.load_gpt2(sess, run_name='run1')
if os.path.exists(sockpath):
os.remove(sockpath)
sock = socket.socket(socket.AF_UNIX)
sock.bind(sockpath)
print("Listening on", sockpath)
sock.listen(1)
while True:
connection, client_address = sock.accept()
try:
print("generating shitpost")
result = gpt2.generate(sess,
length=512,
temperature=0.8,
nsamples=1,
batch_size=1,
return_as_list=True,
top_p=0.9,
)[0].split("\n")[1:][:-1]
print("shitpost generated")
connection.send(json.dumps(result).encode())
finally:
connection.close()
server.close()
os.remove("/xe/gpt2/checkpoint/server.sock")
```
And I used a Dockerfile to set up its environment:
```Dockerfile
FROM python:3
RUN pip3 install gpt-2-simple
WORKDIR /xe/gpt2
COPY . .
CMD python3 main.py
```
Then I bind-mounted the premade model into the container and asked it to think
up something for me. I got back a list of replies and then I knew it was good to
go:
```json
[
"oh dear. I don't know if you're the best mannered technologist you've come to expect from such a unique perspective. On the technical side of things, you're a world-class advocate for open source who recently lost an argument over the state of the open source world to bitter enemies like Python.",
"I also like your approach to DNS! One step at a time. More info here: ",
"tl;dr: it's a bunch of random IP addresses and the outcome is a JSON file that you fill out in as you go.",
"datasoftware.reddit.com/r/programmingcirclejerk-memes",
"datasoftware.reddit.com/r/programmingcirclejerk-memes",
"datasoftware.reddit.com/r/programmingcirclejerk-memes",
"datasoftware.reddit.com/r/programmingcirclejerk-memes",
"Oh dear, can we third-person?",
"A group of us is a CVE-1918 impact statement",
"Is that breaking news?",
"Lol datasom shitposting omg ",
"I'm gonna be on the list for #Giving is easy, don't look so far ahead ",
"Oh dear. Welcome to ThePandora: ",
"I use a lot of shift lol",
"I thought you were an orca",
"Foone, my old computer crashed. What happened to your hard drive? ",
"Yeah I know some of those things should be automated, but this is about experimentation and experimentation is what makes me happy",
"Am I? ",
"Experiment is my favorite part of the article",
"Yes I can, scroll past the how to read words videos",
"I was able to see into space but I cannot seen into your eyes",
"This is with a virtual keyboard/MAC address field",
"Yes but with the keymap \"~M\"",
"Yes this is a structural change, I am trying to tease things out a bit. I am trying to make it slightly different sounding with the key mapping. I am trying to make it different sounding sounding.",
"The main thing I am trying to do is make it easy to type backwards. This is going to take experimentation. I am trying to make it slightly different sounding.",
"Is this vehicle of mercy?",
"God i forgot "
]
```
However, this involved using Docker. Docker is decent, but if I have the ability
not to, I don't want to use Docker. A friend of mine named `ckie` saw that I was
using Docker for this and decided to package the `gpt_2_simple` library [into
nixpkgs](https://github.com/NixOS/nixpkgs/pull/170713). They also made it easy
for me to pull it into robocadey's environment and then I ripped out Docker,
never to return.
Now the bot could fly. Here was the first thing it posted after it got online
with GPT-2 in a proper way:
<iframe src="https://botsin.space/@robocadey/108209326706890695/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="500"
height="175" height=allowfullscreen="allowfullscreen"></iframe>
I can't make this up.
## Art Gallery
Here are some of my favorite posts it's made. Most of them could pass off as my
tweets.
<iframe src="https://botsin.space/@robocadey/108209924883002812/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="190" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108212424672000652/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="190" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108215827551779879/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="210" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108218889999336372/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="210" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108218894030986305/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="800"
height="250" allowfullscreen="allowfullscreen"></iframe>
Some of them get somber and are unintentionally a reflection on the state of the
world we find ourselves in.
<iframe src="https://botsin.space/@robocadey/108219835651549836/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="280" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108218522810351900/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="280" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108217161432474717/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="345" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108216170547691864/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="280" allowfullscreen="allowfullscreen"></iframe>
Others are silly.
<iframe src="https://botsin.space/@robocadey/108217116321450713/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="200" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108218107689729996/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="200" allowfullscreen="allowfullscreen"></iframe>
<iframe src="https://botsin.space/@robocadey/108215257978801615/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="180" allowfullscreen="allowfullscreen"></iframe>
I say things like this:
<iframe src="https://pony.social/@cadey/108218301565484230/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
allowfullscreen="allowfullscreen"></iframe><script
src="https://pony.social/embed.js" async="async"></script>
and it fires back with:
<iframe src="https://botsin.space/@robocadey/108218304118515023/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="180" allowfullscreen="allowfullscreen"></iframe>
This is art. It looks like a robot pretending to be a human and just barely
passing at it. This helps you transform your expectations about what human and
bot tweets really are.
<iframe src="https://botsin.space/@robocadey/108213387014890181/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="200" allowfullscreen="allowfullscreen"></iframe>
If you want to influence `robocadey` into giving you an artistic experience,
mention it on the fediverse by adding `@robocadey@botsin.space` to your posts.
It will think a bit and then reply with a brand new post for you.
## Setting It Up
You probably don't want to do this, but if you're convinced you do then here's
some things that may help you.
1. Use the systemd units in `/run` of [github:Xe/x](https://github.com/Xe/x).
2. Put your model into a squashfs volume that you mount to the
`/var/lib/private/xeserv.robocadey-gpt2/checkpoint` folder.
3. Don't expect any warranty, reliability promises or assistance setting this
up. I made this for myself, not for others. Its source code is made available
to make the code part of that art, but the code is not the art that it makes.
Good luck.
---
I guess what I think about art is that it's not just the medium. It's not just
the expression. It's the combination of it all. The expression, the medium, the
circumstances, all of that leads into what art really is. I could say that art
is the intangible expressions, emotions, and whatever that you experience when
looking at things; but that sounds really really pretentious, so let's just say
that art doesn't exist. Well it does, but only in the mind of the viewer.
There's not some objective scale that can say that something is or is not an
art. Art is imagined and we are conditioned to believe that things are or are
not art based on our upbringing.
I feel that as a shitposter my goal is to challenge people's "objective sense"
of what "can" and "can't" be art by sitting right in the middle of the two and
laughing. Projects like `robocadey` are how I make art. It's like what 200 lines
of code at most. You could probably recreate most of it based on the contents of
this post alone. I wonder if part of the art here comes from the fact that most
of this is so iterative yet so novel. Through the iteration process I end up
creating novelty.
You could also say that art is the antidote to the kind of suffering that comes
from the fundamental dissatisfactions that people have with everyday life. By
that defintion, I think that `robocadey` counts as art.
Either way, it's fun to do these things. I hope that this art can help inspire
you to think differently about the world. Even though it's through a chatbot
that says things like this:
<iframe src="https://botsin.space/@robocadey/108215945151030016/embed"
class="mastodon-embed" style="max-width: 100%; border: 0" width="400"
height="200" allowfullscreen="allowfullscreen"></iframe>
What is this if not art?

View File

@ -0,0 +1,58 @@
---
title: "Twitter, Mastodon and The Parable of rasengan"
date: 2022-04-25
tags:
- twitter
- reaction
---
So a lot of things happened today. The threat that Elon Musk made to
buy Twitter seems to have been true. As I write this, my current
understanding is that the Twitter board of directors is in the process
of accepting the offer that Elon Musk to buy the company.
<xeblog-conv name="Cadey" mood="coffee">This was not on my bingo card
for 2022. I'm starting to think that I got a dud bingo card. I was
hoping that "Alien Invasion" would win out but it looks like that
won't be the case yet. Damn.</xeblog-conv>
I genuinely have no idea how I should be reacting to this news. I
spend a lot of time on Twitter. It's a lot of how I talk with people,
network in the tech community and generally shitpost. I do so much
idle shitposting on Twitter that it would probably count for a lot of
the non-work written word I produce on a weekly basis.
I'm really not sure what I feel about this, but the feelings that I am
getting remind me of [what happened to
freenode](/blog/series/freenode). A while ago someone I follow on
Twitter made a tweet that said something like:
> Elon Musk is to Twitter as rasengan is to freenode
<xeblog-conv name="Mara" mood="hacker">For context: rasengan is the
person that single-handedly destroyed freenode by apparently buying a
worthless holding company and then inciting a lot of
drama. For more information, check out the book Closed
Projects.</xeblog-conv>
I have made my book [Closed
Projects](/blog/closed-projects-2022-03-24) free for the next week.
This contains my moods, reactions and emotions as I was processing
everything falling apart around me. I feel this may help you
understand the emotions that you get watching this shitshow unfold
with Twitter.
For a direct link to the sale page, click
[here](https://itch.io/s/69916/elon-bought-twitter-sale). I will
donate proceeds from this sale to charity. If you choose to send me a
couple bucks for my book, I will donate them to the [Orca
Conservancy](https://www.orcaconservancy.org/).
I am also on Mastodon at
[@cadey@pony.social](https://pony.social/@cadey). Should something
happen to Twitter such that I can't participate there anymore, I will
be on Mastodon. If Twitter really starts falling apart, I will
probably be a lot more active on Mastodon. And probably writing a lot
more.
Let's hope things turn out well.

View File

@ -35,11 +35,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1650647760,
"narHash": "sha256-Ng8CGYLSTxeI+oEux0x+tSRA6K7ydoyfJNQf56ld+Uo=",
"lastModified": 1651114127,
"narHash": "sha256-/lLC0wkMZkAdA5e1W76SnJzbhfOGDvync3VRHJMtAKk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b80f570a92d04e8ace67ff09c34aa48708a5c88c",
"rev": "6766fb6503ae1ebebc2a9704c162b2aef351f921",
"type": "github"
},
"original": {
@ -49,11 +49,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1650161686,
"narHash": "sha256-70ZWAlOQ9nAZ08OU6WY7n4Ij2kOO199dLfNlvO/+pf8=",
"lastModified": 1651007983,
"narHash": "sha256-GNay7yDPtLcRcKCNHldug85AhAvBpTtPEJWSSDYBw8U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1ffba9f2f683063c2b14c9f4d12c55ad5f4ed887",
"rev": "e10da1c7f542515b609f8dfbcf788f3d85b14936",
"type": "github"
},
"original": {

View File

@ -15,6 +15,6 @@ tracing = "0.1"
tracing-futures = "0.2"
[dev-dependencies]
eyre = "0.6.6"
eyre = "0.6.8"
kankyo = "0.3"
tokio = { version = "1", features = ["full"] }