Learning About Space Exploration by Playing with F# and Functional Programming: Setup

Our Sim Engine Sets Up And Runs — all in about 100 LOC

We live in this gravity well called Earth, a big ball of dirt and mud that weighs so much we stick to it. If we’re ever going to get out, explore, and colonize the rest of the universe, we gotta get off this rock. And however we do it has to be cheap and easy.

So what are you doing to help? What do you say we build a rocket and get the heck out of here? We can’t build a real rocket, of course. We’re not Elon Musk. But we can model the problem of getting off the planet using F# and functional programming. We can code a little, play with the model, code some more. We can try different things. See what works. It’s programming, but it’s not commercial programming. There’s no economic value. We’re not using our skills to learn about somebody’s problem and help them. We already know the problem. We’re using the computer and the formality of a programming language to understand the implications of a problem.

First we need to make a new console application in F# that can use WinForms. You may have problems creating a new WinForms console project in F#. I did. This was because once you install DotNet Core, they don’t let you make plain-jane Win32 console projects like that any more, although you can open previous ones. If you have problems, worst-case just clone what I have from GitHub and modify it.

I was not especially happy with this, but I understand it (and it makes strategic sense).

The reason is because the entire .NET ecosystem is moving to more of a cross-platform environment, which is great. But they haven’t written the desktop UI layer, which sucks. This means that the UI part of this code will probably expire in a few years. But the guts of it will be fine — and using F# and FP means that the UI part is really a very small part of the overall application.

What does this app need to do (behavior)? At a minimum, it needs to move step-by-step, simulating gravity and propulsion up until orbital insertion or interplanetary flight. Since getting to some place like Pluto can involve vast scales and long periods of time, it’s critical that the user be able to change the scale of the display and the rate of time passage.

Ok. How do we start (what sort of rules do we have about the way we work Meta/Supplemental)? Most all the time, the first thing todo in a commercial project is return something to the user. Anything. Text is fine. Then we add the display. Later we make it look good. But not this time. (UX continues along a parallel path not under discussion here). Here? We start printing out a big stream of doubles? Nobody can make sense of any of that. There’s no feedback loop. The only thing we have is numbers. So for the development process to work, even if it’s just one person coding, we’re going to have to build the simplest scaffold possible (hence using a console app) that the user can observe to see if the code is working correctly or not.

I also have a code budget of around 150LOC for setting up the scaffolding. I always set up a code budget for new architectural units I stand up. It’s important to understand that this isn’t an effort to play “code golf” — I’m not trying to brag about how little code I use. Instead, my budget keeps me aware at all times whether I’ve gone off the deep end putting too much complexity in one spot.

Based on those behaviors and supplementals, what kind of structure do we need? We need a mathematical model of the earth. We need to model something that goes up. Perhaps a rocket. Perhaps a cannonball. You can shoot a cannonball out of orbit, right? We need a start, pause, and reset button. Finally we need a display to show the sim status along with the earth and thing we’re building.

Cool beans. We’ve behavior, supplementals, and the associated derived structure. That’s what we need to start. How can we make this happen while keeping things as brutally-simple as possible? If you’ve been reading along, you know that I’m a big fan of Total Programming, the Unix Philosophy, and the Onion Architecture. Also TDD and a bunch of other stuff. But we don’t need any of that. (It may show up later, though. Good architectural ideas always do.)

Why don’t we need any more architecture? Because this app doesn’t have to do anything. Hell, it doesn’t even have to run most of the time. The purpose of this is to take a couple of hours and interact with the computer as it we were building some kind of launch system, see where that goes. We should never come back to this after today. IF we do, then we’ll talk that stuff. Nobody said anything about 3D, so we only need 2D data. No matrix math for us just yet. In fact, once our scaffolding is in place, we’ll just start with the earth and a rocket with a stopwatch that runs. Then we can start talking about more behavior and supplementals.

This was fun! I wrote some of the types up in the morning and throughout the day added a little bit here and there. I’m not doing “code like a stupid person” today, so there no need to list all the little snags. Having said that? There weren’t that many snags! I love me some F#. It was all just straight F# — that’s what we wanted. Let’s take at the initial code:

module Utils
open System
open System.Windows.Forms
open System.Drawing
open System.Timers
// Measure annotations
[<Measure>] type kg
[<Measure>] type m
[<Measure>] type km
[<Measure>] type sec
[<Measure>] type mps = m/sec
[<Measure>] type mpss = m/sec^2
// Types
type Position = { XPos:float<km>; YPos:float<km>}
type Velocity = {XSpeed:float<m/sec>;YSpeed:float<m/sec>}
type MassivePlanetaryObjectType = { Mass:float<kg>;Radius:float<km>;Position:Position}
type SpaceShipType = {Height:float<m>; Width:float<m>; Mass:float<kg>; Velocity:Velocity}
type SimulationStateType = {
    SimulationStart:DateTime    
    TickCount:int64
    WindowScale:double
    TimeScale:double
    LastTickTime:DateTime
    TotalRunTimeSoFar:TimeSpan
    SpaceShip:SpaceShipType
    }
// Program spec. Taking out the deployment-specific scaffolding, 
// the entire thing is three platform-independent functions
// Remember: the specification to your program is in your types and tests. Nowhere else
type SimulationEngine = SimulationStateType->SimulationStateType
type GetSimulationState = unit->SimulationStateType
type PutSimulationState = SimulationStateType->unit

I’m using F#’s measurements feature. Haven’t really used it that much, but since there’s physics here, why not? Also note at the bottom I’m taking my types and composing them into function signatures. This is a neat little trick. I’m effectively locking-down whoever implements that code. Types become specs. It’s a beautiful thing.

// Initial Values
let topButtonBarHeight=25
let statusBarHeight=15
let earth = {Mass=5.972E24<kg>; Radius=43.0<km>; Position={YPos=(-43.0<km>); XPos=(-43.0<km>)}}
type DisplayPositionType = |CenterBottom
let spaceship= {
        Height=100.0<m>
        Width=10.0<m>
        Mass=100.0<kg>
        Velocity={XSpeed=0.0<mps>; YSpeed=0.0<mps>} }
let desiredRocketshipWidthInModelTerms = 0.004<km>
let computedDisplayScale = 2.0*earth.Radius/desiredRocketshipWidthInModelTerms
let desiredRocketshipWidthInPixels = 7
let desiredRocketshipHeightInPixels = (int)((2.0/spaceship.Width) * spaceship.Height)
let initialModelState = 
                        {
                        SimulationStart=DateTime.Now
                        TickCount=0L
                        WindowScale=(double)computedDisplayScale
                        TimeScale=100.0
                        LastTickTime=DateTime.Now
                        TotalRunTimeSoFar=TimeSpan.Zero
                        SpaceShip=spaceship
                        }

Our initial values are the kinda thing we might eventually pass in as parms or put in a file somewhere. For now we’ll just stick in one place at the top. Sure is cool putting things like kg and km into our records, isn’t it?

// UI setup. Outer onion for our toy
let simulationForm =
    let temp=new Form()
    temp.Show()
    temp.WindowState<-FormWindowState.Maximized
    temp.Text<-"Rocket Explorer"
    temp.Tag<-initialModelState
    temp
let addTopButton left caption buttonWidth =
    let temp = new Button()
    temp.SetBounds(left, 5, buttonWidth,topButtonBarHeight)
    temp.Text<-caption
    temp.Name<-caption
    temp
let addTopButtons (btns:string list) =
    let buttonWidth = simulationForm.Width/(btns.Length)
    btns |> List.iteri(fun i x->
        let left = buttonWidth * i
        let newBtn=addTopButton  left x buttonWidth
        simulationForm.Controls.Add(newBtn))

I want a few buttons at the top to start, stop, etc. I don’t care anything about them except that they have a name and caption. Got a list of stuff to add across the top? Sounds kind of generic. This is a good spot for some classic F#. (Note that it didn’t start off that way. I factored it as I played around with the code.)

type StringArgReturningVoidFunction = delegate of string->unit 
let rec setStatusLine(text:string):unit =
    let sl=simulationForm.Controls.Item("statusLine")
    if sl.InvokeRequired then
        let d = new StringArgReturningVoidFunction(setStatusLine)
        if simulationForm.Disposing=false && simulationForm.IsDisposed=false
            then 
            try
                simulationForm.Invoke(d, text) |> ignore
            with |_ ->()
            else ()
    else
        sl.Text<-(text)

Aside from getting a old-school console app running, this was really the only snag I hit. You can’t set text in text box just by using the control you found because: threading.

I get a feeling that threading, concurrency, and parallelism are going be a bigger and bigger issue for coders — along with DevOps, and the two are intimately related. For now, however, I just hacked out some stuff from a web page that uses delegates. Dang. I thought I got away from those guys! Well, at least I didn’t have to hack up a Win32 message. Been there, done that. (I would say it wasn’t fun, but back then it was fun)

let setupUI (simEngine:SimulationEngine) =
    let statusLine = new Label()
    statusLine.Text<-"Simulation Time 00:00"
    statusLine.Name<-"statusLine"
    let simulationTimer = new Timers.Timer(100.0)
    simulationTimer.Elapsed.Add(fun args->getSimState() |> simEngine |> setSimState)
    statusLine.SetBounds(0,(topButtonBarHeight + 14),simulationForm.Width,statusBarHeight)
    simulationForm.Controls.Add(statusLine)
    ["Start""Stop""Reset"] |> addTopButtons
    // finally, handlers
    simulationForm.Controls.Item("Start").Click.Add(fun e->
        {getSimState() with SimulationStart=DateTime.Now} |> setSimState
        simulationTimer.Enabled<-true)
    simulationForm.Controls.Item("Stop").Click.Add(fun e->simulationTimer.Enabled<-false)
    simulationForm.Controls.Item("Reset").Click.Add(fun e->simulationTimer.Enabled<-false)
    simulationForm.MouseWheel.Add(fun e->
        let scaleMove:double= 1.0 + double(e.Delta)/1000.0
        if (Control.ModifierKeys.HasFlag Keys.Control) then
            {getSimState() with TimeScale=getSimState().TimeScale*scaleMove} |> setSimState
            else ()
        if (Control.ModifierKeys.HasFlag Keys.Shift) then
            {getSimState() with WindowScale=getSimState().WindowScale*scaleMove} |> setSimState
            else ()
        )
    simulationForm.Paint.Add(fun e->
        // we want the spaceship centered. For now, zoom scale is 1
        let modelState=getSimState()
        let spaceshipBrush = System.Drawing.Brushes.DarkMagenta
        let spaceshipLeft=simulationForm.Width/2 - (int)modelState.SpaceShip.Width/2
        let spaceshipTop=simulationForm.Height/2 - (int)modelState.SpaceShip.Height
        let spaceshipDisplayRectangle = 
            Rectangle(spaceshipLeft,spaceshipTop, desiredRocketshipWidthInPixels, desiredRocketshipHeightInPixels )
        let earthBrush = Brushes.DarkBlue
        let newEarthScale=(float)earth.Radius * computedDisplayScale/2.0
        let earthLeft = (int)((float)spaceshipLeft-newEarthScale*newEarthScale)
        let earthTop =  (int)((float)spaceshipTop-newEarthScale*newEarthScale)
        let earthDisplayRectangle = 
            Rectangle(earthLeft,earthTop,(int)(newEarthScale*2.0), (int)(newEarthScale*2.0))
        // Math needs to go here to determine intersection of earth and display. Dang you, Math!
        e.Graphics.FillRectangle(spaceshipBrush, spaceshipDisplayRectangle)
        if     (earthDisplayRectangle.Left<0  || earthDisplayRectangle.Right>simulationForm.Width)
            && (earthDisplayRectangle.Top<0   ||  earthDisplayRectangle.Top>simulationForm.Height)
            && (earthDisplayRectangle.Right<0 ||earthDisplayRectangle.Right>simulationForm.Width)
            && (earthDisplayRectangle.Bottom<0||earthDisplayRectangle.Bottom>simulationForm.Height)
            then
                let bottomScreenRectangle =
                    Rectangle(0,spaceshipDisplayRectangle.Bottom,simulationForm.Width,simulationForm.Height/2)
                e.Graphics.FillRectangle(earthBrush, bottomScreenRectangle)
            else 
                e.Graphics.FillEllipse(earthBrush, earthDisplayRectangle)
        )

This the conclusion of the UI setup. Pretty standard stuff. I call that button code I wrote earlier. Set up a timer for the sim to run. At the bottom is our big paint method. Until the sim starts running, there’s really no testing much of anything. Right now it just looks good enough, and that’s all we have.

I stayed inside my code budget which the most important green light. Less code is better code. Less code is more easily understood code. Done correctly, less code is simpler code. Less code is more maintainable code. Less code is good. Less code is life, love and happiness. Write less code.

Looking through code as I blog, I see a bunch of little change I should make, which is the way it always is. This code probably pretty buggy! Why? Because, as we noted, there’s no feedback loop in place: no tests, no interaction with the user. All I have is a couple of prototyped function signatures. But I’m at the point where I can start using a feedback loop, so mission accomplished. I did what wanted to do. If I get back to this to this coding toy, it’ll be time to start launching rockets!

Interested in how I decided what to do first? Or why I made the architectural decisions I did? I wrote a book on organizing product information at all levels of your organization. Backlogs and programming are just another level, and you don’t need a tool, a form, a document or anything to do them right. You just have to know what you’re doing and why. Buy the book and find out how!

Oh yeah, here’s the GitHub ModelRocket Repo, if you’d like the code.


Follow the author on Twitter

August 28, 2018

Leave a Reply

Your email address will not be published. Required fields are marked *