Yeah TUIs are great. I'm shit at making GUIs but I was able to slap together a relatively simple TUI for some tooling at work, without any AI. Cheaper, faster, cross platform, less intensive. Most TUI frameworks nowadays have mouse support as well etc. So what if it looks just a little dated?
The bad part is that Microsoft (generally) used to have some of the best QA in the industry, and that was somewhat extended to Xbox. But they laid off their QA team a year or two ago because "AI will change everything!"
> But they laid off their QA team a year or two ago because "AI will change everything!"
They gutted QA in 2014, ending their software development engineer in test positions, and suggesting the developers should test their own code to get velocity.
Subsequent releases were worse than before, and things have compounded since then. Hard to say that laying off the rest of the team made a noticable difference.
Oh thanks for the correction/supplemental information. The gutting you're referring to might be the huge lay offs that I'm thinking of. Possibly conflating the recent AI driven layoffs with those from 12 years ago.
This definitely does not extend to games. I remember microsoft games having terrible UX over a decade ago (not the game, the account garbage around it).
So, I've had a pretty shit time with Microsoft products over the last ten years, but it wasn't quite as bad 5-7 years ago. Our disagreement on that might be that I'm only in my late 20s, so if you're older than I am, or had newer consoles than I had around that time, I might be a little more tolerant of jank.
But according to the other replies, it seems Microsoft has been purging QA for years now.
Thank you for the correction! I think I was conflating the two (having heard about the one from 2014, and then also hearing about the more recent rounds, and not recognizing they were different)
And also it has worse performance than Java if you just install a basic optimization modpack like Adrenaline on Java, which many players do. There's effectively ~zero reason to use Bedrock if you're on PC.
In Microsoft's defense at the time the java code base had a ton of technical debt and overtime they've essentially done a full rewrite of everything.
That said, I'm convinced that 90% of the reason that the current mess exists is because Microsoft was convinced that most users would be migrating from Java to bedrock. And they probably would have (modding and other things aside), had the Microsoft team not decided to "fix" many aspects of redstone.
Since then there's been the constant issue of parity between the two versions, which will likely never be achieved. And 90% of the time it's because the Microsoft developers working on bedrock edition decide they know better than mojang does, so they can implement a feature in a different way, or just implement a new feature entirely.
Ugh it's a mess, and I've spent way too long thinking about it.
As someone who's messed with redstone on both, Bedrock is uniquely infuriating because it adds some really cool things (being able to move entity blocks like chests and furnaces comes to mind) while completely destroying the groundwork of even moderately advanced redstone. The most visible is the timing for certain things becoming nondeterministic, and while technically a bug originally, quasi-connectivity is extremely useful
I've never been more thoroughly convinced that I would like ruby more than from this article. I'm currently stuck reaching for python a lot of the time (absolutely love it tbc), but maybe it's worth changing things up and trying to give ruby a shot.
It was one of the first programming languages I was introduced to at 16 or so, but an older person that I looked up to told me it would get me stuck in "hobby coder land". He was wrong in so many ways, but even if he was right, I wanna have fun in my hobby code :)
I've had one job in my life, still at the same company. (8 years).
I applied cause the listing mentioned Python, and I was programming in Python at the time.
Once I started they were like yeah we put that there to reach a broader public but we use Ruby (on Rails).
So that's what I learned. I've just returned to Python via LLM's. I literally have not felt the need nor desire to use Python once I got used to writing Ruby.
Hi, out of curiosity is there a way to persistently set the `--native-tls` flag? UV always fails without it because of Zscalar configurations at my day job.
Also, is there any plan to add support for specifying that a compatible python version for a specific architecture? One of the packages I maintain at work has to use 32 bit python, and I always have to pass the `--python /path/to/32bit`
Yes you can set it via an environment variable (UV_NATIVE_TLS=true) or in your uv.toml (native-tls = true). However, check the docs, I think they are renaming it to sytem-certs.
Looks like I forgot to answer the other half :) Yes you can pin a specific Python like `uv python pin cpython-3.14.0-macos-aarch64-none`, it will create a `.python-version` file which will be respected when you’re in the directory.
When I learned Scheme, I liked the language but strongly disliked macros and quotation. I'd only been using it a short while and when I searched for solutions to a few problems these "fexpr" things kept appearing up, which i didn't understand, and this "Kernel" language. I decided to learn it since "fexprs" were apparently the solution to several of my problems. This wasn't easy at first - I had to read the Kernel Report several times, but I ended up finding it way more intuitive than using macros and quotes.
I've not written a Scheme macro since. I've written hundreds of Kernel operatives though.
I was also a typoholic previously, but am in remission now thanks to Kernel.
Think of macros as what you want when you want to perform computation at compile time rather than run time.
An example: building the equivalent of a switch statement, but that compares (via string equality) with a set of strings. The macro would translate this into code that would do something like a decision tree on string length or particular characters at particular positions.
Basically anything that's done with a preprocessor in another language can be done with macros in Lisp family languages.
The other motivation for me is to drastically reduce boilerplate code. I can’t believe people here are saying they never use macros, they are so good for this that avoiding them sounds to me like a skill issue! Overuse can damage readability, sure, but so can pretending macros are not an option.
Operatives do that for me, better than macros. Parent is correct that macros are compile time, which gives them a performance advantage over operatives - but IMO, they're not better ergonomically. I find operatives simpler, cleaner and more powerful.
Operatives are based on FEXPRS from older lisps - they're basically a function-like form, but where the operands are not implicitly reduce at the time of call.
(foo (+ 2 3) (* 3 4))
($bar (+ 2 3) (* 3 4))
`foo` is a function, when it is combined with the arguments, it receives the values 5 and 7.
`$bar` however, receives its operands verbatim. It receives (+ 2 3) as its first operand and (* 3 4) as its second - unevaluated.
The operative/FEXPR body decides how to evaluate the operands - if at all.
The difference between an operative/FEXPR and a macro is that macros are second-class objects which must appear in their own name - we cannot assign them to variables, pass them or return them from functions. Operatives and FEXPRs are first-class objects that can be treated like any other.
The difference between FEXPRs and Operatives is to do with scoping and environments. FEXPRs were around before Scheme - when Lisps were dynamically scoped. This meant we could have unpredictable behavior and so called "spooky action at distance". They were problematic and basically abandoned almost entirely in the 1980s.
Shutt introduced Operatives as a more hygienic version - based on statically scoped Scheme. Instead of the operative being able to mutate the dynamic environment arbitrarily, there are limitations. The first part of this is that environments are made into first-class objects - so we can assign them to a symbol and pass them around. The final part is that an operative receives a reference to the dynamic environment of its caller - which we bind to a symbol using the operative constructor, `$vau`.
($vau (operands) dynamic-env . body)
Compare to:
($lambda (arguments) . body)
So operatives are called in the same way a function is called - but the operands are not reduced, and the environment is passed implicitly.
The body can decide to evaluate the operands using the environment of the caller - essentially behaving as if the caller had evaluated them
(eval operands dynamic-env)
But it can chose other evaluation strategies for the operands - such as evaluating them in a custom created environment which we can make with (make-environment) or ($bindings->environment).
This also allows the operative to mutate the environment of its callee - but only the locals of that environment. The parent environments cannot be mutated through the reference `dynamic-env`.
Technically, `$lambda` is not primitive in Kernel - though it is the main constructor of applicatives (functions) - the primitive constructor is called `wrap` - and it takes another combiner (an operative or applicative) as its parameter. Wrapping a combiner simply forces the evaluation of its arguments when called - so functions are just wrappers around operatives - and the underlying operative of any function can be extracted with `unwrap`.
There's a lot more to them. They're conceptually quite simple in terms of implementation, but they have enormous potential use cases that are unexplored.
Read more on the Kernel page[1]. In particular, the Kernel report[2]. There's also a formal calculus describing them, called the vau calculus[3].
Hmm, this sounds like exactly the opposite of what I was talking about. It delays execution rather than promoting execution to compile time.
What I had expected you to talk about was some way of getting the compile time execution of macros by a sufficiently smart compiler that could do extensive partial evaluation at compile time, including crossing procedure boundaries. Of course that's antithetical to the Lisp philosophy of allowing dynamic redefinition of functions and such.
In Common Lisp macros can also be used to implement a kind of Aspect-Oriented Programming, using the macroexpand hook. This hook enables macroexpansion to be dynamically modified at compile time without changing the source code.
I understand the use case, but Scheme macros never felt intuitive to me. I think it may be the quotation more than anything that I dislike - though I also dislike that they're second class (which was the key thing which led me to Kernel).
I use C preprocessor macros extensively and don't have the typical dislike for them that many people have - though I clearly understand their limitations and the advantage Scheme macros have over them.
Since learning Kernel, the boundary of "compile time" and "runtime" is more blurry - I can write operatives which behave somewhat like a macro, and I do more "multi-stage" programming, where one operative optimizes its argument to produce something more efficient which is later evaluated - though there are still limitations due to the inability to fully compile Kernel.
As one example, I've used a kind of operative I call a "template", which evaluates its free symbols ahead of time but doesn't actually evaluate the body. When we later apply the some operands it replaces the bound symbols with the operands, looking up any symbols to produce an expression which we don't need to immediately evaluate either - but this expression has all symbols fully resolved. This is somewhere between a macro and regular operative.
Consider:
($define! z 10)
($define! @add-z
($template (x y)
(+ x y z)))
In this template `x` and `y` are bound variables and `+` and `z` are free. The template resolves the free symbols and returns an operative expecting 2 operands, effectively providing an operative with the body:
([#applicative: +] x y 10)
When we call the template with the two operands, it resolves any symbols in the arguments and returns the full expression with no symbols present, but it doesn't evaluate the expression yet.
When we decide to evaluate the expression, no symbol lookup is necessary - it can perform the operation rather quickly, despite the slow interpretation.
---
The $template form above isn't too difficult to implement. I've iterated several forms of this - some which only partially resolved the bound symbols, but lost them in a RAID failure. An earlier version which has some issues I still have because I put it online:
At present the best interpreter is klisp, and the fastest is bronze-age-lisp, which uses klisp - with parts of hand-written 32-bit x86 assembly.
I've been working on a faster interpreter for a number of years as a side project, optimized for x86_64 with some parts C and some parts assembly. It has diverged in some parts from the Kernel report, but still retains what I see are the key ingredients.
My modified Kernel has optional types, and we have operatives to `$typecheck` complex expressions ahead of evaluating them. I intend to go all in on the "multi-stage" aspect and have operatives to JIT-compile expressions in a manner similar to the above template.
I use klisp[1] and bronze-age-lisp[2] mostly for testing, as they're the closest to a feature complete implementation of the Kernel Report.
I've written a number of less complete interpreters over the years. I currently have a long-running side-project to provide a more complete, highly optimized implementation for x86_64.