6 ms·
I built a Game Boy emulator in F#
- DeathArrow 5mo agoNow I would like to see the other way around, F# running on Game Boy.
- cermicelli 5mo agoFinally 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.
- hectdev 5mo agoIt's always going to exist. People still build things with hand tools in the year 2026. Let's call it Artisanal Coding.
- MarsIronPI 5mo agoBut it already has a name; this noble art is called "programming", or better yet: "hacking".
- hectdev 5mo agoLanguages evolve
- raddan 5mo agoEven 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.
- hectdev 5mo agoI'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.
- default-kramer 5mo ago> I probably spent over 20 hours debugging, scanning the emu-dev Discord, creating tests, and even throwing the issue at earlier AI models. Nothing worked. But then after a few weeks away from the emulator I tried Claude Opus, and it found the issue in just a few minutes. Even if you want to write all the code yourself (which is a fine decision), the only reason in 2026 to bang your head against a problem like this for 20 hours is if you really enjoy doing so. (I'm surprised that "earlier AI models" didn't work for the author. For me, free-tier Gemini gets stuff like this correct all the time.)
- rafaelmn 5mo ago> 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#? One reason I realized recently - when you work it through with an LLM you get full process history linearly serialized, the back and forth, thinking traces, web lookups. When I need to get back into the task it's much easier to get back in to "the flow". I think it'll be common practice to start commiting agent logs with the code pretty soon.
- wing-_-nuts 5mo agoCurious, do you work in F#? I looked for jobs using functional langs in my last job change and I found positions extremely rare.
- raddan 5mo agoI am a computer scientist, not a programmer. So the short answer is that the language I choose mostly does not matter.
- deadbabe 5mo agotrad coding
- danparsonson 5mo agoI feel like trad coding would be more along the lines of 'I work 10 hours a day coding for minimum wage because that's a worker's place in life, and I love it!'
- egil 5mo ago... while live streaming actually earns the money :-P
- mastermage 5mo agoThis app was coded by hands of 32 virgins on a moon lit full moon night.
- LeCompteSftware 5mo agoAs a longtime F# developer and longtime recipient of STEM academic bullying[1] I refuse to use LLMs in large part because ChatGPT-3.5 was so ridiculously bad and obvious about copy-pasting from F# GitHub repos. I never felt the AGI, I just saw a plagiarism machine whose decorations had fallen off. Eventually I am sure someone at Microsoft noticed and rang the RLHF alarm, so GPT improved substantially. It seems pretty usable for F#. I am sure some unprincipled F#er is crushing it with agents these days. But I didn't think "oh boy they solved the plagiarism problem, let's go generate some slop!" I thought "oh great, now it's no longer going to be blatantly obvious when ChatGPT plagiarizes." I really don't want to roll a d100, or even a d1000, to completely compromise a core value of mine in in exchange for a productivity benefit. I'll just be slow and jobless, thanks. This is serious: I am getting into solar installations and junk hauling. [1] The "students don't want to think" problem is much older than LLMs. In 2007 I took a senior-level PDEs class, and almost everyone copied my homework because I was actually motivated to study PDEs, and too psychologically weak to resist those mean lazy math majors. Then it happened again in math grad school! Actually unbelievable. Why are you even in the program?
- fleroviumna 5mo ago[dead]
- throwaway27448 5mo agoEh you should have give up hope in humanity sometime around when the soviet union fell Emulators are cool as hell though, and GBA ones are a nice one to tackle yourself
- hmokiguess 5mo agoF# is super fun, awesome work!
- z500 5mo agoThat'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
- tombert 5mo agoI'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.
- ragnese 5mo agoYes! 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...
- tombert 5mo agoYeah, and even if you need concurrency/parallelism within the function, it can be forgivable to use ConcurrentDictionary or ConcurrentBag or one of the many, many other thread safe mutable data structures built directly into .NET. I will personally almost always prefer the pretty functional versions of things, and that's almost always what I start with. I like immutable data structures, and they are usually more than fast enough. Occasionally, though, you hit a bottleneck of some kind (usually in some form of loop), and you have to avoid all the beautiful functional stuff and go back to sad imperative stuff. When I do that, I usually try and keep it scoped to one function. Even within one function, I do find the persistent structures easier to reason about, but as you stated it's a small enough surface area to not be too irritating. There are exceptions to this, of course. Sometimes for caching/memoizing I will make a global ConcurrentDictionary, and I'll use the interlocked thing to do global counters sometimes.
- __loam 5mo agoI'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!
- WoodenChair 5mo agoYes, CHIP-8 is kind of the standard "I want to get into emulators" first project. In my latest book Computer Science from Scratch we go CHIP-8 -> NES in chapters 5 to 6. GBA is quite a step up from CHIP-8. I would suggest doing NES or GameBoy next, but of course with today's LLM help GBA is very reasonable if you are going that route.
- __loam 5mo agoGot it
- MattCruikshank 5mo agoSorry 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?
- Galanwe 5mo agoI guess buying the second hand devices wouldnt be that expensive.
- jrumbut 5mo agoI 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.
- yoyohello13 5mo agoI 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.
- skrebbel 5mo agoDid you look at the code? F# has mutable variables/arrays and this uses that for eg memory.
- yoyohello13 5mo agoYeah 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.
- debugnik 5mo agoCool 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.
- ibejoeb 5mo agoThe Register source has this comment: // Registers can't be a record type because the values need to be truncated to 8 bits when writing, so setters are needed // This is for the web renderer as Fable transpiles uint8 to Number (more than 8 bits) in JS and doesn't apply any truncation // Known non-standard behaviour in Fable (https://fable.io/docs/javascript/compatibility.html#numeric-types) So, I think, it's just conservatively cleaning the data due to Fable's widening via js Number on the web target.
- debugnik 5mo agoOof, thanks for pointing that out, I hadn't noticed and I've only ever used F# on .NET. That's terrible on Fable's part, the least they could do is truncate. I wasn't aware Fable's translation is so naive.
- ibejoeb 5mo agoI haven't used Fable much, but apparently it maps .NET arrays to js TypedArray. Presumably you could keep the registers in 8-element array and fable will properly produce a Uint8Array. I'd like to benchmark that.
- rienbdj 5mo agoIt’s really hard to please everyone all of the time on this front. This kind of thing is why Roc compiles to WASM but not JS.
- 5mo ago
- Ginop 5mo ago[flagged]
- CSMastermind 5mo agoInsanely 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.
- thrownawaysz 5mo agomildy 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.
- hugeBirb 5mo agoI'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/ https://mtmc.cs.montana.edu/
- thrownawaysz 5mo agoFound it MetalNES, transistor level NES emulation https://github.com/iaddis/metalnes https://github.com/iaddis/metalnes While searching I also found a new one, VisualNES https://kaiokendev.github.io/nes/about https://kaiokendev.github.io/nes/about There is also one for GB https://github.com/aappleby/MetroBoy https://github.com/aappleby/MetroBoy
- andruc 5mo agoMy 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.603928/ 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!
- thrownawaysz 5mo agoFound it MetalNES, transistor level NES emulation https://github.com/iaddis/metalnes https://github.com/iaddis/metalnes While searching I also found a new one, VisualNES https://kaiokendev.github.io/nes/about https://kaiokendev.github.io/nes/about There is also one for GB https://github.com/aappleby/MetroBoy https://github.com/aappleby/MetroBoy
- danparsonson 5mo ago
- abricq 5mo ago[dead]
- mkw5053 5mo agoVery cool. Makes me want to build something with F*
- dmitrygr 5mo ago> No code is free from the influence of AI these days, even learning projects Speak for yourself
- deadbabe 5mo agoBuilding emulators is cool but everyone does it, and the end result is more or less the same. Is there some things that could be done during the process to perhaps make an emulator a little more unique instead of a perfect replica?
- redrobein 5mo agoWhat do you mean? It has to mirror the behavior of the hardware pretty closely otherwise... well... the games don't run.
- BadBadJellyBean 5mo agoWhen I read F# I always have to think of the song with the same name by Tim Minchin and it starts playing in my head.
- redrobein 5mo agoF# is a good language, but I feel like it's forever stuck in C#'s shadow. A lot of the library code is C# and .NET handmedowns. Not interfaces or libraries crafted with F# in mind, often having no explicit documentation for use with F# either.
- rienbdj 5mo agoTranslating library usage from C# to F# is pretty mechanical so not sure if specific docs are needed. The larger issue is the C# community loves OOP so you often have to wrap these libraries into something more “FP” if that’s how you want to work. Overall it’s far better than having nothing (looking at Haskell, OCaml as much as I enjoy them!)
- quadium4004 5mo ago[dead]
- omcnoe 5mo agoYeah there is some degree of awkwardness created by the interaction, but I think it’s less about needing specific libraries to map well and more about getting a good understanding of what the interop rules are, and what the shape of the underlying generated output actually looks like. C# interoperability loosens guarantees (particularly immutability) that F# code normally relies on. There are surprising limits that come up in generics because of how they map to C#.
- sirjaz 5mo agoThis is awesome. Love seeing projects like this.
- DavyJone 5mo agoF# is my coding love language that I never get to use (outside of personal projects) :(
- surajguptayc 5mo ago[dead]
- tehnub 5mo agoInteresting, enjoyable post. Like the bit about data modeling. I've been dabbling in some OCaml and that kind of modeling is the best part. Also interesting to learn of CAMLBOY. Feedback to the author: Skip the AI edit step. I'd have preferred grammar errors or inelegance to what we have here, which is a bit stale.
- hurril 5mo agoAh F#, my greatest love. How I wish the C# guys and girls would see this instead of further bastardizing (don't hate me) C# into being everything but poorly. Don't you see that if you would use F# instead, creating projects with C# and F#, that you would get what is being added to C# but actually working and ergonomically? Interop is great!
- Sirental 5mo agoIt's a shame though that if you come from the world of OCaml, F# feels like its stuck in C#'s shadow a bit. You can get pretty far with F# by using it as a functional language, but eventually you'll want to interop with the rest of the .NET ecosystem and suddenly you're writing in a weird OOP/Functional hybrid style.
- hurril 5mo agoOCaml is wonderful too but having written F# for different companies for years, my code is pretty much never that hybrid stuff. Sure, .NET:s weird asynch apis, sometimes the code comes out a little bit weird but that is the exception to the rule imho.
- arwhatever 5mo agoF# is so elegant and terse for writing functional-style wrappers around OO code packages! Unfortunately, you find yourself needing to write functional-style wrappers around OO code packages.
- mannycalavera42 5mo agodsyme nodding proudly meme here
- makeryi411 5mo ago[flagged]
- hallegbg 5mo agoReally Cool!
- gill-bates 5mo agoGreat project! Really cool to see something like this. On the other hand, and this says nothing about you or your work, I realize I can put to bed my desire to learn and use F# after seeing what it looks like in a real project. The purely functional stuff is beautiful but once you drop in to more imperative/mutable code I find it really ugly to look at. I suppose unfortunately I suspect that in most real projects you will have to. Not sure if it just means I should choose a different functional language to jump in to, or if I should just work on applying functional concepts to the language(s) I already work with (fairly easy since C# is my primary language and has ever-increasing support for the functional paradigm).
- gorlokthedev 5mo agothis is my first time learning that F# exists, what benefits does it offers if we had to compare with C#? especially in your emulator.