I built a Game Boy emulator in F#
90 points
2 hours ago
| 10 comments
| nickkossolapov.github.io
| HN
debugnik
1 hour ago
[-]
Cool to see F# here! Emulators are a great way to learn a language. On first sight you chose well between more or less idiomatic F# for each job.

Some low hanging fruit to reduce allocations: the discriminated unions in Instructions.fs could be [<Struct>], reusing field names to reuse internal fields.

Also, minor nitpick but I'm confused about some of the registers. They are already of type byte, the setters with `a &&& 0xFFuy` don't add anything over `member val A = 0uy with get, set`. I'm guessing this changed over time.

reply
cermicelli
1 hour ago
[-]
Finally someone putting in actual human effort to learn something, and not a LLM helped me build X in Y minutes.

There is some hope for humanity after all I suppose.

reply
hectdev
32 minutes ago
[-]
It's always going to exist. People still build things with hand tools in the year 2026. Let's call it Artisanal Coding.
reply
raddan
17 minutes ago
[-]
Even if you use AI, there's a certain point where it's not clear that an AI would make you faster. F# is my favorite language, and I've been programming in it so long (since 2012) that I feel like I think in F#. Asking an AI for something can be faster if I can state my requirements informally; but if I need to specify many things precisely to an AI... why not just write the code in F#? Part of the beauty of good functional designs is that they are declarative, not imperative, so in some sense you're really just stating what you want, at finer and finer granularities, until what you want is trivial.

Even when I want code written in a different language (e.g., C/C++), I often still start by making a prototype in F#. This helps me nail down the logic without having to worry about things like allocation or layouts. Perhaps I could ask an AI to do this second step for me, and then use the F# implementation as an oracle. Anyway.

reply
hectdev
12 minutes ago
[-]
I'm of the mindset that you can use AI however you want to get the speed improvements you're looking for. Personally, I use Agile methods to incrementally implement manually testable features, refine and debug, then commit. Then I use another chat/agent to keep tabs of the overall progress (giving it a summary from the agent that did the work), and then move to the next task by asking the coordinator to draft a prompt for the next bit of work I describe.
reply
z500
1 hour ago
[-]
That's so cool! I love F#, but I wrote a little Smalltalk interpreter in it and I can confirm it isn't exactly a speed demon for that kind of thing if you use it as intended lol
reply
tombert
1 hour ago
[-]
I've found that with F#, I get better performance if I do dumb imperative stuff, but keep the side effects within a function. At that point, the functions can basically be "pure" but you can get decent speed.

For example, I usually like using the `Map` data structure, and that's a pretty neat immutable structure and is usually fine for most stuff, but when performance becomes critical, it's easy enough to break into a boring imperative loop with a regular hash map. If I keep everything contained into one function, I usually can avoid feeling super dirty about it.

reply
ragnese
28 minutes ago
[-]
Yes! That's exactly how you should do it while working with a language that doesn't have a compiler that will aggressively analyze, and rewrite and optimize your code for you. (So, most languages with "heavy runtimes" that support a bunch of dynamic stuff and JITs)

There are basically two points to programming with immutable-first data. One, eliminate certain classes of data race concurrency bugs. Two, less mutable state in a given context makes it easier to reason about.

So, if you're inside a function scope and you aren't launching any concurrent operations from inside that function, you don't have to worry about benefit #1. If you're inside a function (and you're not reaching out for global mutable state), then the context you need to keep in your working memory is likely fairly small, so a few local mutable variables doesn't significantly harm "understandability" of the implementation (in most cases). So, you really don't have to worry about #2, either. Make your functions black boxes with solid "APIs" (type signatures), and let the inside do whatever it needs to make it work the best.

Just because premature optimization is the root of all evil, it doesn't mean we need to jump right to premature pessimization...

reply
jackmott42
1 hour ago
[-]
With some care about what features to use and when, F# can be very fast. Which is nice, use functional paradigm when you want, or low level imperative code in hot loops if you need. But yeah if you use linked lists and sequences and immutable data types everywhere it sure isn't Rust.
reply
yoyohello13
1 hour ago
[-]
I always find emulators written in functional languages impressive. It tends to be much easier to map hardware to an imperative language. I enjoy seeing the functional abstractions people come up with.
reply
skrebbel
1 hour ago
[-]
Did you look at the code? F# has mutable variables/arrays and this uses that for eg memory.
reply
yoyohello13
36 minutes ago
[-]
Yeah I did see that part. Although he mentioned his Chip8 emulator which was fully immutable. Still interesting so see when people use the mutability escape hatches.
reply
CSMastermind
47 minutes ago
[-]
Insanely cool. I've had it in the back of my mind to write a Rust compiler for the game boy for a long time and everytime I see something like this I think about brushing off that project.
reply
thrownawaysz
37 minutes ago
[-]
mildy related but wasn't there an emulator (maybe not GB but NES or SNES?) which had a visual panel showing each CPU cycle step by step? afaik it was very slow but the 1000% accuracy was the goal not playability.
reply
andruc
8 minutes ago
[-]
My guess is you're thinking of no$gmb, a very early Game Boy emulator:

https://gbatemp.net/threads/no-gmb-2-5-dos-full-version.6039...

I've got fond memories of using this to get a preview of Pokemon Gold before it was released in NA!

reply
hugeBirb
22 minutes ago
[-]
I'm not sure if this is the one you are talking about but I remember seeing this a little while ago. https://mtmc.cs.montana.edu/
reply
hmokiguess
1 hour ago
[-]
F# is super fun, awesome work!
reply
MattCruikshank
1 hour ago
[-]
Sorry for the tangent - does anyone have some really zoomed in views of GB, GBColor, GBA screens in operation? I'd love for retro shaders to be able to more faithfully reproduce.

I mean, ideally, we'd run different color test patterns through, in different lighting conditions, to build a really detailed model, right?

reply
Galanwe
1 hour ago
[-]
I guess buying the second hand devices wouldnt be that expensive.
reply
jrumbut
26 minutes ago
[-]
I wonder at what point even a still functioning device no longer looks the same?

I've been going through a lot of very old stuff recently and a lot of it is well preserved in a way but given enough years everything changes.

I don't think any original Gameboys have been made in twenty years or more.

reply
__loam
1 hour ago
[-]
I'm actually starting a new project to create a gba emulator in zig, and also starting with chip8. I'm going to skip nand to tetris because I played Turing complete. Cool to see I'm on the right track!
reply
Ginop
1 hour ago
[-]
I misread Fem-Boy and I was not understanding the context anymore lol
reply