some more notes and copied over blog posts now that I figured out metadata issue
This commit is contained in:
parent
22d540f4ce
commit
6e03d129e8
20 changed files with 1705 additions and 1 deletions
8
notes
8
notes
|
@ -14,7 +14,13 @@ https://fonts.google.com/knowledge/using_type/implementing_open_type_features_on
|
|||
https://fonts.google.com/knowledge/choosing_type/pairing_typefaces
|
||||
https://fonts.google.com/knowledge/choosing_type/pairing_typefaces_within_a_family_superfamily
|
||||
|
||||
https://web.dev/articles/optimize-webfont-loading
|
||||
|
||||
noted fonts:
|
||||
Hanuman https://fonts.google.com/specimen/Hanuman
|
||||
TT2020 https://www.fontspace.com/tt2020-font-f42044
|
||||
"Zilla Slab", Inter, X-LocaleSpecific, sans-serif
|
||||
https://blog.mozilla.org/opendesign/zilla-slab-common-language-shared-font/
|
||||
Bookerly
|
||||
"Source Sans Pro", "Lucida Sans Unicode", "Helvetica", "Trebuchet MS", sans-serif, "Noto Emoji", "Quivira"
|
||||
Palatino,"Palatino Linotype","Palatino LT STD",serif
|
||||
|
@ -23,4 +29,4 @@ Courier New', monospace;
|
|||
line-height: 1.5;
|
||||
https://fonts.google.com/specimen/Marcellus?preview.size=79&stroke=Serif
|
||||
https://fonts.google.com/specimen/Tinos?preview.size=79&stroke=Serif
|
||||
https://fonts.google.com/noto/specimen/Noto+Serif+Display?classification=Display&stroke=Serif&stylecount=18&preview.text=Hello%20there
|
||||
Noto serif: https://fonts.google.com/noto/specimen/Noto+Serif+Display?classification=Display&stroke=Serif&stylecount=18&preview.text=Hello%20there
|
||||
|
|
216
posts/HackBU2024.md
Normal file
216
posts/HackBU2024.md
Normal file
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
|
||||
title: "Bits bobs and notes from HackBU 2024"
|
||||
|
||||
description: "A summary of my experience, lessons and thoughts on the HackBU 2024 hackathon"
|
||||
|
||||
date: "2024-02-20"
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# Hi :)
|
||||
|
||||
Over the last weekend I went to [HackBU 2024](https://hackbu.org/2024/). This blog post is me writing about it (maybe not fully coherently). Also as an aside I went to the 2023 hackathon as well but I didn't write about it, oh well.
|
||||
|
||||
## A reminder that I can do things quickly
|
||||
|
||||
As with last year<sup><a href="#1">1</a></sup> I worked on a project solo. Also like last year I was able to successfully get out a prototype of that project though unlike last year the prototype didn't completely work. But I'm getting ahead of myself, I should probably describe what I built before going into detail about my dissapointments in it.
|
||||
|
||||
## What I built
|
||||
|
||||
In Binghamton there are 2 bus systems, 1 is provided by the county and the other is provided by the university. The university buses were not in google maps meaning google maps wouldn't show routes involving them. As such I was going to build a system to make it easy to get a route using either or both bus systems.
|
||||
|
||||
This might seem ambitious at first but it was actually quite simple, all I had to do was reverse engineer 2 live maps to get the data on bus routes from their apis, use google's route api to get the travel time of the buses through their routes, calculate the best route from point A to point B with the retrieved bus routes and learn google's map api to visualize the data and build a simple frontend to overlay on that for the user to give input into...
|
||||
|
||||
I swear it sounds harder than it was.
|
||||
|
||||
## Oops a bit too much scope
|
||||
|
||||
Did I mention that I was planning on being even more ambitious than what I just described by using the live bus positions and past history to try and calculate when a bus would arrive at a particular bus stop and that point A and point B would've been proper addresse if I hadn't cut back on scope?
|
||||
|
||||
So yeah before anything I wasted an hour or 2 working on setting up an ORM that I didn't use. But after that I got to work on useful stuff.
|
||||
|
||||
## Work begins
|
||||
|
||||
First things first, I had to get the data. Now you'd think that reverse engineering a bus live map would be hard but as it turns out it's pretty easy at least for what I'm doing. It was literally just
|
||||
|
||||
1. go to live map website
|
||||
|
||||
2. open up the network tab of the browser dev tools
|
||||
|
||||
3. refresh the page and search for the words "bus", "route" and "stop" in the requests
|
||||
|
||||
4. click on the obvious results and use brain to figure out what json fields like "name" and "stops" and "lat" and "lon" could possibly mean
|
||||
|
||||
conveniently the Hackathon can't really prevent prior work that isn't code so all of the api reverse engineering was done the day before the hackathon so the time wasted on ORM stuff canceled out.
|
||||
|
||||
## Why yes I do prefer non-linear story telling
|
||||
|
||||
That reminds me I should probably mention why I was working on this solo as well as what I'm even using to build it. So unlike last year I did try a little bit more to get a group to work on something with but none of the other ideas were interesting and the people I was hoping to group with assumed I'd be fine on my own. Which they were right but I'd have liked the help if only so I could've increase the scope a bit.
|
||||
|
||||
But yeah once it was clear I'd be working on my own I decided to go with a language I was comfortable with and that I knew had all the tools I needed. That language being [Rust](https://www.rust-lang.org/). [Tide]() for the http server backend, [Reqwest]() for making http requests to various apis, [SerDe]() for serializing and deserializing json, and some other libraries which aren't interesting to list out<sup><a href="#2">2</a></sup>.
|
||||
## Corners cut
|
||||
|
||||
I'm not going to talk about the overall development process because it's boring and mostly obvious stuff. However due to being solo and only having 24 hours I did need to cut some corners.
|
||||
|
||||
First at the start of actual work I only expected to get an api done but no frontend, however the main bulk of the api was done before I started really getting tired so I had plenty of time to get a frontend with google maps out.
|
||||
|
||||
However I did have to cut many corners for finding an optimal route. Firstly I didn't do a graph search at all. If the optimal path used more than 2 buses or had more than 1 bus without stopping at either the university or the greyhound station then my system wouldn't find it. The reason for this was because my system only checked 3 types of route to get from where you started to your destination. Single bus, bus to Binghamton university then transfer to another bus and bus to the greyhound station then transfer to another bus. This was because having used the buses myself I know that those 3 methods will work pretty well for getting you from point A to point B and doing a proper search seemed like a lot of work.
|
||||
|
||||
Another corner I cut was on the heuristic for how good I considered a route. A good heuristic would take into acount walking distance, waiting time and bus transit time. My heuristic was to minimize euclidean distance from the starting position to the bus stop added to the distance between the bus stop they got off at to the destination. Which leads to both obvious and subtle incorrectness in measuring how good routes are but it works well enough so whatever.
|
||||
|
||||
An accidental corner I cut was that uuuh I might have forgotten/ran out of time to put logic in to make sure we aren't trying to go backwards along a bus route. 90% of the time this doesn't matter though so eeeh.
|
||||
|
||||
The time estimate for travel is divided by 3. I don't know why Google Routes gave me time estimates which were higher than necessary.
|
||||
|
||||
I was going to deploy this with docker/docker-compose instead of messing with CORS but more on that in the stories.
|
||||
|
||||
Broome county buses visually have straight lines between the bus stops instead of following the road. I'll talk about this a bit more when I get into the stories but for now all you need to know is that the reverse engineered live map doesn't give me the path and using google routes was something I thought I didn't have time for until right now as I'm writing this... Fuck.
|
||||
|
||||
## Story time
|
||||
|
||||
### The fucking s
|
||||
|
||||
I haven't run into someone who's tried to claim Google is really good at software yet but if I do I will bring this up. So when I was using the google routes api to figure out how long it would take I noticed that the time format looked something like "250s", so for about a minute I was panicking because "oh god am I going to have to parse out time units and standardize it" but after sending a request for a route frome LA to NYC I got back another time with an s so it's in seconds but dammit if google's documentation doesn't say that.
|
||||
|
||||
### prematurely closes your connection, refuses to elaborate
|
||||
|
||||
Here's a docker compose file
|
||||
|
||||
```yml
|
||||
version: "3.3"
|
||||
services:
|
||||
backend:
|
||||
build: .
|
||||
ports:
|
||||
- 9090:80
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
build: BBB_frontend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:80
|
||||
depends_on:
|
||||
- backend
|
||||
```
|
||||
and here's an nginx config
|
||||
```
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
location /api/ {
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_pass http://backend/;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
see any problems? no? Neither do I, no idea why I...
|
||||
|
||||
```
|
||||
2024/02/18 07:57:20 [error] 30#30: *1 upstream prematurely closed connection while reading response header from upstream, client: 172.31.0.1, server: localhost, request: "GET /api/ HTTP/1.1", upstream: "http://172.31.0.2:80/", host: "localhost:8080"
|
||||
```
|
||||
|
||||
Oh yeah that's right, I got this error when I tried to set this up in docker-compose and I still have no idea why. I can only guess that something fucked up is going on between Tide and Nginx, oh well that wasted crucial time that could've been better spent noticing and fixing
|
||||
|
||||
### The Polyline encoding fuckup
|
||||
|
||||
Okay I didn't say this outright before so I'll say it now. Google's documentation sucks [here](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)'s the page describing the polyline encoding in the off chance that's a dead link here's the part that I read, assuming that the rest was context I didn't need
|
||||
```
|
||||
The steps for encoding such a signed value are specified below.
|
||||
|
||||
1. Take the initial signed value:
|
||||
-179.9832104
|
||||
2. Take the decimal value and multiply it by 1e5, rounding the result:
|
||||
-17998321
|
||||
3. Convert the decimal value to binary. Note that a negative value must be calculated using its two's complement by inverting the binary value and adding one to the result:
|
||||
00000001 00010010 10100001 11110001
|
||||
11111110 11101101 01011110 00001110
|
||||
11111110 11101101 01011110 00001111
|
||||
4. Left-shift the binary value one bit:
|
||||
11111101 11011010 10111100 00011110
|
||||
5. If the original decimal value is negative, invert this encoding:
|
||||
00000010 00100101 01000011 11100001
|
||||
6. Break the binary value out into 5-bit chunks (starting from the right hand side):
|
||||
00001 00010 01010 10000 11111 00001
|
||||
7. Place the 5-bit chunks into reverse order:
|
||||
00001 11111 10000 01010 00010 00001
|
||||
8. OR each value with 0x20 if another bit chunk follows:
|
||||
100001 111111 110000 101010 100010 000001
|
||||
9. Convert each value to decimal:
|
||||
33 63 48 42 34 1
|
||||
10. Add 63 to each value:
|
||||
96 126 111 105 97 64
|
||||
11. Convert each value to its ASCII equivalent:
|
||||
`~oia@
|
||||
```
|
||||
|
||||
here's what I wrote trying to implement that
|
||||
```rs
|
||||
fn enc_float(num:f64)->String{
|
||||
let mut working:i32 = (num*1e5).round() as i32;
|
||||
//hopethis does what's needed
|
||||
working<<=1;
|
||||
if num < 0.0 {
|
||||
working = !working;
|
||||
}
|
||||
let mut bits:[bool;30] = [false;30];
|
||||
for i in 0..30{
|
||||
bits[i] = working % 2 == 1;
|
||||
working >>=1;
|
||||
}
|
||||
bits.chunks(5).rev()
|
||||
.map(|bools|{
|
||||
let mut accu:u8 = 0;
|
||||
for i in 0..5{
|
||||
accu += if bools[4-i]{
|
||||
1
|
||||
} else {0};
|
||||
accu <<=1;
|
||||
}
|
||||
accu |= 0x20;
|
||||
accu +=63;
|
||||
char::from(accu)
|
||||
}).collect::<String>()
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
nothing about this is obviously wrong although if you read the instructions I showed (and not the blurbs above and below) carefully there's two mistake that I made. First I didn't encode all 30 bits I needed, I only got 25 and second I Or'd every bit chunk with 0x20 rather than all but the last one. In my opinion that bit of the documentation is worded badly "OR each value with 0x20 if another bit chunk follows", compared to "OR all but the last value with 0x20" but that's not my main complaint. My main complaint is that they have step by step instructions which I just showed **in addition** to a critical paragraph block above it which I skipped due to convention being that if you have a step by step guide in either documentation or a tutorial that everything that needs to be done is contained within those steps. I've copied the critical paragraph below with important bit that I messed up boldened.
|
||||
|
||||
> The encoding process converts a binary value into a series of character codes for ASCII characters using the familiar base64 encoding scheme: to ensure proper display of these characters, encoded values are summed with 63 (the ASCII character '?') before converting them into ASCII. The algorithm also checks for additional character codes for a given point by checking the least significant bit of each byte group; if this bit is set to 1, the point is not yet fully formed and additional data must follow.
|
||||
> Additionally, to conserve space, **points only include the offset from the previous point** (except of course for the first point). All points are encoded in Base64 as signed integers, as latitudes and longitudes are signed values. The encoding format within a polyline needs to represent two coordinates representing latitude and longitude to a reasonable precision. Given a maximum longitude of +/- 180 degrees to a precision of 5 decimal places (180.00000 to -180.00000), this results in the need for a 32 bit signed binary integer value.
|
||||
|
||||
It's also boldened on the page itself but regardless I skipped over it.
|
||||
|
||||
You may be wondering why this matters, why was I implementing polyline and the answer is so I could draw on google maps, and yeah surprise because of that Broome county buses don't show up because I did this wrong. The reason I didn't fix it was because I wasn't able to find out until about 3 hours before submission and didn't notice for the first 1-2 of those hours due to a mixture of sleep deprivation and eating breakfast.
|
||||
|
||||
1 - I built a CI system called [Romance]() last year which has a separate repo with the [frontend](), and it needs even more duct tape and dreams than this years project if you want it to work properly
|
||||
|
||||
2 - [chrono](), [async-std](), and [anyhow]() and I put in and then took out [geo-types](), [tokio]() and [sea-orm]()
|
33
posts/Rust_CPP_comp.md
Normal file
33
posts/Rust_CPP_comp.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
|
||||
title: "Comparing Rust and C++"
|
||||
|
||||
description: "A post on how I view rust and C++ in relation to each other and my thoughts on them"
|
||||
|
||||
date: "2024-04-11"
|
||||
|
||||
draft: true
|
||||
|
||||
---
|
||||
|
||||
# Less black and white than the hype suggests
|
||||
|
||||
C++ is a flawed language but I think the hype around Rust obscures the ways in which it can be decent. So I want to write about that while also simping on Rust by pointing out that it makes doing this stuff the default meanwhile C++ at best has other options that for a noob are more obvious.
|
||||
|
||||
## Move semantics, references and smart pointers
|
||||
|
||||
C++ has move semantics and references and you can use that to write code that performs similar things to what Rust does.
|
||||
|
||||
```cpp
|
||||
auto x = std::make_unique<std::int32_t>(3);
|
||||
// need to be explicit with std::move but still move semantics, if you know rust then unique_ptr is Box
|
||||
std::unique_ptr<std::int32_t> y = std::move(x);
|
||||
|
||||
// this is an implicit call to a method, Rust would require that you use String::from
|
||||
std::string s = "hi";
|
||||
|
||||
// without std::move this would copy over the contents of s which could be slow, Rust would do the move implicitly unless you called clone
|
||||
std::string s2 = std::move(s);
|
||||
```
|
||||
|
||||
which considering all the pre-existing C++ code that can do stuff like this even if it was written before C++ had smart pointes, RAII or references.
|
4
posts/_index.md
Normal file
4
posts/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: "Blogs"
|
||||
description: some blogs I've posted
|
||||
---
|
127
posts/finite_KCMP_nums.md
Normal file
127
posts/finite_KCMP_nums.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
|
||||
title: "Finite KCMP numbers"
|
||||
|
||||
description: "Fuck it my brain has a bit too much free time so why not figure out a proof for an isomorphism between programs writen in a turing complete language and natural numbers and use it to do fun stuff"
|
||||
|
||||
date: 2022-12-20
|
||||
|
||||
draft: true
|
||||
|
||||
---
|
||||
|
||||
# Finite KCMP numbers
|
||||
|
||||
So this blog article exists because I have too much time and realized that numbers with a finite KCMP have an isomorphism to the natural numbers among some other interesting stuff and I wanna write a blog article about it.
|
||||
|
||||
> Huh? KCMP? Natural numbers? Isomorphism? the fuck?
|
||||
|
||||
## An initial explanation
|
||||
|
||||
Okay maybe I should start with some explaining, KCMP is shorthand that me and a friend(their name is Micha, here's their [blog](https://lochalhost.pl/en/blog) and here's their [github](https://github.com/michalusio)) use when referring to [Kolmogorov complexity](https://en.wikipedia.org/wiki/Kolmogorov_complexity), Kolmogorov complexity is the term used to describe the length of the shortest program written in a turing complete language needed to calculate a particular value. From here on however I'm gonna write KCMP because that's shorter. If you've never heard of KCMP I don't blame you, until Micha brought the term to my attention I wasn't sure what I was thinking of had a word.
|
||||
|
||||
> Wait you were thinking of KCMP before you even had a word for it?
|
||||
|
||||
Yup, you see some months ago I started this journey with a relatively simple question "are there numbers which we can't calculate?". The answer is yes there are numbers that we cannot calculate ever these numbers are irrational numbers with infinite KCMP. For a while that was that but before we go on I should probably make sure you know what I mean by isomorphism and natural numbers.
|
||||
|
||||
Natural numbers are the numbers you count with, `1,2,3,4,5,6,7,...` no fractions, no negatives or anything that you can't count with infinite fingers another way to describe them would be to just say they're all the positive integers.
|
||||
|
||||
When I say [isomorphism](https://en.wikipedia.org/wiki/Isomorphism) I'm talking about a method that we can use to convert from objects within one set to objects in another set that we can also reverse to get back the original object.
|
||||
|
||||
## The beginning
|
||||
|
||||
The way this started is that I was thinking about the fact that natural numbers, lists and code/functions can all be used to represent each other. But going into that is a whole rabbit hole involving control flow, lambda calculus, how you can represent things with other things, abstract syntax trees and how modern electronic computers work at a basic level so I won't go into details here. All you need to know is that while I was thinking about that I made a connection to some previous random thoughts that I had between the time I first learned about KCMP and then. Those thoughts being about how I wondered what the sizes of the sets of numbers with finite and infinite KCMP.
|
||||
|
||||
> Wait aren't both of those infinite?
|
||||
|
||||
Yep but there's more than one size of infinity, the smallest infinity is the size of the set of natural numbers or [aleph](https://en.wikipedia.org/wiki/Aleph_number) null/nought. One of the infinities that's larger than that is the infinity of the real numbers. Due to that I was wondering whether the set of values with finite KCMP had the size of the naturals or the reals. Wait what was I talking about oh yeah isomorphism with naturals. The connection I made when thinking about how code/functions, lists and natural numbers are all equivalent was that I had just answered my question that I had had for a decent while and then I remembered one of the reasons why I was wondering that and got very confused.
|
||||
|
||||
## Why I got very confused
|
||||
|
||||
The reason I got very confused then is because I could apply a [diagonalization argument](https://en.wikipedia.org/wiki/Cantor%27s_diagonal_argument) to the set of all numbers with finite KCMP and get a number which shouldn't be in the set of all numbers with finite KCMP but meets all the criteria to be in that set, meaning I had found a [paradox](https://en.wikipedia.org/wiki/Paradox).
|
||||
|
||||
> How do you know this number should be in your set?
|
||||
|
||||
for the sake of making this conversation easier lets give this number the name of ψ because the symbol ψ is underutilized in math. We know that ψ if it exists(we'll get to that) has finite KCMP because the process used to generate it given the set of all finite KCMP numbers only adds a finite amount of additional KCMP on top of the KCMP of the set of all numbers with finite KCMP which we know also has finite KCMP because we can describe the creation of that set with the following python program
|
||||
```py
|
||||
counter = 0
|
||||
finite_KCMP_set = set()
|
||||
while True:
|
||||
counter = counter + 1
|
||||
finite_KCMP_set.add(eval_num(counter))
|
||||
```
|
||||
> wait but that doesn't halt and also what's this `eval_num` function you never explained the whole natural numbers as code thing
|
||||
|
||||
...do I really have to dive into that rabbit hole? ... Fuck it lets dive in.
|
||||
|
||||
## An isomorphism between natural numbers and programs for a turing machine
|
||||
|
||||
Alright so first thing first we need to convert a natural number into an array of bytes which is pretty trivial to do.
|
||||
```py
|
||||
num = random_natural()
|
||||
bytes = []
|
||||
i = 0
|
||||
while i <= log256(num):
|
||||
bytes.append(num%256)
|
||||
num /= 256
|
||||
i += 1
|
||||
```
|
||||
This array of bytes can then be treated as machine code that we run on whatever architecture thereby isomorphism between naturals and programs for a turing machine complete. ... Okay I'll be a bit more rigorous but not much. A turing machine can be constructed by having finite instructions which can operate on infinite memory so each instruction can just correspond to 1 natural number and we can read out an arbitrary natural number from our array by having the highest bit on each byte correspond to whether the value is continued in the next byte although given you don't need more than 255 instructions for a turing complete machine but doing this is convenient as we can use similar logic for the arguments to an instruction to pass in infinitely large values to instructions as jump locations or places to read data from. Anyways I'm not going into more detail beyond that, I'm sure you can get plenty creative making your own turing complete machine with infinite memory. To invert the relation you just take the byte array of a program and run it through this code
|
||||
```py
|
||||
num = 0
|
||||
for i in range(0,len(bytes)):
|
||||
num *= 256
|
||||
num += bytes[i]
|
||||
```
|
||||
|
||||
## The paradox
|
||||
|
||||
Anyways back to the issue at hand we have a number(ψ) that should be in a set by the definition of the set but also shouldn't be in that set due to how it got constructed. At this point I wasn't sure what to make of this but when I mentioned this to Micha and he had a few theories on what was going on.
|
||||
|
||||
1. ψ isn't in the set(except it is)
|
||||
2. This set isn't constructible
|
||||
3. the set isn't enumerable(interesting if true but obviously false)
|
||||
|
||||
> can you explain these statements?
|
||||
|
||||
sure but I don't remember how much of this I thought up vs Micha so I can't give proper credit so if that's important to you you'll need to reach out to me or Micha so we can share some discord messages.
|
||||
|
||||
### ψ isn't in the set
|
||||
|
||||
This would explain the paradox because it would mean there is no paradox however it's false because as described before the number has finite steps with finite KCMP and thereby also has finite KCMP.
|
||||
|
||||
### The set isn't constructible
|
||||
|
||||
This is the theory I'm currently in favor of as it seems to be the assumption that's made that isn't true, this also means that ψ doesn't exist.
|
||||
|
||||
### The set isn't enumerable
|
||||
|
||||
This is false because obviously the set is enumerable you enumerate by going through each natural number and evaluating the program equivalent.
|
||||
|
||||
## A set that actually exists
|
||||
|
||||
I can definitely credit the definition of this set to Micha, they gave a definition of. *"The set of all generatable tape sequences of a TM which have a limit"*. ψ isn't in this set because it doesn't have a proper limit, let n be the natural who corresponds to the program to generate ψ the nth digit of ψ can't be any number because it needs to not be equal to itself thereby meaning ψ doesn't have a limit and isn't included in our set.
|
||||
|
||||
## Some conjecture
|
||||
|
||||
With that we leave the realm of things I discussed with Micha and into the realm of increasingly stupid ideas.
|
||||
|
||||
With this knowledge that numbers with finite KCMP have a mapping to the integers...
|
||||
|
||||
> wait hold on don't those numbers include complex numbers, fractions, quaternions, irrational numbers, vectors and more? How do you know what's what from the limiting byte sequence?
|
||||
|
||||
Stop poking holes in my fun math thoughts anyways with the knowledge that integers correspond to finite KCMP numbers I conjecture that the fact that the infinity of the real numbers is larger than the infinity of the naturals will never come up in practice barring our current understanding of the universe being completely wrong or one or more universal constants having infinite KCMP(we'll come back to this in a second) due to all process of calculation that we have available are no more powerful than a turing machine which can only compute values which we just showed to map to the natural numbers instead of the real numbers.
|
||||
|
||||
## An amusing unfalsifiable hypothesis
|
||||
|
||||
I hypothesize that all of the universal constants have infinite KCMP due to the universe being a continuum of possible universal constants across a multi-dimensional plane and the observable universe being just one point on that plane picked at random, due to it being picked at random the probability of it being all values with infinite KCMP is practically 100%. Do I have evidence, research or anything else backing this theory up? Nope but it's interesting.
|
||||
|
||||
> Why is the probability practically 100% if the values are picked at random and How is that unfalsifiable?
|
||||
|
||||
The reason the probability is practically 100% is because the size of finite KCMP numbers is countably infinite and because the size of the real numbers is bigger that means that the set of numbers with infinite KCMP is the size of the real numbers thereby being infinitely bigger making the ratio comparable to 1:∞ meaning the probability of any universal constant having finite KCMP(assuming all are chosen fully randomly over a continuum) is 0(technically not impossible for math reasons but practically impossible).
|
||||
|
||||
As for the question of unfalsifiability we'd need to be able to make infinitely precise measurements to confirm it one way or the other and things like plank's constant conspire to prevent this in addition to us measuring with an error margin of 0 is pretty difficult if it's possible at all.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Doing all this abstract high level math is fun though I fully expect that none of this will ever be useful in any way ever. Mildly tempted to argue otherwise in a CS ethics class I have coming up in uni soon but that probably isn't worth the effort or crappy grade.
|
65
posts/fractions.md
Normal file
65
posts/fractions.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
|
||||
title: "Fractions"
|
||||
|
||||
description: "So I've been thinking about representing fractions/rational numbers in binary effieciently..."
|
||||
|
||||
date: 2023-08-07
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# Fractions
|
||||
|
||||
So recently I've been thinking about how fractions could be represented in binary effieciently. Okay I've had it in the back of my mind for months but [this video](https://www.youtube.com/watch?v=4d6YrTKmjfE) gave me a spark on a solution.
|
||||
|
||||
## The beginning
|
||||
|
||||
I don't remember why I first started considering this but the first obvious solution I came up with was to do something simple like this.
|
||||
|
||||
```rs
|
||||
struct Fraction{
|
||||
numerator:u32,
|
||||
denominator:u32
|
||||
}
|
||||
```
|
||||
|
||||
Which does work, however it's extremely wasteful with situations like 1/2 = 2/4 = 4/8 = 5/10 = ... among others.
|
||||
|
||||
This is okay depending on your use case, if you're okay taking a memory and computational hit you can just check if any resulting fraction can be simplified and simplifying. But I don't like that because it's ineffiecient and very inelegant.
|
||||
|
||||
## Why not just use floating point then
|
||||
|
||||
Because fractions rule and scientific notation drools, also floating point is just an approximation bro and thinking about how to solve this for fractions is fun.
|
||||
|
||||
## Second solution attempt
|
||||
|
||||
Alright so that first try didn't work particularly well with how wasteful it was but I'm sure this time it'll go better.
|
||||
|
||||
```rs
|
||||
struct Rational{
|
||||
integer: u32,
|
||||
fraction: u32
|
||||
}
|
||||
```
|
||||
|
||||
... okay I should probably explain. The denominator of the fraction is 2^32 so now we don't have to worry about having situations where 2 fractions simplify to the same value because all denominators are the same meaning that only happens when they're represented by the same value, hurrah! Anyways addition and subtraction are both pretty easy and cheap, just do them piecewise and handle overflows. But multiplication and division are a problem now because both of them require use to have some kind of external storage to do and are going to be pretty computationaly ineffiecient. Well crap.
|
||||
|
||||
## Inspiration from the video
|
||||
|
||||
And that's basically where I got stuck until I found the video that I linked above. In the video they describe an algorithm for how you can get all rational numbers exactly once. Why it works doesn't matter for this blog(go watch the video if you care) but I will describe how it works. First start off with the fractions 0/1 and 1/0 ignoring that the second is undefined. Now add their numerators and denominators toegether to get a new number 1/1. Put that new number between them and then repeat that process with all adjacent numbers for however many steps you want. What I did with this was stop at a finite number of repetitions(if you wanna try at home I made a repo that generates all the fractions) the binary representation would then be whatever indice in the list a particular fraction was at.
|
||||
|
||||
Amusingly this gave the illusion of actually working for a moment. Namely when I added the indices of x/y and (y-x)/y for a few examples I got 1. But then I tried adding 1/18 to itself 3 times, and I got 1/16.
|
||||
|
||||
## Why does this work and why does it stop working?
|
||||
|
||||
That is the question, well my theory is that there's a symmetry from 0-1 that makes reflecting all values x/y over the value 1/2 makes them into (y-x)/y due to the way this sequence is generated. The reason it doesn't work for values that don't add up to 1 is that there's no equivalent symmetry which causes problems due to the distribution of fractions not being uniform over the integers(aka going to and from integers with this system is non-linear). This problem leaves us without even a way to salvage a good mechanism of having fractions from 0-1 which would've been useful in combination with method 2 if multiplication and division didn't cause issue.
|
||||
|
||||
## Oh wait solution 2 is fine actually
|
||||
|
||||
yeah I didn't think about it hard enough originally, shift both numbers to the right by half the number of fraction bits before you do the equivalent integer operation and you're fine. That said it may make sense to only allocate a quarter of the number to the fraction(good enough for most cases) or if I were to actually implement this it'd be user specified(if people actually started using it then there'd probably be encoding problems but I'm not even going to make this so not my problem).
|
||||
|
||||
## Conclusion
|
||||
|
||||
The simplest solution is often terrible but the second simplest is generally at least okay. This also gives a new appreciation for how elegant floating point is(dodging the question of how many bits go to the precision completely). I also have a side quest on making a program to generate fractions for solution 3 which I intend to start writing about now.
|
207
posts/fractions_sidequest.md
Normal file
207
posts/fractions_sidequest.md
Normal file
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
|
||||
title: "Fractions Sidequest"
|
||||
|
||||
description: "In my last blog I wrote about my explorations on a new number type that specifies fractions rather than approximate binimal(? decimal has a latin root for 10 but floating point uses binary so what word?) or integers"
|
||||
|
||||
date: 2023-08-08
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# The Sidequest
|
||||
|
||||
Welcome to a blog post about a sidequest I went on while exploring a computer [fraction](/blog/fractions) based number system. Specifically for my third solution I wanted to generate a list of fractions generated by the algorithm from [the video](https://www.youtube.com/watch?v=4d6YrTKmjfE).
|
||||
|
||||
## Algorithm recap
|
||||
|
||||
the algorithm is decently simple but knowing it is a bit of a pre-requisite for the rest of this post. As such the steps are below.
|
||||
|
||||
1. Start with a pair of fractions you want the generated fractions to range over(there are probably restrictions on what you can pick but for the rest of this post assume they're 0/1 and 1/0 which are fine and allow for ranging over the entire number line)
|
||||
2. Add the numerators and denominators of the 2 fractions
|
||||
3. Put the newly created fraction between the fractions used to generate it
|
||||
4. repeat with all fractions next to each other in the list for however long you want for more fractions(you won't get repeats)
|
||||
|
||||
## Script 1
|
||||
|
||||
I wanted to use a fast lang for this so I chose Rust(also because I personally like Rust). It didn't take long for me to write this(slightly changed for clarity)
|
||||
|
||||
```rs
|
||||
fn main()->anyhow::Result<()>{
|
||||
|
||||
let mut fracs = Vec::<Frac>::new();
|
||||
fracs.push(Frac(0,1));
|
||||
fracs.push(Frac(1,0));
|
||||
|
||||
for i in 0..19{
|
||||
eprintln!("{}",i);
|
||||
step(&mut fracs);
|
||||
}
|
||||
|
||||
//remaining code in main wrote the fractions to a file and didn't change, maybe I could've written it to be faster but that's not the focus of this blog
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn step(list:&mut Vec<Frac>){
|
||||
|
||||
let mut i = 0;
|
||||
// I wanted a progress bar and in this case it actually is the reason I even knew there was a performance problem
|
||||
let bar = indicatif::ProgressBar::new(list.len() as u64);
|
||||
|
||||
while i < list.len()-1{
|
||||
bar.inc(1);
|
||||
list.insert(i+1,list[i]+list[i+1]);
|
||||
i+=2;
|
||||
}
|
||||
|
||||
bar.finish_and_clear();
|
||||
}
|
||||
|
||||
// trait impls are for convenience
|
||||
#[derive(Clone, Copy)]
|
||||
struct Frac(u16,u16);
|
||||
|
||||
impl Add for Frac{
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Frac(self.0+rhs.0,self.1+rhs.1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code feels bad even from a code quality point of view but idk why, regardless it's hilariously bad performance wise.
|
||||
|
||||
<video width="700" height="400" controls src="/video/frac_gen_v1.mp4"></video>
|
||||
|
||||
Considering that we're "only" doing addition this is incredibly slow. Slower than addition in (insert butt of the joke language of this week here). All that in mind something is definitely up and if you read the code above and think about it enough you'll probably see it.
|
||||
|
||||
... Yeah the problem is this line here
|
||||
|
||||
```rs
|
||||
list.insert(i+1,list[i]+list[i+1]);
|
||||
```
|
||||
|
||||
Citing documentation
|
||||
|
||||
> Inserts an element at position index within the vector, **shifting all elements after it to the right**.
|
||||
|
||||
In this case all elements after it is tens of millions of values to put this in big O notation, doing things this way for every element makes this process O(n²).
|
||||
|
||||
The solution is of course simple, don't ever put anything into a vec anywhere other than the end(barring witchcraft). Unfortunately implementing that solution required rewriting this code. But I took this as a nice oppurtunity to also multi-thread this code.
|
||||
|
||||
## Concurrency how?
|
||||
|
||||
Unfortunately this isn't quite trivially parallizable so I can't just use rayon. In the face of this a very naive solution to this problem would be something like
|
||||
|
||||
```rs
|
||||
// ignoring move semantics and the need to only use functions that exist for convenience and readability
|
||||
|
||||
fn recurse(f1:Frac, f2:Frac, remaining:usize)->Vec<Frac>{
|
||||
let middle = f1+f2;
|
||||
|
||||
let left_thread = std::thread::spawn(||recurse(f1,middle,remaining-1));
|
||||
let right_thread = std::thread::spawn(||recurse(middle,f2,remaining-1));
|
||||
|
||||
let left = left_thread.join();
|
||||
let right = right_thread.join();
|
||||
|
||||
//don't need return but not everyone knows rust
|
||||
return concat(left, middle, right)
|
||||
}
|
||||
```
|
||||
|
||||
the reason I call this the naive solution is because it uses OS threads and OS threads are expensive memory wise. Also if you spawn more of them than CPU cores you get minimal benefit and if you keep spawning them anyways the OS tends to have a panic attack. That's bad so instead of using OS threads lets use green threads for less overhead while still using multiple threads from a pool.
|
||||
|
||||
## Script 2
|
||||
|
||||
```rs
|
||||
#[tokio::main]
|
||||
async fn main()->anyhow::Result<()>{
|
||||
const RECURSIONS:u64 = 19;
|
||||
|
||||
let fracs = recurse(Frac(0,1),Frac(1,0),RECURSIONS).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// actual code is much more ugly in reality due to reasons(code below won't compile), if you wanna see it there's a link to the repo with all this code at the bottom of the article, the git commit is de72a7a0
|
||||
// also removing progress bar code because nobody cares just know I still had a progress bar
|
||||
async fn recurse(f1:Frac,f2:Frac, remaining:u64)->Vec<Frac>{
|
||||
// base case for the recusion
|
||||
if remaining == 0 {
|
||||
return Vec::new()
|
||||
}
|
||||
|
||||
// same idea as the naive version
|
||||
let middle = f1+f2;
|
||||
let left_task = tokio::task::spawn(recurse(f1,middle,remaining-1));
|
||||
let right_task = tokio::task::spawn(recurse(middle,f2,remaining-1));
|
||||
|
||||
let left = left_task.await.expect("left future failure");
|
||||
let mut right = right_task.await.expect("right future failure");
|
||||
|
||||
// how concat is being achieved
|
||||
let mut ret = left;
|
||||
ret.push(middle);
|
||||
ret.append(&mut right);
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Frac is the same as before
|
||||
```
|
||||
|
||||
looks good at first glance(actual version code quality is bad but blog version seems alright). What happens when we run it?
|
||||
|
||||
<video width="700" height="400" controls src="/video/frac_gen_v2.mp4"></video>
|
||||
|
||||
Oh... we run out of memory... or well we run out of 30 gigabytes of memory because I set a limit to avoid effecting the other stuff running on the server(because it isn't mine). But why? Doing the math if all we had to deal with was the fractions we'd be using about `17501876*4/1000**3 ~ 0.07 GB`, if we include the overhead of all the Vecs we make and are pretty agressive with how much memory they use maybe 0.21 GB which is a difference of over 142x. So what's the rest of the memory?
|
||||
|
||||
Well... I'm not 100% sure actually but my current best guess is the green threads/tokio tasks. Whatever it is on average it seems to have memory usage measured in hundreds of bytes and/or a kilobyte or 2 roughly doing a bit of quick math(I just divided 30GB/num_of_running_tasks). So I guess I gotta take out the green thread usage huh.
|
||||
|
||||
## Script 3(the finale for now)
|
||||
|
||||
So yeah I did that, I didn't need to rewrite this time just a refactor.
|
||||
|
||||
```rs
|
||||
fn main()->anyhow::Result<()>{
|
||||
const RECURSIONS:u64 = 32;
|
||||
|
||||
let fracs = recurse(Frac(0,1),Frac(1,0),RECURSIONS);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recurse(f1:Frac,f2:Frac, remaining:u64)->Vec<Frac>{
|
||||
// nothing new here
|
||||
if remaining == 0 {
|
||||
return Vec::new()
|
||||
}
|
||||
|
||||
let middle = f1+f2;
|
||||
let left = recurse(f1,middle,remaining-1);
|
||||
let mut right = recurse(middle,f2,remaining-1);
|
||||
|
||||
let mut ret = left;
|
||||
ret.reserve(ret.len()+right.len()+1);
|
||||
ret.push(middle);
|
||||
ret.append(&mut right);
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Frac now uses 32 bit ints rather than 16 bit ints due to an overflow
|
||||
```
|
||||
|
||||
This solves the whole running out of memory thing. A funny side effect is that now it's even faster(even though it's 1 thread).
|
||||
|
||||
<video width="700" height="400" controls src="/video/frac_gen_v3.mp4"></video>
|
||||
|
||||
So that was fun going through and making all this work out well, now I can generate gigabytes upon gigabytes of fractions with ease.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Could I optimize this more? Yes I could pre-allocate a buffer and use a specialized thread pool(and probably some unsafe code as well thinking about it). But I won't because it's fast enough, the remaining speed gains probably aren't worth it and most of the execution time is spent writing the results to disk. Overall this was a fun sidequest as a part of the fraction quest. I did other stuff between the article before the fraction one and the fraction one and maybe I'll dump those articles at some point soon so I can stop feeling bad about them sitting in my website's git repo doing nothing.
|
||||
|
||||
[git repo with the generator](https://github.com/Pagwin-Fedora/fraction_generator)
|
204
posts/gh_actions.md
Normal file
204
posts/gh_actions.md
Normal file
|
@ -0,0 +1,204 @@
|
|||
---
|
||||
|
||||
title: "Setting up CD for this site"
|
||||
|
||||
description: "How I setup Github actions to automatically update this site"
|
||||
|
||||
date: 2022-01-22
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
So recently I got a bit of a bee in my bonnet to go setup CD for this website. The main reasons that drove this were 1. deploying the site was mildly tedious which is a good enough reason on it's own and 2. I wanted to learn how to do it.
|
||||
|
||||
|
||||
|
||||
## Wait but how did I find out about and how to do this?
|
||||
|
||||
I was aware of Github actions and a vague sense of how it should work observing how things went when I made my small contribution to [Gerald](https://github.com/Gerald-Development/Barista-Gerald). But observing that didn't really give a sense of how it worked. What did was when my friend [Micha](https://github.com/michalusio) was working on implementing [their own blog](https://lochalhost.pl) and set things up with Github actions to implement CI/CD. Then I saw [this Fireship video](https://www.youtube.com/watch?v=eB0nUzAI7M8) which gave me a nice amount of context for this. Even with that bit of knowledge on how to setup stuff with Github actions I didn't really have a motivation to go do it.
|
||||
|
||||
|
||||
|
||||
## The spark to actually do it
|
||||
|
||||
Then for a couple of reasons wanted to write a blog article about progress on [Pogo](https://github.com/Pagwin-Fedora/Pogo). But I decided that before I wrote any more articles that I should go look into setting up CD for my site.
|
||||
|
||||
|
||||
|
||||
## Implementing CD with Github actions
|
||||
|
||||
So with inspiration in my heart to go and do stuff with Github actions I began. First off I needed to set up the condition for my workflow running which was a pretty simple as I wasn't really doing anything interesting here.
|
||||
|
||||
```yaml
|
||||
|
||||
on:
|
||||
|
||||
push:
|
||||
|
||||
branches:
|
||||
|
||||
- master
|
||||
|
||||
```
|
||||
|
||||
|
||||
## The jobs
|
||||
|
||||
I knew due to reading some pages on Github's action market place and previous context that I would need to have at least 3 if not more steps
|
||||
|
||||
1. checkout the code
|
||||
|
||||
2. use Hugo to build the site
|
||||
|
||||
3. deploy to my VPS
|
||||
|
||||
|
||||
|
||||
So going through each of those steps in order to see how I went about doing them first we have checking the code out which was a pretty simple `uses: actions/checkout@v2` additionally telling it to fetch submodules due to the structure of my project. After checking out the code I had to use Hugo and conveniently there was a module for Hugo `peaceiris/actions-hugo@v2` although sadly it only installs Hugo so another step would have to be added to build the app. But that step was a pretty simple `run: hugo --minify`. I will say though that if the example on the marketplace page didn't use the `--minify` option I wouldn't have either because I didn't know it existed so that was a nice little learning experience. After building the code I needed to deploy it which due to this being a static site was theoretically as simple as copying files with rsync. But I didn't want to have an automated action have access to root or my user for security and anti-stupidity reasons. To implement that I had to leave the realm of Github actions and go over to my VPS to set some stuff up.
|
||||
|
||||
|
||||
|
||||
## Some work on the VPS
|
||||
|
||||
That stuff in question was adding a new user and changing the perms of /var/www/pagwin.xyz so that new user could edit files there. This was pretty simple.
|
||||
|
||||
```sh
|
||||
|
||||
sudo useradd website # I can hear people laughing at me already for not passing the -m option but relax I'll explain later
|
||||
|
||||
sudo chown -R website:www-data /var/www/pagwin.xyz # btw I didn't explain earlier but my website files are in /var/www/pagwin.xyz not /var/www/html because I'm hosting multiple sites on this VPS and the folder change makes it easier to keep track of which one I'm screwing with
|
||||
|
||||
```
|
||||
|
||||
However my unwillingness to have the new user have a home directory for cleanliness and to avoid unnecessarily leaving a user that could receive emails(I have an email server setup on this VPS as well) I didn't create a home directory. But in order for the deployment workflow on Github to deploy to the VPS via rsync it would need ssh access... Okay the problem may not be obvious if you don't understand ssh/good security practices very well. The problem is that in order to login to ssh via an ssh key you need to put that key into `$user_home/.ssh/authorized_keys` which requires the user have a home directory that I am unwilling to create. Password authentication is also not an option because allowing password auth on to a server is insecure compared to only allowing ssh keys. This is especially true when the ssh login is being done by an automated system. Also my VPS requires the usage of a TOTP if you login via a password and setting that up for Github actions sounds like a nightmare. Also also in order for the server to know the TOTP which requires a file... which goes into the home directory meaning nothing has changed or improved by trying to use a password.
|
||||
|
||||
Conveniently while `$user_home/.ssh/authorized_keys` is the default location for ssh public keys it's pretty easy to add another location for sshd to look for authorized_keys just by adding the line `AuthorizedKeysFile .ssh/authorized_keys /etc/ssh/keys/%u.authorized.pub` to `/etc/ssh/sshd_config` where the later bit of `/etc/ssh/keys/%u.authorized.pub` is added on from the default. That last bit of the config tells ssh to look for the public keys at an additional location where the username of the user trying to login replaces %u. After that whole hassle is done with generating the ssh key is pretty simple with `ssh-keygen` and putting the public key in the right spot. Adding the private key as a Github secret was annoying however but I'll discuss that in the [#Dealing With My Stupidity](#Dealing%20With%20My%20Stupidity(and%20a%20private%20ssh%20key)) section.
|
||||
|
||||
|
||||
|
||||
## What were we talking about oh yeah Github actions
|
||||
|
||||
anyways yeah this is what I initially(spoiler I change it) wrote for Github actions to go and deploy the app.
|
||||
|
||||
```yaml
|
||||
|
||||
uses: up9cloud/action-rsync@master
|
||||
|
||||
env:
|
||||
|
||||
HOST: pagwin.xyz
|
||||
|
||||
KEY: ${{secrets.SSH_KEY}}
|
||||
|
||||
TARGET: /var/www/pagwin.xyz/
|
||||
|
||||
```
|
||||
|
||||
With that I saved the file to `.github/website-publish.yml` and felt a mild sense of accomplishment. In hindsight that sense and first file are hilarious and while I would love to immediately explain why first I want to take a second to show a step I added after I finished dealing with my stupidity. That step is a cleanup step that deletes the old site before copying over the new one so people can't snoop around in redundant files. I implemented that with this tidbit.
|
||||
|
||||
```yaml
|
||||
|
||||
uses: appleboy/ssh-action@master
|
||||
|
||||
with:
|
||||
|
||||
host: pagwin.xyz
|
||||
|
||||
username: website
|
||||
|
||||
key: ${{secrets.SSH_KEY}}
|
||||
|
||||
script: rm -rf /var/www/pagwin.xyz/*
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Dealing With My Stupidity(and a private ssh key)
|
||||
|
||||
The obvious act of stupidity if you paid attention to what I wrote is that I saved the file to `.github/website-publish.yml` instead of `.github/workflows/website-publish.yml`. Fixing that was pretty easy when I figured out what was going on. After that I then had to tweak the deploy step a bit to make rsync work properly. I did a couple of things wrong with the deploy step, one I didn't specify a username, and two I didn't specify my source directory. The source directory thing was particularly stupid as I wanted the files inside the public folder but just putting ./public gave the folder itself with the files in it. As I removed that from my vps I deleted the `/var/www/pagwin.xyz` folder which required a brief recreation of that folder. Then I setup the source correctly to get the files properly but also set the target wrong so everything would go in a folder * which was annoying but at the end of all that I had a pretty smooth setup. Also when trying to copy my private key over to gh actions I struggled a little because I wanted to use xclip to put it in my clipboard but due to website being a different user I couldn't do that directly. This would've been fixed relatively easily if I realized this is why github's cli exists but oh well I eventually got it figured out.
|
||||
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
Overall I'm very happy I did this because it gave me a nice bit of practical understanding of how to set up Github actions for future projects. I hope reading about my technical spaghetti VPS and idiocy wasn't too boring. Oh yeah for those who care this is what the yaml file looked like in the end
|
||||
|
||||
```yaml
|
||||
|
||||
name: Website publish
|
||||
|
||||
|
||||
|
||||
on:
|
||||
|
||||
push:
|
||||
|
||||
branches:
|
||||
|
||||
- master
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Code Checkout
|
||||
|
||||
uses: actions/checkout@v2
|
||||
|
||||
with:
|
||||
|
||||
submodules: true
|
||||
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Hugo Setup
|
||||
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
|
||||
with:
|
||||
|
||||
hugo-version: '0.91.2'
|
||||
|
||||
- name: Build
|
||||
|
||||
run: hugo --minify
|
||||
|
||||
- name: Clean
|
||||
|
||||
uses: appleboy/ssh-action@master
|
||||
|
||||
with:
|
||||
|
||||
host: pagwin.xyz
|
||||
|
||||
username: website
|
||||
|
||||
key: ${{secrets.SSH_KEY}}
|
||||
|
||||
script: rm -rf /var/www/pagwin.xyz/*
|
||||
|
||||
- name: Deploy
|
||||
|
||||
uses: up9cloud/action-rsync@master
|
||||
|
||||
env:
|
||||
|
||||
HOST: pagwin.xyz
|
||||
|
||||
USER: website
|
||||
|
||||
KEY: ${{secrets.SSH_KEY}}
|
||||
|
||||
SOURCE: ./public/*
|
||||
|
||||
TARGET: /var/www/pagwin.xyz/
|
||||
|
||||
```
|
59
posts/how.md
Normal file
59
posts/how.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: "How this website was made"
|
||||
description: "It's with hugo and the rest of this is probably gonna be short and boring viewer discretion is advised"
|
||||
date: 2020-09-30
|
||||
---
|
||||
## Prelude
|
||||
Before we get to how I actually made this site let's discussed how I failed to make this site(repeatedly). I was inspired to make a simple website/blog from [this blog post](https://k1ss.org/blog/20191004a), I rapidly regretted having that as my main inspiration. I tried setting up scripts for generating pages using the output from pandoc, making the pages look nice and what not as well as make a script for generating an rss feed but rapidly realized that all of this was going to be a pain and gave up. Rinse and repeat a couple of times over several months to a year or so.
|
||||
|
||||
## Actually making this website
|
||||
One day(2 days before this post was written actually) I was browsing reddit when I came across [this comment](https://www.reddit.com/r/linuxquestions/comments/j0wcfj/i_hand_you_a_computer_with_a_minimalistic_install/g6vxxxj/) and realized that I'm an idiot because static site generators exist and what I had previously been doing was basically writing my own, I may still write my own but more so as a project on it's own than as something that's contributing to something else. After that work went relatively smoothly with me spending the first day learning what the fuck hugo(no I didn't do my research into static site generators don't judge me okay) is then on the second day I actually started to get into writing all the stuff for the site. For my theme as you may already know if you've looked at the footer for this website I'm using liquorice as my theme. I chose it for being simple, very nice for reading text(what I expect will be the main thing that's done on this website) and because I just liked the overall feel. There were some aspects that I felt the need to improve though such as the homepage being a bit more than just a list of every page on the site, something about lists(I don't remember what), making the subsections of these blogs and other pages in the future jump points in case I write something that would actually benefit from those jump points and not just a short one page piece and finally making the links actually look visually distinct from the text beyond simply being bold. There are probably other changes I'm forgetting and in the future I expect I'll tweak this further but that's all for now. Some of those tweaks will be me making the website smaller and more compressed following the original spirit of that kiss blog and I can already see some points where I can shave some size off but that's a story for another time.
|
||||
|
||||
## Making the jump points
|
||||
most of those points are pretty easy if you read hugo's documentation and are willing to try random things but the jump points are a slight challenge and something worth writing about in more detail. First things first ignoring all this being generated from markdown causing some oddities how do we make a jump point on a webpage? Well with anchor tags of course!
|
||||
```html
|
||||
<a name="some_name_or_something_idk" href="./#some_name_or_something_idk">some content doesn't matter</a>
|
||||
```
|
||||
this is nice now if somebody goes to example.com/#some_name_or_something_idk their browser will jump them straight down to wherever that anchor tag is. But it doesn't jump to the anchorblock when we click on it it simply sets our url and if we reload it jumps to it. *Editor's note: as I write this I'm unsure if I'm an idiot who didn't need to do the work with this javascript I'm about to talk about so it may well be possible that it's unnecessary and the above code already does that*. So in order to fix that we'll be adding an event to our anchor element like this.
|
||||
```html
|
||||
<a name="name" href="./#name" id="name">some text</a>
|
||||
<script>
|
||||
let elem = document.getElementById("name");
|
||||
elem.addEventListener(event=>{
|
||||
event.target.scrollIntoView();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
Technically the event could be added by adding an onclick parameter to the anchor element in the dom but we we start dealing with another problem which I'll get to after explaining this it'll be way cleaner to just use `addEventListener` anyway the code is relatively self explanatory but I'll explain it anyways. Our element has an id that we attached to it by adding the parameter `id="name"` we can get our element in our code by asking the browser to give our element to us using the id as a reference to find the element with the method `document.getElementById`. We could totally just use `document.getElementsByName` and take the first element from that but I personally chose to add and use the id. With `addEventListener` we can attach a function that'll be called when an event fires in this case the click event for when the user clicks on the anchor element. The function in question take the event object given to it and takes out the dom element that was actually clicked on with the target property. We then scroll to that dom element with scrollIntoView. Now all we need to do is have it so that when we write out our header elements we just surround them with anchor elements and... wait.
|
||||
|
||||
## We didn't write those header elements though
|
||||
Oh yeah we didn't write the header elements in the first place they're written in from whatever markdown generator that hugo uses. Well how do we handle this? There may be some way of changing how hugo generates html from the markdown but that sounds hard let's just write some javascript.
|
||||
```js
|
||||
let elems = document.getElementsByTagName("h2");
|
||||
for(let elem of elems){
|
||||
elem.outerHTML = `<a name="${elem.innerText}" href="./#${elem.innerText}" id="${elem.innerText}"><h2>${elem.innerHTML}</h2></a>`;
|
||||
document.getElementById(e.innerText).addEventListener('click', event=>{
|
||||
event.target.scrollIntoView();
|
||||
});
|
||||
}
|
||||
```
|
||||
Ok so you already understand that last bit with the event listener and what not so allow me to explain the rest. `document.getElementsByTagName` is the same as `document.getElementById` except it gets more than one element and does it by their tag name. The for loop iterates through all the elements we just got and through each iteration we can refer to the element we're on by the variable `elem`. The parameter of `outerHTML` isn't used very often `innerHTML` and `innerText` are used more often because most people only want to control the text inside of a dom element but want to leave the outer tags untouched but in this case that's useful because we actually want to add anchor tags around our header tags which is what we do. Hooray the problem with the markdown generation not allowing fine enough control was solved. Now about adding that script in to do that work.
|
||||
|
||||
## Adding the script in
|
||||
You'd think this was simple and it kinda was but keep in mind that I've only been using hugo for less than 3 days at this point. Besides that I also only wanted this script in the single pages or the pages that blog/articles/whatever were on and not on list pages which list out all the pages as the list pages also used h2 elements but I didn't want the h2 elements there to be modified by this script. Thankfully this was easy because I had shortly before hand wanted to do something similar with a stylesheet but man adding in that stylesheet had some nuisances. The first thing I found of use for this purpose was [cond](https://gohugo.io/functions/cond/) but I still needed to figure out how to test for whether the page was a list or not so I started looking through hugo's page variables and I found 3 candidates for this `.IsNode`, `.IsPage` and `.IsSection` with the last one just being the negate. I got somewhat frustrated when I found none of these useful for what I was trying to do. Eventually I stumbled upon `.Kind` and bumbled about a bit trying to figure out how to test for a `.Kind` of page until I found [eq](https://gohugo.io/functions/eq/). So great I now can test for whether a page is a page I want the stylesheet applied to so
|
||||
```html
|
||||
{{ cond (eq .Page.Kind "page") "<link rel='stylesheet' href='{{.Site.BaseURL}}/css/single.css'>" "" }}
|
||||
```
|
||||
should work right? Nope nope nope for multiple reasons nope. For one thing trying to put the base url with curly brackets didn't work because apparently hugo doesn't do curly brackets in curly brackets also when I opened the page in a browser the tag and all the tags beneath it(which were placed in the head in the partial template btw) are now in the body??? Also I made it seem like I had solved the cond thing before this came up but that was happening at this point as well. So first things first how do we put a variable midway through a tag that we're inserting? Well apparently the answer to that is [printf](https://gohugo.io/functions/printf/)(I personally would've named it something like format rather than printf even if it uses something called printf internally but maybe that's just me) so now we have.
|
||||
```html
|
||||
{{ cond (eq .Page.Kind "page") (printf "<link rel='stylesheet' href='%s/css/single.css'>" .Site.BaseURL) "" }}
|
||||
```
|
||||
which is closer but it still jumps into the body for some reason. That reason as it turns out is because Hugo ~~being somewhat annoying because it decides not to warn you for failing to be explicit about whether you want a tag as a tag~~ being very cool and safe escaping all the tags to prevent cross site scripting/injection or whatever else problems in code that you're explicitly writing out in a folder for templates. Ugh anyways after running the output of the printf through [SafeHTML](https://gohugo.io/functions/safehtml/) we get this final iteration that works how I want it to of.
|
||||
```html
|
||||
|
||||
{{ cond (eq .Page.Kind "page") ( safeHTML (printf "<link rel='stylesheet' href='%s/css/single.css'>" .Site.BaseURL)) "" }}
|
||||
```
|
||||
Nothing about this changes for the script that we want on our blog pages only other than that we replace link with a script tag.
|
||||
|
||||
## Conclusion
|
||||
This was fun and I'm glad I found out about the existance of [hugo](https://gohugo.io/). I'll probably update this site in the future and this blog will probably get outdated but unless I decide the site looks almost completely different run into a very annoying or interesting problem or completely remake the site for osme reason or another I probably won't update this blog or release any other blogs with updates on changes I make to this site(and knowing me even when those things come up I probably won't write about them) one of the things I want to change is the links to different platforms/feeds/whatever but based on already made efforts I think I'll save that for another time.
|
119
posts/invidious_and_goals.md
Normal file
119
posts/invidious_and_goals.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
|
||||
title: "Yeeting the distractions and setting goals"
|
||||
|
||||
description: "So recently I've begun work on trying to remove distractions so I'm more likely to work on productive stuff and this blog is effectively a lightning round of things I did to accomplish that"
|
||||
|
||||
date: 2022-09-01
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
So recently I’ve engaged in a renewed push to be productive somewhat consistently and this time it just may work(unlike the 3-5 other times). With this push, I’ve decided to begin moving off of youtube by going down to my subscriptions. In order to do that I implemented a few small projects.
|
||||
|
||||
## Getting the feeds(but not really)
|
||||
So to enforce that my initial plan was to only watch the content I saw through an RSS feed, preferably via mpv. In order to do that I needed a list of channel ids corresponding to the youtube channels I was subscribed to. In order to get that I could’ve gone through and manually gotten each channel id through youtube’s web interface… But that would take forever and ain’t nobody got time for that manual labor when you can spend twice as long automating(although doing that automation gave me experience that may save me time now). So to do that I looked into google’s [Youtube Api](https://developers.google.com/youtube/v3) and found a way to get a list of [subscriptions](https://developers.google.com/youtube/v3/docs/subscriptions/list). But to make use of that I’d need to go and learn how to do stuff with OAuth. Thankfully after faffing about a bit I realized that there’s an [npm package](https://www.npmjs.com/package/googleapis) that does a lot of that work for me. Anyways with that, it was time to ~~steal example code~~ write software, oh hey where did all that preexisting code come from?
|
||||
|
||||
## Oh the callbacks
|
||||
|
||||
Well, that code came from [here](https://developers.google.com/youtube/v3/quickstart/nodejs) and oh my god do they use callbacks. Personally, I think callbacks suck and are the worst way of having some sort of asynchronous task. So I did a decent amount of refactoring to convert things to use promises. However much to my chagrin I found that I couldn’t use async await because apparently the npm package didn’t return normal promises, or maybe something else was happening I’m not entirely sure looking over the code now with intellisense but trust me when I tried back when I was figuring this out it didn’t work and it was annoying. Though that said I also don’t know why I couldn’t/wouldn’t convert from the weird promises to normal promises due to that being relatively easy with js’s promise api but I digress.
|
||||
|
||||
|
||||
## Getting the subs
|
||||
|
||||
I don’t remember if I implemented the code that got my subscriptions concurrently with the callback refactor or if I did it after. In any case, all of the code to get the subscriptions is 2 relatively small functions.
|
||||
|
||||
```js
|
||||
function getSubscriptions(auth, page) {
|
||||
var service = google.youtube("v3");
|
||||
return service.subscriptions.list({
|
||||
mine:true,
|
||||
auth,
|
||||
maxResults:50,
|
||||
part:"snippet",
|
||||
pageToken: page ? page:""
|
||||
});
|
||||
}
|
||||
|
||||
function handlePage(authority,response){
|
||||
let items = response.data.items;
|
||||
for(let item of items){
|
||||
console.log(item.snippet.title+" ".repeat(60-item.snippet.title.length)+item.snippet.resourceId.channelId);
|
||||
}
|
||||
if(response.data.nextPageToken){
|
||||
getSubscriptions(authority, response.data.nextPageToken)
|
||||
.then(handlePage.bind(null,authority))
|
||||
}
|
||||
}
|
||||
```
|
||||
Yeah, pretty simple but allow me to explain what bits of these 2 functions are doing and why.
|
||||
```js
|
||||
var service = google.youtube("v3");
|
||||
//...
|
||||
let items = response.data.items;
|
||||
```
|
||||
Both of these are done primarily for convenience so I'm not writing the same thing over and over again. If you notice the service one uses the inferior var instead of let it's because I was lazy and didn't change that bit from the example code. And now that I'm done with the bit you can find that code [here](https://developers.google.com/youtube/v3/quickstart/nodejs).
|
||||
```js
|
||||
return service.subscriptions.list({
|
||||
mine:true,
|
||||
auth,
|
||||
maxResults:50,
|
||||
part:"snippet",
|
||||
pageToken: page ? page:""
|
||||
});
|
||||
```
|
||||
The only other bit of code in the getSubscriptions function just calls the method in the npm package to make a request for the subscriptions of the user who provided Oauth authorization, 50 results at a time specifically giving things under the "snippet" category of data. For the pageToken bit what it's doing is if it's null/undefined it specifies it as an empty string so we get the first page and if it's not then it's just itself so we can get the next page.
|
||||
```js
|
||||
for(let item of items){
|
||||
console.log(item.snippet.title+" ".repeat(60-item.snippet.title.length)+item.snippet.resourceId.channelId);
|
||||
}
|
||||
```
|
||||
This bit of code just outputs each of the fetched channels' names and their id such that all the ids visually align for the part of my brain that wants everything to look neat. The reason I wanted things in this format was that I wanted to manually filter out the channels I didn't watch so having the channel name with the id would make it faster to get through for the obvious ones. The reason I was console.logging instead of writing to a file via the fs module was because I was lazy and decided to just have the information output via stdout and redirected to a file via a > operator in bash.
|
||||
```js
|
||||
if(response.data.nextPageToken){
|
||||
getSubscriptions(authority, response.data.nextPageToken)
|
||||
.then(handlePage.bind(null,authority))
|
||||
}
|
||||
```
|
||||
This last bit of code checks to see if there's a token for the next page of subscriptions and if there is it gets them, providing the nextPageToken to do just that in the getSubscriptions function and once the new response pops up it sends it to handlePage. More specifically what happens is I use the bind method of js functions as a way to have a partial function which can otherwise be said as a function that already has one of its arguments passed in. Until somewhat recently I wasn't aware you could use bind like that but one time when I was commenting an amount of annoyance at js not having a built-in function that allows for the easy construction of partial functions like Python's functools.partial or Haskell's function currying built into the language in a discord server a friend pointed out that the bind method can be used for that so the more you know I guess.
|
||||
|
||||
## Why I specified but not really for getting the feeds
|
||||
|
||||
As it turns out what I wanted could be better accomplished by self-hosting an [Invidious](https://github.com/iv-org/invidious) instance. However, my weird format that I had of my subs wouldn't work and I didn't want to redo filtering out channels I don't want so I decided to make a script that would make an opml file which is one of the file types that invidious could import. To do that I wrote a rust script.
|
||||
```rs
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
pub fn main() -> std::io::Result<()> {
|
||||
const START:&str = "<opml version=\"1.1\"><body><outline text=\"Imported Youtube Subscriptions\" title=\"Imported Youtube Subscriptions\">";
|
||||
let mut file = File::open("channels")?;
|
||||
let mut buf:String = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
let middle = buf.split("\n")
|
||||
.filter(|v|v!=&"")
|
||||
.map(gen_middle)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
const END:&str = "</outline></body></opml>";
|
||||
print!("{}\n{}\n{}",START,middle,END);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_middle(line:&str)->String{
|
||||
let tokens = line.split(" ").filter(|v|v!=&"").map(String::from).collect::<Vec<String>>();
|
||||
let name = tokens[0..(tokens.len()-1)].join(" ");
|
||||
let id = tokens.last().unwrap();
|
||||
format!("<outline text=\"{}\" title=\"{}\" type=\"rss\" xmlUrl=\"/feed/channel/{}\"/>",name,name,id)
|
||||
}
|
||||
```
|
||||
TLDR on that whole bunch of code is I have a constant string as the start of the file which gets output. Then there's a middle that's generated from the list of channels in that weird format the previous script generated such that every entry gets put into the template `<outline text=$channel_name title=$channel_name type="rss" xmlUrl="/feed/channel/$channel_id"/>"` and then all the channel entries are joined together. After that, I put a constant value at the end to close everything up. Developing that script there was a bit of a hiccup where Invidious wouldn't take it because the channel name only had the first word due to me making an initial mistake which I eventually fixed.
|
||||
|
||||
### Wait what about hosting an Invidious instance?
|
||||
|
||||
Oh yeah, I should probably summarize that process. I tweaked a config file in a couple of places so it fitted my particular use case. I added entries to my /etc/sites-enabled/ and my DNS, then ran Certbot. After that with a simple `docker-compose up -d` with the provided docker-compose.yml file and it worked without anything worth commenting on happening.
|
||||
|
||||
## Conclusion
|
||||
|
||||
So that was a lot of words to describe something that only took maybe 6 hours altogether over a couple of days. That said I hope to do a bit more to keep myself on task, specifically, I want to set up a discord bot that will dm me the tasks that I have in google tasks(which is why I mentioned doing that youtube api automation may end up saving me time) and notion every day and my larger goals each week. So that'll be an interesting short little project when I find the time. Then once that's done I can finally return to my little overengineered todo list [Pogo](/blog/pogo_so_far) where I scrapped the very small amount of code I wrote following the spec I wrote in a previous blog and will probably implement in Erlang instead. Though before any of that I need to make a blog post ranting about serenity and give into implementing a bad fix to a problem their api caused. But after all that I can continue work on that libvirt util in Lisp. Anyways with all of that rambling out of the way I wish the reader of this a nice day.
|
43
posts/micro_blogs.md
Normal file
43
posts/micro_blogs.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
|
||||
title: "Micro blogs (1)"
|
||||
|
||||
description: "a bunch of thoughts ideas and what not that aren't worth of full blogs but that I still want to write down"
|
||||
|
||||
date: "2024-01-30"
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# You know the drill
|
||||
|
||||
Same deal as the [speedrun blog](https://pagwin.xyz/blog/speedrun/) putting down a bunch of ideas that I want out of my head but aren't worthy of a full blog.
|
||||
|
||||
## The entire set plus one more in the set
|
||||
|
||||
Let me start off where I started off<sup>[[1]](#1)</sup> lets say that someone who we shall name Steve anonymously puts a bounty on themselves which is described as "$1 in addition to whatever money Steve has on their person" how much money should be payed out to whoever collects the bounty on Steve and where would it come from? Well in this case the way that reality works and the set of actors involved constrains us to the answer of "whatever money Steve has on their person" and no more. This answer would correspond to addition being equivalent to the set union operator. That does work but with slightly different context it seems like the answer would be different, for example pretend that a god came down and said "I am going to transfer ten humans in addition to the entire human population to a habitable planet in a different galaxy". In this case specifying ten humans in addition implies that we're transferring a number of humans greater than just the current human population but also ten more because otherwise why specify those ten humans. The problem that I have is how many humans come out on the other side. "Why not just the current population plus ten?" well because transfer implies they already exist and aren't being created in that moment so the number should be the same and also my brain thinks there's an interpretation or slightly different wording where you could argue there'll be infinitely many humans. I'm pretty sure this is a [type 5 paradox](https://youtu.be/ppX7Qjbe6BM?t=2035)
|
||||
|
||||
## Excel with types/static analysis?
|
||||
|
||||
So I think I started thinking about this when I rewatched [this Matt Parker video](https://www.youtube.com/watch?v=yb2zkxHDfUE). I'm wondering if there's a niche for some spreadsheet software is intended to require the user to specify types for cells or a full sql-esque table or something in addition to doing some nice lints/static analysis like you would see in software to minimize errors. My mind has also feature creeped this idea out a bit to have this program capable of exporting some file package/sql database and an executable so you can have something maintaining the structure of the data while other programs do automated stuff in the hopes that Ludicity doesn't come in for a [drop kick](https://ludic.mataroa.blog/blog/i-will-fucking-dropkick-you-if-you-use-that-spreadsheet/).
|
||||
|
||||
|
||||
## PSA please check to make sure browser zoom works okay on your site <sup>[[2]](#2)</sup>
|
||||
|
||||
And also phones and screen readers but the browser zoom one is the one that affected me when I'm writing this (over 2 months after the prior 2 sections). Particularly if you have a blog with some text content that's centered with whitespace as the margins and I zoom in I don't just want to see the text get bigger I also want the margins to shrink so the text has more room. Reason being so zooming isn't proportional to additional scrolling. Thanks.
|
||||
|
||||
## Wait mobile vs desktop is just handled via a CSS if statement?
|
||||
|
||||
I was exploring the CSS to understand how the margins shrank for my site but not the offending site and I noticed on both sites that the css styling jumped a bit as I shrank and grew the page width only to realize the reason was because of some [CSS if statement(s)](https://css-tricks.com/a-complete-guide-to-css-media-queries/). I don't know why I previously thought you could deal with this in some other way but TIL.
|
||||
|
||||
|
||||
## How long should I make these?
|
||||
|
||||
I've never thought about this before but how long should I make blogs? How long should I make microblog dumps? I don't know I should probably think about that... later I will think about that later.
|
||||
|
||||
## Footnotes
|
||||
|
||||
<a href="./#1" name="1">1</a> - I started off in a fan-fic of the stormlight archive where a character I inserted into the story was putting up a bounty for themselves by promising "a shardblade in addition to any shardblades or shardplate $character has on them" rather than with money
|
||||
|
||||
<a href="./#2" name="2">2</a> - If you noticed that the blog example was specific that's because this post was incited by a blog with whitespace which didn't shrink the whitespace when I zoomed. Also at time of writing I checked to make sure my site adheres to this.
|
15
posts/micro_blogs_2.md
Normal file
15
posts/micro_blogs_2.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
|
||||
title: "Micro blogs (2)"
|
||||
|
||||
description: "a bunch of thoughts ideas and what not that aren't worth of full blogs but that I still want to write down"
|
||||
|
||||
date: "2024-01-30"
|
||||
|
||||
draft: true
|
||||
|
||||
---
|
||||
|
||||
# I think react native or expo is haunted
|
||||
|
||||
|
39
posts/mineflayer_why.md
Normal file
39
posts/mineflayer_why.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
---
|
||||
title: "Mineflayer pains"
|
||||
description: "Describing all the pains with mineflayer I've dealt with so far"
|
||||
date: 2021-01-10
|
||||
---
|
||||
## Preface
|
||||
Given I'm gonna be complaining about [mineflayer](https://github.com/PrismarineJS/mineflayer) you may be wondering why I don't roll with something else given my complaints. The problem with that is that there is nothing else to my knowledge or at least nothing else high level, not even in other languages. There probably is and I just didn't look hard enough but oh well. Also I would've built up my own thing from scratch but reverse engineering/reimplementing a network protocol without official docs and without even unofficial docs if you're trying to do stuff with older versions is kinda hard and if you wanna see how far I got then you can look at the [repo with my work](https://github.com/Pagwin-Fedora/McProtocolLearning) and I refused to work with the slightly lower level(relative to mineflayer) [node minecraft protocol](https://github.com/PrismarineJS/node-minecraft-protocol)(made by the same person) because if I'm using someone else's work I may as well go all the way to the highest level
|
||||
## Why?
|
||||
Oh yeah I decided to make a minecraft bot because it seemed fun and it seems like there's all sorts of room to implement cool stuff though the specifics of what I'm making will probably be covered in another block post. Anyways onto the problems with mineflayer
|
||||
## constructor isn't used to create an object
|
||||
Specifically the thing that's a problem is that when you want to create a new [Bot](https://github.com/PrismarineJS/mineflayer/blob/master/docs/api.md#bot) instance you need to call the method mineflayer.createBot. Why have a method completely detached from the object that makes a new instance of that object when you can just make it a constructor I have no idea. You may be wondering "what's the big deal it's a slightly different way to instantiate the object?" the problem with that being that if you try to make a subclass of `Bot` to add methods for your own purposes you have to do janky stuff to work around the constructor not being used. In my case I did
|
||||
```typescript
|
||||
class taskBot extends mineflayer.Bot{
|
||||
...
|
||||
constructor(options:mineflayer.BotOptions){
|
||||
const bot = mineflayer.createBot(options);
|
||||
super()
|
||||
Object.assign(this,bot);
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
by the way I'm using typescript in case you can't tell which will lead to another couple of headaches I'll get into soon but yeah using `Object.assign` is absolutely not ideal at all(an as of writing I have no idea if using this hack leads to problems I haven't encountered yet). As an aside there's no reason this problem couldn't be fixed at least from the glances I've given to mineflayer's source code.
|
||||
## different ways of having a point in 3d space passed to different functions
|
||||
I hate this with a firey passion and am very happy that it's super easy to deal with this. Okay so you may be wondering "what's the big fuss?" and I'll tell you that different functions will take either a Vec3(I have a small gripe with Vec3 as well but that's not worth making a fuss about) or 3 separate arguments specifying the x, y and z coordinates. Having these 2 approaches means that there isn't a correct form to have positions stored in your program because you'll have to deal with at least 1 form that isn't the form you have them stored in anyways. This problem is an easy fix you can just have an array that stores 3 numbers in it and when you need a Vec3 or need to pass it into a function that takes 3 args use the spread operator as args to the function or the Vec3 constructor.
|
||||
## typescript hell
|
||||
As I've already mentioned I'm using typescript for my own purposes. However with typescript there are a couple of problems that come up that are annoying to deal with. Plugins and minecraft-data.
|
||||
## Plugins
|
||||
My gripe with plugins can be further subdivided into how plugins add attributes to the `Bot` instance and how they have the `Bot` instance emit new events. The first problem is that the `Bot` type has a set of attributes that typescript knows about but when you load a plugin for the Bot instance to do something like add pathfinding abilities a new attribute is added to the bot that has all the new abilities within it but typescript doesn't know that the `Bot` instance has a new attribute. The solution I found for this which also removed a bit of complexity from the code was to make a subclass of Bot and add in the plugin attributes as needed which led to the problem already described above involving the constructor. My second problem with the events was that typescript also keeps track of what events an event emitter will emit so if you try to listen for an event it won't emit it'll give you an error. But again when you run a `Bot` instance through a plugin it's type doesn't change so it doesn't get any of it's new events. Sadly the solution for this required me to commit what's effectively a typescript sin.
|
||||
```typescript
|
||||
(this as any).once(...)
|
||||
```
|
||||
I think I heard an angel die. Of course I personally don't blame these typescript problems on the developer because 1)they wrote this in javascript and mistakes can happen and 2) I don't know how I'd solve them so yeah.
|
||||
## minecraft-data
|
||||
this one's short, basically there isn't an easy way to get the type for the object you get when you provide your minecraft version to the `minecraft-data` module. There's probably a way(in fact I'm pretty confident that I'm being an idiot here) but I can't be bothered finding it
|
||||
## conclusion
|
||||
mineflayer dev if you're reading this for whatever reason please fix the problems relating to the constructor and consistent arguments to functions that take points. Although for the latter I understand if you can't make it all consistent because it would be a breaking API change in all likelyhood. Overall I think the api is alright and I don't have enough will power or brain cells to remake it for myself but I would certainly appreciate these pain points being addressed if they can.
|
111
posts/pogo_again.md
Normal file
111
posts/pogo_again.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
|
||||
title: "Pogo again"
|
||||
|
||||
description: "I swear I'm going to finish it this time (I'd borked the format for this post in hugo previously so sorry if you noticed)"
|
||||
|
||||
date: 2023-01-01
|
||||
|
||||
draft: true
|
||||
|
||||
---
|
||||
|
||||
# Another blog post that mentions Pogo
|
||||
|
||||
Welcome to another blog about pogo aka that todo list that I way over-engineered. Anyways I started over again but this time I swear I'm gonna finish it, I promise I won't throw it out again. Oh what have I done? I have the database setup and some method for interacting with the data inside which I'm 100% going to through out in favor of raw sql queries... Wait no I promise I'm doing good this time the only reason I'm probably not gonna use existing work is because I'm going to make the api able to give back data based on what's being requested and also have things be mostly stateless because that allows scaling and... Okay I might still be over-engineering this but at least the way I'm over-engineering it is by making it scale instead of making it a mess and annoying to work on. Anyways here's some explanation of what I've done so far.
|
||||
|
||||
## Initial setup
|
||||
|
||||
So to start with I made a struct corresponding to a task in this todolist app which looked like this
|
||||
```rs
|
||||
#[derive(Clone)]
|
||||
pub struct TaskV1{
|
||||
title:String,
|
||||
body:String,
|
||||
connected:Vec<String>,
|
||||
parents: Vec<rc::Weak<TaskV1>>,
|
||||
children: Vec<rc::Rc<TaskV1>>
|
||||
}
|
||||
```
|
||||
however after some work at time of writing it's looking like the task struct will either look like
|
||||
```rs
|
||||
#[derive(Clone)]
|
||||
pub struct TaskV1{
|
||||
id: Uuid,
|
||||
title:String,
|
||||
body:String,
|
||||
progress:f32,
|
||||
login:String
|
||||
}
|
||||
```
|
||||
or
|
||||
```rs
|
||||
```
|
||||
heh yeah as I said I'm considering getting rid of the methods involving tasks which would make a task struct(at least a general task struct) redundant but if you pressed me I'd tell you that
|
||||
```rs
|
||||
#[derive(Serialize,Deserialize)]
|
||||
struct TaskSerial{
|
||||
title: Option<String>,
|
||||
body: Option<String>,
|
||||
progress: Option<f32>,
|
||||
children: Option<Vec<Uuid>>,
|
||||
parents: Option<Vec<Uuid>>,
|
||||
media: Option<Vec<Uuid>>
|
||||
}
|
||||
```
|
||||
would be the task struct.
|
||||
|
||||
## But why?
|
||||
|
||||
Why would I remove some attributes like that or stick all of them into Options? Why are there a bunch of Uuids now? Also why do tasks have parents and children? These are good questions, to answer the first two I'm gonna spend a good chunk of this blog explaining what I've actually done but lemme answer the latter question real quick
|
||||
|
||||
## Task organization as a pseudo-tree
|
||||
|
||||
The reason nodes can have children is twofold: first, tasks can have subtasks and second, this allows me to avoid having a special category type which has tasks as children. This simplifies things from a code perspective and allows me to avoid duplication of functionality between categories and tasks, the only bit of redundancy is the fact that a category having progress is nonsensical but that's something I can figure out when I get to building a client. Anyways onto why Tasks underwent a bunch of change
|
||||
|
||||
## Abstracting task encoding away
|
||||
|
||||
Oh you think I started with DB stuff no no no I started by setting some stuff up to make encoding and decoding tasks as seamless as possible. Namely a versioning enum(it's boring moving on) and a Trait which was setup like this.
|
||||
```rs
|
||||
/// Trait that any method of encoding and decoding tasks needs to implement
|
||||
#[async_trait]
|
||||
pub trait TaskEncoder{
|
||||
/// The type that can be gotten from a call to either provide_identifiers or
|
||||
/// encode_task and if a value of it is gotten that way then should be usable with decode_task
|
||||
/// to retrieve the original task it must be serializable with serde due to it being the value
|
||||
/// passed around when working with tasks potentially onto disk or over network
|
||||
//Specifying DeserializeOwned may be a problem in the future if I need to deal with types with
|
||||
//lifetimes but until then this is good
|
||||
type Identifier:serde::Serialize + serde::de::DeserializeOwned;
|
||||
type EncodingError;
|
||||
type DecodingError;
|
||||
type IdentityFetchError;
|
||||
async fn encode_task(&mut self, task:TaskVersioning, login:&str)->Result<Self::Identifier,Self::EncodingError>;
|
||||
async fn decode_task(&mut self,id:Self::Identifier, login:&str)->Result<Option<TaskVersioning>,Self::DecodingError>;
|
||||
async fn provide_identifiers(&mut self, login:&str)->Result<Vec<Self::Identifier>,Self::IdentityFetchError>;
|
||||
}
|
||||
```
|
||||
Once that was done I just needed to implement it in SQL and the details involved in that made having the struct a bit more iffy. With that trait done I moved on to doing ~~your mom~~ the sql.
|
||||
|
||||
## Doing the sql(and remembering I need to store completion)
|
||||
|
||||
Yeah I forgor about the fact that I need to store task completion/progress so I added a field corresponding to that at some point but it isn't worth commenting on beyond that. Anyways as the header of this sections suggests I decided to have pogo store it's data in an sql db, specifically postgres. So I got to work making the schema making a table like this.
|
||||
```sql
|
||||
CREATE TABLE tasks {
|
||||
id uuid,
|
||||
title varchar,
|
||||
body varchar
|
||||
}
|
||||
```
|
||||
Now if you have any sql experience you'll probably understand why everything became Uuids at this point(or maybe your techniques are beyond my comprehension and you're just confused idk). The reason I added an id field in the sql is so I could look up a particular task by id corresponding to this row being added to the db is the field getting added to the struct. Where are the children, parents and content/media fields? Oh well you see from the 1 minute of googling I did postgres doesn't let you have a column which is a variable length array but more importantly I remembered that sticking structures inside of other structures isn't how sql is supposed to work. So lik a good little programmer I made some more tables. One table which you just saw is the task table which has all the tasks in it, the second table specifies parent child relations via uuids, the third table is a list of media with their own uuids and the fourth table is a relation between media ids and task ids. Combined with me not wanting to recursively fetch tasks you can see why everything is uuids now instead of having tasks in tasks. Also at some point amongst this change I added a column to the task table to specify a login via a varchar which I'm intending to use to store probably an Oauth token from google/github or alternatively an sha256hmac of their username and password, probably with additional hashing on top. Also I made some indexes over tables.
|
||||
|
||||
## Making the backend api
|
||||
|
||||
So now we've explained the first possible end point for the task struct that point being just 1 row in the task table. But what about the one with all the Options or the argument that the task struct won't be a thing anymore? Well that's due to the rest inspired http api. While I haven't finished implementing it yet I think I'm done designing the stuff related to the task object it so here are the endpoints relating to tasks.
|
||||
```
|
||||
GET /task/$id
|
||||
PUT /task
|
||||
UPDATE /task/$id
|
||||
DELETE /task/$id
|
||||
```
|
||||
PUT just creates a task with everything set to default and gives back the uuid of that task and DELETE just deletes it. The ones which make things iffy are GET and UPDATE which both allow the client making a request to the api to only provide some of a task or for it to only update some of the task. Due to data now being optional the original task struct didn't represent what endpoints would be delivering, what's more it wasn't needed for database queries due to SQL queries being decently flexible with me using direct SQL queries through [sqlx](https://crates.io/crates/sqlx).
|
61
posts/pogo_so_far.md
Normal file
61
posts/pogo_so_far.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
|
||||
title: "Pogo so far"
|
||||
|
||||
description: "A summary of the work I've done on pogo so far and where I intend to go with it"
|
||||
|
||||
date: 2022-01-31
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
So in [My last blog](https://pagwin.xyz/blog/gh_actions/), I briefly mentioned a project I'm working on called [Pogo](https://github.com/Pagwin-Fedora/Pogo) and then said absolutely nothing about what it actually is or what I was going to do with it. I did say I wanted to write a blog article on it though and this is that blog article.
|
||||
|
||||
## What is Pogo?
|
||||
|
||||
[Pogo](https://github.com/Pagwin-Fedora/Pogo) is a todo list I'm working on that I wanted to use as an excuse to learn
|
||||
|
||||
1. [Go](https://go.dev/)
|
||||
2. [Rust](https://www.rust-lang.org/)'s FFI
|
||||
3. how to store and retrieve data with [SQL](https://en.wikipedia.org/wiki/SQL) queries/statements
|
||||
3. how to make UI in [Dart](https://dart.dev/)/[Flutter](https://flutter.dev/)
|
||||
4. and how to write [Sphaghetti](https://www.goya.com/media/4173/creole-spaghetti.jpg?quality=80) in C++
|
||||
|
||||
I put it this all toegether with copious usage of FFI to connect the Rust, Go and [C++](https://en.wikipedia.org/wiki/C%2B%2B) for the backend and the dart/flutter for the frontend.
|
||||
|
||||
## Problems
|
||||
|
||||
But recently I've decided that all of that is dumb and unnecessary. Initially it made sense to use the somewhat unwieldy FFI setup because the backend was going to take a struct instead of a string and structs cannot be passed via stdin<sup>[citation needed]</sup>. Once I realized how hard and unnecessary that was due to FFI things I dropped it. With that my architecture was just passing around strings and I realized that my setup is overly complicated for that. So instead of FFI and TCP spaghetti instead I'm going to have ~~subprocess spaghetti~~ each component of the application be separate executables of some kind or another.
|
||||
|
||||
## The New Architecture
|
||||
|
||||
This description is going to suck so I recommend clicking off if you get bored. For my new architecture there will be 5 components
|
||||
|
||||
1. The init script
|
||||
2. The frontend
|
||||
3. the front-to-back middleware
|
||||
4. the back-to-front middleware
|
||||
5. and The backend.
|
||||
|
||||
The original repository that held all the code will have it all removed as the frontend, middleware and backend are developed in separate repositories which are had as git submodules in the main repository which will be where an init script and a build script will be held.
|
||||
|
||||
### The init script
|
||||
|
||||
The init script is a script that will be running as long as the application itself holding the frontend, middleware and backend as child processes.
|
||||
|
||||
### The frontend
|
||||
|
||||
The frontend will accept user input and print it to stdout in a manner understood by the middleware and passing state changes given to it as Pogo messages by stdin to the user.
|
||||
|
||||
### The middleware
|
||||
|
||||
The front to back middleware will take messages from the frontend and parse them for the backend to understand and vice versa for back to front.
|
||||
|
||||
### The backend
|
||||
|
||||
The backend takes messages from stdin and does transforms to the db and accepts queries of it's current state printing out the state to stdout.
|
||||
|
||||
## Conclusion
|
||||
|
||||
I expected this blog to be longer but I realized as I started to write it that writing about what I had previously done with Pogo was boring because it was nothing interesting. Well there is some interesting stuff in how I previously was working on Pogo but most of that is too tangential for a post on the project itself such as how bad C/C++ networking is, how good networking is with rust after I stopped being an idiot, doing battle with Go's sql library and stuff related to FFI. I may make a blog post or two on these topics in the future but I still need to learn more about them to do them justice. Overall the post was a bit painful to write and I suspect it's also painful to read. Note to self write about a project as interesting stuff comes up not to explain yourself.
|
103
posts/rust_enums.md
Normal file
103
posts/rust_enums.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
|
||||
title: "Rust Match and Enums"
|
||||
|
||||
description: "A blog article I wrote to win a dumb argument"
|
||||
|
||||
date: "2022-09-06"
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
As the description of this article says I wrote this to try and probably fail to win an argument. Namely, a friend ~~argued~~ asked "Why use rust enums just use an interface and manual type checks?" specifically the context was me advocating for the use of rust enums instead of C's union types. Example:
|
||||
```dart
|
||||
String anything(Object something) {
|
||||
if(something is bool){
|
||||
if(something) return "fizz";
|
||||
return "buzz";
|
||||
}
|
||||
if(something is int){
|
||||
if(something < 10) return "smol";
|
||||
if(something < 20) return "Mcdonald's medium";
|
||||
return "LARGE";
|
||||
}
|
||||
return "$something";
|
||||
}
|
||||
```
|
||||
Personally, I think this is a bad solution to the problem of having multiple valid types for something generally speaking. Specifically speaking taking an argument which we only know implements some interface can be good.
|
||||
|
||||
## Rust enum example
|
||||
|
||||
But I haven't made any arguments for my position yet I've just specified how you can go without C's unions which are terrible and you should never use them outside of the implementation of something that solves the problem in a better way. What makes rust enums great for this use case is that we are explicitly limiting what can be passed in at compile time and are being explicit about what we want and why. The rust equivalent to that example is
|
||||
```rs
|
||||
//why a 128 bit integer and not just a 64 bit? Well because I wanted to show off that rust has that that's why
|
||||
enum OurEnum{InterviewQuestion(bool), FastFoodOrder(u128), Other(Box<dyn Display>)}
|
||||
fn anything(something:OurEnum)->String{
|
||||
match OurEnum{
|
||||
InterviewQuestion(true)=>{
|
||||
"fizz"
|
||||
},
|
||||
InterviewQuestion(false) => {
|
||||
"buzz"
|
||||
}
|
||||
FastFoodOrder(size_index) => {
|
||||
if size_index < 10 {
|
||||
"smol"
|
||||
}
|
||||
else if size_index < 20 {
|
||||
"McDonald's medium"
|
||||
}
|
||||
else {
|
||||
"LARGE"
|
||||
}.into()
|
||||
},
|
||||
Other(displayable) => {
|
||||
format!("{}", displayable)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Now you might be saying "oh my god that's way more verbose why would I want that?!?!". The reason you want that is because all that verbosity provides more information to people who have to deal with this code in the future ~~and also partially due to rust having &str and String as different things as well as me showing off a bit~~. So what new information do we get with this code that we don't get with the Dart code?
|
||||
|
||||
## Each case has a name
|
||||
|
||||
As the section title says each case has a name, now someone reading this code knows that our boolean case corresponds to InterviewQuestion whatever that may mean in a larger context. This also means that if we want different things for the same type under certain conditions we just need to add something to the enum.
|
||||
|
||||
|
||||
## Happy little compiler errors
|
||||
|
||||
In addition to the whole deal of enums helping self-document they also make it so if we fuck up we get a compiler error unless we use if let or _ but if you use those you are your own worst enemy. This also makes it so if we add a case we can't compile the code unless we handle that case everywhere we're doing stuff with our Enum. What this also means is that if we only wanted to handle booleans and unsigned integers we could do that without runtime errors.
|
||||
|
||||
## Oh hey I implement your interface now fuck you
|
||||
|
||||
Oh yeah also with dart interfaces nothing is stopping someone from just implementing the interface you take as an argument and just sending that to your function that takes that interface which would just completely mess everything up.
|
||||
|
||||
## Bonus points
|
||||
|
||||
These are more cool things with this code but they aren't as closely related to enums.
|
||||
|
||||
### Match being awesome
|
||||
|
||||
So you may notice that OurEnum::InterviewQuestion is referred to twice, the reason for that is that match allows you to rather than capture the value in an enum in a variable, just check if the value inside that case of the enum is some specified value, in this case, it'd be more convenient to just capture and use if else but it's still cool that we don't have to.
|
||||
|
||||
### Hello Interface my old friend
|
||||
|
||||
So remember how I said we shouldn't use an interface for this but interfaces can be useful in other cases. Well, the Other case for OurEnum is using an interface. Oh sorry, it's using a "trait", more specifically a trait object but the details of that go over my head so yeah it's an interface. In this case, we're specifying that we only want things that implement [Display](https://doc.rust-lang.org/std/fmt/trait.Display.html). What this means is that we can display it as a string. That may seem obvious but some datatypes may for some reason or another not play nicely with the idea of being displayed like that which won't implement the Display trait meaning it can't be given as an input to this function.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Accept it person I'm arguing with Rust enums are superior, if anyone complains about a lack of C Unions in Dart maybe consider suggesting Rust enums... or just copy [typescript's unions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) which is probably what these people are actually advocating for and tbh while technically inferior to rust enums has its own merits.
|
||||
|
||||
## Is how I would've ended it
|
||||
|
||||
But arguments/discussions aren't one sided like that and I chatted with the person shortly after writing this article and they made an interesting point.
|
||||
|
||||
## If the type fits you should accept it
|
||||
This is a rebuke to my "Oh hey I implement your interface now, fuck you" and is simply asking how that's a problem. After all the interface should specify what you need from some data type to be able to use it in some application. This also moves the control flow of which code to run outside of the method taking in data. Combined with the idea that the interface setup can also give compiler errors where it makes sense the question has to be now be asked why are rust enums preferable?
|
||||
|
||||
## welll uuuuuuuuuuuuh
|
||||
When you have a finite number of states which may contain state within themselves and aren't just representing different input types they're pretty nice.
|
||||
|
||||
## Conclusion 2(Electric boogaloo)
|
||||
Use interfaces when you want certain garantees about a type, use rust enums when you want a finite set of states.
|
70
posts/rust_type_hiccups.md
Normal file
70
posts/rust_type_hiccups.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
|
||||
title: "Rust Type Hiccups"
|
||||
|
||||
description: "A couple of hiccups I've run into with rust's type system"
|
||||
|
||||
date: 2022-12-20
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# Rust type hiccups
|
||||
|
||||
This article is the result of running into annoyances in rust's type system. These annoyances aren't bugs as much as they are limitations caused by how rust's type system is currently implemented. Interestingly one of these seems relatively easy to fix at first glance while the other is a problem whose root cause(s) have been around for a good few years and probably won't be fixed for at least some ways into the future.
|
||||
|
||||
## Case 1: From\<Result\<T,E\>\> for Result\<From\<T\>,E\> and vice versa for E
|
||||
|
||||
Did you know that rust didn't have this implemented for you? I certainly didn't until about a week ago when I wanted to convert from a `Result<String, Error>` to a `Result<(), Error>` for the sake of convenience when returning from a function. Now you may think that this is the one which I think is the easy fix but if you thought that you'd be wrong.
|
||||
|
||||
### Why this seems easy
|
||||
|
||||
If you don't know rust or didn't think about this that much yet you may wonder how this is easy. Well to answer that I think I'll just show you the implementation.
|
||||
```rs
|
||||
impl <T,U:From<T>,E> From<Result<T,E>> for Result<U,E> {
|
||||
fn from(val:Result<T,E>){
|
||||
val.map(U::from)
|
||||
}
|
||||
}
|
||||
```
|
||||
That's it the entire definition and implementation of what I want in just 5 lines of code where 2 of them are just curly brackets. So why doesn't that work?
|
||||
|
||||
### The motherf\*cking identity implementation
|
||||
|
||||
Yeah, the issue is that rust implements `From<T> for T` so all types can be gotten from themselves. The issue with this is twofold
|
||||
1. it means that U can be T which means that
|
||||
2. the implementation we just wrote out before implements `From<Result<T, E>> for Result<T, E>` which is the identity which means we have two implementations of a trait on one type which rust doesn't allow. By the way, if you want to make an error message more helpful in rust change the error message you get when you try this to point out that the issue is that U can be T.
|
||||
|
||||
### Can we just tell rust to not implement this when U = T?
|
||||
|
||||
Nope not a thing in rust at the moment and I remember(perhaps incorrectly) that [negative bounds](https://github.com/rust-lang/rust/issues/42721) are for traits but not types generally.
|
||||
|
||||
### What about [specialization](https://doc.rust-lang.org/unstable-book/language-features/specialization.html)?
|
||||
|
||||
Nope
|
||||
1. right now the [current subset of specialization](https://doc.rust-lang.org/unstable-book/language-features/min-specialization.html) that's considered stable doesn't allow for implementation in a case of a generic popping up twice which is the exact issue we have here
|
||||
2. the identity implementation From has doesn't have the default keyword so if you wanna override it you're kinda fucked(unless you're trying to modify the stdlib like I was).
|
||||
|
||||
## So what was the other hiccup?
|
||||
|
||||
Oh yeah, there was a separate hiccup. Yeah that hiccup came from me trying to do SI-derived units via const generics, my minimum viable product version looked something like this
|
||||
```rs
|
||||
struct Measure<const km:i16, const sec:i16, const kg:i16>{
|
||||
amount:f64
|
||||
}
|
||||
impl<const km1:i16, const sec1:i16,const kg1:i16,const km2:i16, const sec2:i16,const kg2:i16> Div<Rhs=Measure<km2,sec2,kg2>> for Measure<km1,sec1,kg1> {
|
||||
// Don't mind the curly brackets rust compiler wanted me to put them there for whatever reason
|
||||
type Output = Measure<{km1-km2},{sec1-sec2},{kg1-kg2}>;
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
Self::Output{
|
||||
amount: self.amount/rhs.amount
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
and had I not run into my type hiccup Mul would've been implemented similarly and Add and Sub would've been trivial. Alas, rust doesn't allow this to compile because `cannot perform const operation using kg1` and so on for all the other const generics... what? What do you mean I can't do a const operation with a const wtf ~~I also got an error that said something about how an associated type wasn't allowed when I set Rhs but shhhhh~~. Yeah in the compiler I imagine that allowing for calculating const generics with values that are const/const generics is relatively easy so I hope that does get improved at some point.
|
||||
|
||||
## Conclusion
|
||||
|
||||
I like rust's type system and most of the time it does a pretty good job, however when it doesn't work it's annoying. These 2 examples being particularly unsatisfying because they're so natural but oh well hopefully rust is able to make them work in the future.
|
37
posts/serenity_why.md
Normal file
37
posts/serenity_why.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
|
||||
title: "Serenity pains"
|
||||
|
||||
description: "A rant against how serenity forces the developer to handle state"
|
||||
|
||||
date: 2022-09-01
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
This is the second blog article I've written dissing on how a library does something, the [first time](/blog/mineflayer_why) being effectively "Mineflayer doesn't do things the way I want it to and that annoys me". This article will probably follow in that tradition but unlike the Mineflayer one where I would've been inconvenienced to do what I want with Serenity I need to make my code potentially unsound to do what I want.
|
||||
|
||||
## What is Serenity?
|
||||
|
||||
[Serenity](https://crates.io/crates/serenity) is a rust crate that can be used to create discord bots. I've used it a couple of times without issue mainly for [DBMS](https://github.com/Pagwin-Fedora/DBMS) and [frame yeet](https://github.com/Pagwin-Fedora/discord-frame-yeet), very professional names I know. However the issues come up when you want mutable state in the [event handler](https://docs.rs/serenity/0.11.5/serenity/client/trait.EventHandler.html).
|
||||
|
||||
## No mutable self
|
||||
|
||||
For all the events in the event handler self is passed as a non-mutable reference. Which means you can't change any state in your event handler. Serenity... why? Have you not considered the possibility that someone may want to initialize something in their event handler after the bot has started or to have the response to events change due to previous events?
|
||||
|
||||
## Yes they did consider the possibility
|
||||
|
||||
It's why they pass a [Context](https://docs.rs/serenity/0.11.5/serenity/prelude/struct.Context.html) struct which has a field called data where data can be stored in a [TypeMap](https://docs.rs/serenity/0.11.5/serenity/prelude/struct.TypeMap.html) which mostly solves the inability to have mutable state. Keyword **mostly**
|
||||
|
||||
## Drop the state
|
||||
|
||||
The issue is what happens if the state being stored is attached to something else and you want the state to be dropped when that something else is dropped. Well in that case you just have to suffer in some way or another. You obviously need to implement the [Drop trait](https://doc.rust-lang.org/std/ops/trait.Drop.html) which means the struct needs to store a way to refer to the TypeMap in itself. But Serenity doesn't have a way to do that before you've constructed the client. The [ClientBuilder](https://docs.rs/serenity/0.11.5/serenity/client/struct.ClientBuilder.html) has the type\_map method if you want to provide one but that moves it which means you can't hold onto a reference to it that can be used in our Drop implementation. The ClientBuilder also has a get\_type\_map method which gives a reference to it's type map. But because it's a reference we can't give it to the thing that wants to potentially get rid of something in the TypeMap. Which leaves only ugly and uglier options for dealing with this.
|
||||
|
||||
## Ugly solutions
|
||||
|
||||
The first solution to this problem is to tell serenity to go fuck itself and write unsafe code convert self to a mutable pointer which we mutate in the ready event to initialize state within the handler with the Arc<RwLock<TypeMap>> that's provided by the Context struct. The second solution is to give up on having Drop clean up the TypeMap at the same time. Personally I think both of these solutions suck and wish that I wasn't forced to pick for the program I ran into this problem for.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This is the only Serenity specific problem I've run into using this library though so I'd say it's a good library overall. So yeah, back to actually writing code instead of blog articles for now.
|
98
posts/speedrun.md
Normal file
98
posts/speedrun.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
|
||||
title: "Speeding through misc stuff"
|
||||
|
||||
description: "I want to get out a bunch of stuff that's in the git repo or my head so I'm dumping it here"
|
||||
|
||||
date: 2023-11-11
|
||||
|
||||
draft: false
|
||||
|
||||
---
|
||||
|
||||
# Speedrun time
|
||||
|
||||
Alright I have stuff in the git repo for this website and in my head that I want to get out so let's go.
|
||||
|
||||
## This blog is run via a docker container now
|
||||
|
||||
I was planning on adding comments which meant doing more than static files so it made sense to do that comments system via docker and to make deployment consistent I setup the base website to work via docker as well, unfortunately the comment system died at the last hurdle of sending out email(s) to verify a user's email but whatever it's suffieciently close to done that I'll probably finish it later.
|
||||
|
||||
## Numbers with finite vs infinite kolmogorov complexity
|
||||
|
||||
This is a blog post in the website git repo it's pretty much fully written but I decided I didn't want to publish it fully. You'll find the markdown [here](https://github.com/Pagwin-Fedora/website/blob/master/content/blog/finite_KCMP_nums.md). To describe the idea in brief for any mathematical object you can either describe it with a program written in a turing complete program that takes up a finite amount of space or you can't. If you can and the value you get as you run the program for an arbitrarily large number of steps is well defined then it has finite kolmogorove complexity and if you can't it has infinite. The blog post describes why the well defined part is needed as well as a badly written proof for why the set of all real numbers with finite kolmogorov complexity has the same cardinality as the intgers and ends with unfalsifiable nonsense conjecture about the universe. Sidenote: if you try to brute force the value of ψ (as described in the full post) you'll find that the cardinality of the set of all possible values for ψ is the same as for the Real numbers which is amusing.
|
||||
|
||||
## Remember pogo?
|
||||
|
||||
Lol I redid it again and wrote a blog post on it but didn't publish it so that's amusing.
|
||||
|
||||
## Music notation
|
||||
|
||||
So about a week ago I saw [this tantacrul video](https://www.youtube.com/watch?v=Eq3bUFgEcb4) and towards the start of it I decided to come up with a new music notation for fun. I yeeted the notes because I thought arbitrary symbols seemed dumb made use of color to communicate things like sharps and other symbols while keeping the current number of bars. Here's what I wrote down with little to no influence from the video.
|
||||
```
|
||||
rather than arbitrary symbols lets use lines
|
||||
length is encoded via the length of a line
|
||||
pitch is encoded via the vertical position of a line
|
||||
lines without any modifiers are colored red
|
||||
blue lines are used when a note is sharp
|
||||
green when flat
|
||||
purple, orange, dark cyan and gold remaining for various other things
|
||||
|
||||
greyscale colors can't be used for line colors because black and white are the background and they don't stand out enough of particular note white can't be used because it's used for spacing things meaning it could be misconstrued as the space between notes/dashing it can make 1 note look like multiple notes and black is needed for clarification in things like pitch and possibly the end of one note and the beginning of another due to it possibly being difficult to tell when a note ends and another begins if they're similar pitch
|
||||
|
||||
if a note needs multiple specifiers those specifiers should be dashed with each other aka they should alternate back and forth at a regular interval, if it needs multiple of the same specifier lines of the needed color should branch out of the line at the start of the line
|
||||
|
||||
if multiple notes with different pitches need to be played you draw multiple lines
|
||||
|
||||
another thing that can be used to specify information about a note is the shape of the front and back of the line, pointed, rounded and block are all shapes that can be taken, different specifiers can be used at the beginning and end of a line but these specifiers should only be used for things that are relevant at the beginning and end of a note respectively
|
||||
|
||||
all notes need to end in a black endpoint to visually clarify the end of a note
|
||||
|
||||
background staff I think it's called is kept the same so pitch is clear
|
||||
```
|
||||
After watching the video I realized that I had reinvented piano bars and that I left a lot unspecified and had 2 problem, 1) short notes are hard to diferentiate from each other 2) this setup screws over color blind people. There are hacky solutions to those problems and I could work on making this system more complete but I did it for fun and I'm bored of it now so I won't.
|
||||
|
||||
## I'm dipping my toes in haskell and I made a monstrosity
|
||||
|
||||
```hs
|
||||
data SubShell = S Integer Integer | P Integer Integer | D Integer Integer | F Integer Integer deriving (Show, Eq)
|
||||
|
||||
instance Ord SubShell where
|
||||
S l1 _ <= P l2 _ | l1==l2 = True
|
||||
S l1 _ <= D l2 _ | l1==l2 = True
|
||||
S l1 _ <= F l2 _ | l1==l2 = True
|
||||
P l1 _ <= D l2 _ | l1==l2 = True
|
||||
P l1 _ <= F l2 _ | l1==l2 = True
|
||||
D l1 _ <= F l2 _ | l1==l2 = True
|
||||
S l1 _ <= S l2 _ = l1 <= l2
|
||||
S l1 _ <= P l2 _ = l1 <= l2
|
||||
S l1 _ <= D l2 _ = l1 <= l2
|
||||
S l1 _ <= F l2 _ = l1 <= l2
|
||||
P l1 _ <= S l2 _ = l1 <= l2
|
||||
P l1 _ <= P l2 _ = l1 <= l2
|
||||
P l1 _ <= D l2 _ = l1 <= l2
|
||||
P l1 _ <= F l2 _ = l1 <= l2
|
||||
D l1 _ <= S l2 _ = l1 <= l2
|
||||
D l1 _ <= P l2 _ = l1 <= l2
|
||||
D l1 _ <= D l2 _ = l1 <= l2
|
||||
D l1 _ <= F l2 _ = l1 <= l2
|
||||
F l1 _ <= S l2 _ = l1 <= l2
|
||||
F l1 _ <= P l2 _ = l1 <= l2
|
||||
F l1 _ <= D l2 _ = l1 <= l2
|
||||
F l1 _ <= F l2 _ = l1 <= l2
|
||||
|
||||
```
|
||||
|
||||
:) this was for modeling electron subshells btw here's a [gist](https://gist.github.com/Pagwin-Fedora/07042faaa3e5ae275652874b47cb969f) with the full program.
|
||||
|
||||
## The fractions sidequest continues
|
||||
|
||||
After my [blog post](https://pagwin.xyz/blog/fractions_sidequest/) on optimizing this I've gone further by implementing the ideas I suggested at the end. At this point I believe my choke points are the progress bar (if I remove it I'll get a 2x speedup lol), the queue (I'm thinking of implementing it myself to use a fixed buffer which it goes through in a circle/ring and uses a couple of [AtomicUsize](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html) values) and doing IO (I don't think I can speed this up very much unfortunately).
|
||||
|
||||
## My writing sucks
|
||||
|
||||
My sentences go on for too long and I generally think I ramble a bit too much but I also don't want to do a lot of editing for these blogs because I like just writing them in a flow state and doing editing later is very boring/unfun >:(.
|
||||
|
||||
## Microblogging hmmmmmmm
|
||||
|
||||
I've been thinking of setting up my own mastodon instance recently but I want everything including th mastodon instance in containers so I can pack up and move servers easily and I'm not sure how to do that with mastodon. Not sure if I wanna do that though because I wouldn't use it that much and I like being able to direct my writing energy into macro blog posts and more blog posts like this one. I suspect this won't be the last time I use this format. Anyways that's all folks I hope you have a nice day.
|
87
posts/universal_proc_tut.md
Normal file
87
posts/universal_proc_tut.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: "A universal tutorial on the basics for every* programming language"
|
||||
description: "A tutorial that covers all the basic concepts that procedural languages commonly have"
|
||||
date: 2021-07-03
|
||||
draft: false
|
||||
---
|
||||
## Prelude
|
||||
No this isn't comprehensive, so no you won't be able to immediately go start making something after having read this and no this doesn't cover everything you might possibly run into, see [#The Asterisk](#The%20Asterisk). Also the examples will be in Pseudocode so this isn't a generic python/javascript tutorial :P.
|
||||
|
||||
## The Asterisk
|
||||
If you didn't notice the asterisk in the title next to every, now you know there is one. The reason for that asterisk is that this blog only covers stuff that is common(as built in language features) in procedural programming. If you don't know what "procedural" means don't worry about it and pretend that this covers every programming language.
|
||||
|
||||
## The Data
|
||||
Fundamentally programming is about the manipulation of data for varying purposes. On occasion a programmer will want data to be displayed which can be accomplished in many ways. For the purposes of this blog `DISPLAY(some_data)` indicates that whatever `some_data` is should be displayed. Now that data can be displayed, what data can we have? There are a few different types of data we can have<sup>[[1]](#1)</sup>. For now I'll only specify 3. Those 3 are strings, numbers and booleans. First there are strings, more commonly known as text. In many programming languages, including our pseudocode, we can create a string in our program by surrounding some text with quotes `"like this"`. Our second type is numbers which hold numbers. We can of course add, subtract, multiply, divide and raise to a power all of these numbers with the notation of `number1 + number2`, `number1 - number2`, `number1 * number2`, `number1 / number2` and `number1 ^ number2` respectively. Last but not least we have booleans which can hold the values of `true` and `false`. Just like how we can apply different operations onto different number we also have some operations we can apply to booleans. First we have `NOT` which will take a boolean and give back the opposite boolean e.g. true → false and false → true. Second we have `OR` which takes 2 booleans and gives back true if either of them are true and false otherwise. Third we have `AND` which takes 2 booleans and gives back true only if both of them are true and false otherwise. Finally we have `XOR` which will only give back true if only 1 of it's inputs is true eg `true XOR true` → false and `false XOR false` → false but `true XOR false` → true.
|
||||
|
||||
## Variables
|
||||
Variables allow for the storage of data in named<sup>[[2]](#2)</sup> buckets which is very useful especially when taking input that can change at runtime. We can put a value into a variable like so `someVar ← "a string"` which would set the variable someVar to the string value of "a string".
|
||||
|
||||
|
||||
## Conditionals
|
||||
Conditionals(specified with `IF`) are statements that can allow us to gate code behind some condition being true. Besides the obvious of just specifying a boolean value ourself we can also perform certain tests on other values to get booleans. First we can see if 2 values are the same i.e. the same type and the same value of that type which we can do with a simple `value1 = value2` for ease of syntax we can also see if they are not equal to each other with `value1 ≠ value2` furthermore if both values are numbers we can see if one if greater than or less than the other with `value1 > value2` and `value1 < value2` respectively. I should probably provide an example snippet of pseudocode
|
||||
```
|
||||
someNum ← 5
|
||||
"Sidenote: when I have a statement that can take multiple lines of code"
|
||||
"I'll surround those lines with curly brackets, also when I need to make"
|
||||
"a comment which is intended to do nothing to the code like this it'll be"
|
||||
"a free floating string(s) which nothing is done with like this"
|
||||
IF someNum = 5 {
|
||||
DISPLAY("someNum is indeed 5")
|
||||
}
|
||||
ELSE {
|
||||
DISPLAY("I have no idea how this happened")
|
||||
}
|
||||
```
|
||||
|
||||
## Procedures
|
||||
Procedures are blocks of code which we can run when we want without having to copy and paste, furthermore we can change the values stored in variables in the procedure that the procedure lets us commonly referred to as arguments, an example of a procedure(in most procedural languages) is one which lets the programmer display some text which in our case is called `DISPLAY` the exact implementation of which is mostly unimportant for us to declare our own procedure we'll just put the word `PROC` before the name of our procedure and the arguments that our procedure takes separated by commas in parentheseses after the procedure's name for ane example of one of our user defined procedures I'll just write a procedure that prints out "Hello!" and then whatever was provided as an argument to it
|
||||
```
|
||||
PROC helloProcedure (name) {
|
||||
DISPLAY("Hello!")
|
||||
DISPLAY(name)
|
||||
}
|
||||
"the code above hasn't been run yet we need to call the procedure in order to run it like below"
|
||||
helloProcedure("World")
|
||||
```
|
||||
## Lists
|
||||
lists are a methodology of storing multiple data values within a single variable<sup>[[4]](#4)</sup>. When you want to store or retrieve a value from a list you need to specify at what position in the list you want to retrieve the value from via a number with the indexing of that list commonly starting from 0 with certain very special languages which choose to start or allow the programmer to start lists from indices other than 0. When declaring a list in our pseudocode I'll use square brackets surrounded comma separated values of the list, the size of the list will not be set in stone for convenience but it should be noted that in most programming languages you need to be explicit when making a list larger. An example of creating and using a list is shown below
|
||||
```
|
||||
someList <- ["First item", "Second item", "meh item"]
|
||||
someList[2] <- "Third item"
|
||||
"First item should be displayed when the line of code below is run"
|
||||
DISPLAY(someList[0])
|
||||
```
|
||||
## Loops
|
||||
loops are kinda self explanatory as they loop running code repeatedly. Commonly there are 2 types of loop in programming languages and when there are more they can be easily described in relation to these 2. These 2 types of loop are called a `WHILE` loop and a `FOREACH`<sup>[[3]](#3)</sup> loop. A `WHILE` loop will repeatedly run whatever code is inside of them as long as a condition is met, a foreach loop will go through each item in a collection of items such as a list and run some code using that item. Both loops make working with lists much easier because we don't need to write out the code for every single list entry and if the list we may be dealing with will have a size that cannot be known at the time of writing the program might, with the combination of some methodology of retrieving the length of the list, be the only option. the syntax for `FOREACH` loops will borrow from the syntax for assignment with the variable which will go through each value in our list being assigned to and the loop values are being pulled from being used as the value to assign from
|
||||
```
|
||||
i <- 0
|
||||
"initializing an empty list which will be filled with the while loop below"
|
||||
dataSet = []
|
||||
WHILE i < 50 {
|
||||
dataSet[i] <- i + 1
|
||||
}
|
||||
"both loops below will display all the values in the list in the var dataSet"
|
||||
i <- 0
|
||||
WHILE i < 50 {
|
||||
DISPLAY(dataSet[i])
|
||||
"August 2024 pagwin here, just realized I"
|
||||
"also forgot this for the other while loop"
|
||||
"if you saw this article prior to March 2024"
|
||||
"I forgot to include the line below sorry about that."
|
||||
i <- i + 1
|
||||
}
|
||||
FOREACH n <- dataSet {
|
||||
DISPLAY(n)
|
||||
}
|
||||
```
|
||||
## Conclusion
|
||||
That should cover the basic syntax features someone trying to learn a procedural programming langauge should be trying to learn. Some programming languages have macros, terenary statements and extensive standard libraries which will have a bunch of useful utilities those are best learned once these basic syntax features are understood and different langauges tend to differ regarding what and whether these features are include
|
||||
|
||||
## Footnotes
|
||||
<a href="./#1" name="1">1</a> - I see you in the back there person who wants to be technically correct(if I wasn't writing this it'd be me) with your statements about how in C a lot of what differentiates types is just dereferencing the integer pointers down to their values and pretending chars aren't integers but I don't care so shut up.
|
||||
|
||||
<a href="./#2" name="2">2</a> - most programming languages have certain specifications on what you can name your variables and there's also reccomended ways you should name your variables and both of these can vary per language so you should probably read their documentation for specifics but for my purposes I'm gonna stick to [camelCase](https://en.wikipedia.org/wiki/Camel_case) using only english letters as I'm unaware of any non-esoteric programing languages which disallow such naming
|
||||
|
||||
<a href="./#3" name="3">3</a> - some programming languages will call foreach loops just for loops but most languages call them foreach loops
|
||||
|
||||
<a href="./#4" name="4">4</a> - Hello pagwin from March 2024 here, I was reading this back and realized that it's more complicated than this and I forgot to mention that at the time. As you can see from the code below the 4 annotation oftentimes you can assign to elements of a list individually without needing to remake the entire list around it and assign the whole list to the variable again. A more accurate description is a list is a way to hold multiple buckets you can store values in within a single variable. Going beyond that involves talking about references and that's beyond this article's scope.
|
Loading…
Reference in a new issue