Site Map - skip to main content - dyslexic font - mobile - text - print

Hacker Public Radio

Your ideas, projects, opinions - podcasted.

New episodes Monday through Friday.



Host Image
Host ID: 364

Eternal tinkerer of code, who occasionally writes things down at or contributes to hylang project at

episodes: 9

hpr2703 :: Fog of war in Yesod based game

Released on 2018-12-12 under a CC-BY-SA license.

Duality of the universe: there's true state of the universe used in simulation and there's state the the players perceive. These most likely will always be in conflict. One possible solution is to separate these completely. Perform simulation in one system and record what players see in other.

For every type of entity in the game, there's two sets of data: real and reported. Reports are tied to time and faction. Examples are given for planets. Thus, we have Planet, PlanetReport and CollatedPlanetReport. First is the real entity, second is report of that entity tied in time and faction. Third one is aggregated information a faction has of given entity. In database two first ones are:

Planet json
    name Text
    position Int
    starSystemId StarSystemId
    ownerId FactionId Maybe
    gravity Double
    SystemPosition starSystemId position
    deriving Show

PlanetReport json
    planetId PlanetId
    ownerId  FactionId Maybe
    starSystemId StarSystemId
    name Text Maybe
    position Int Maybe
    gravity Double Maybe
    factionId FactionId
    date Int
    deriving Show

Third one is defined as a datatype:

data CollatedPlanetReport = CollatedPlanetReport
    { cprPlanetId :: Key Planet
    , cprSystemId :: Key StarSystem
    , cprOwnerId  :: Maybe (Key Faction)
    , cprName     :: Maybe Text
    , cprPosition :: Maybe Int
    , cprGravity  :: Maybe Double
    , cprDate     :: Int
    } deriving Show

Data from database need to be transformed before working on it. Usually it's 1:1 mapping, but sometimes it makes sense to enrich it (turning IDs into names for example). For this we use ReportTransform type class:

-- | Class to transform a report stored in db to respective collated report
class ReportTransform a b where
    fromReport :: a -> b

instance ReportTransform PlanetReport CollatedPlanetReport where
    fromReport report =
	CollatedPlanetReport (planetReportPlanetId report)
			     (planetReportStarSystemId report)
			     (planetReportOwnerId report)
			     (planetReportName report)
			     (planetReportPosition report)
			     (planetReportGravity report)
			     (planetReportDate report)

To easily combine bunch of collated reports together, we define instances of semigroup and monoid for collated report data. Semigroup defines an associative binary operation (<>) and monoid defines a zero or empty item (mempty). My explanation about Monoid and Semigroup were a bit rambling, so maybe have a look at which explains it in detail.

instance Semigroup CollatedPlanetReport where
    (<>) a b = CollatedPlanetReport (cprPlanetId a)
				    (cprSystemId a)
				    (cprOwnerId a <|> cprOwnerId b)
				    (cprName a <|> cprName b)
				    (cprPosition a <|> cprPosition b)
				    (cprGravity a <|> cprGravity b)
				    (max (cprDate a) (cprDate b))

instance Monoid CollatedPlanetReport where
    mempty = CollatedPlanetReport (toSqlKey 0) (toSqlKey 0) Nothing Nothing Nothing Nothing 0

In some cases there might be a list of collated reports that are about different entities of same type (several reports for every planet in solar system). For those cases, we need a way to tell what reports belong together:

-- | Class to indicate if two reports are about same entity
class Grouped a where
    sameGroup :: a -> a -> Bool

instance Grouped PlanetReport where
    sameGroup a b =
	planetReportPlanetId a == planetReportPlanetId b

After this, processing a list of reports for same entity is short amount of very general code:

-- | Combine list of reports and form a single collated report
--   Resulting report will have facts from the possibly partially empty reports
--   If a fact is not present for a given field, Nothing is left there
collateReport :: (Monoid a, ReportTransform b a) => [b] -> a
collateReport reports = mconcat (map fromReport reports)

For reports of multiple entities is bit more complex, as they need to be sorted first, but the code is similarly general:

-- | Combine list of reports and form a list of collated reports
--   Each reported entity is given their own report
collateReports :: (Grouped b, Monoid a, ReportTransform b a) => [b] -> [a]
collateReports [] = []
collateReports s@(x:_) = collateReport itemsOfKind : collateReports restOfItems
    where split = span (sameGroup x) s
	  itemsOfKind = fst split
	  restOfItems = snd split

Final step is to either render reports as HTML or send them as JSON back to client. For JSON case we need one more type class instance (ToJSON) that can be automatically generated. After that handler function can be defined. After authenticating the user and checking that they are member of a faction, reports of specific planet (defined by its primary key) are retrieved from database, collated, turned into JSON and sent back to client:

$(deriveJSON defaultOptions {fieldLabelModifier = drop 3} ''CollatedPlanetReport)

getApiPlanetR :: Key Planet -> Handler Value
getApiPlanetR planetId = do
    (_, _, fId) <- apiRequireFaction
    loadedPlanetReports <- runDB $ selectList [ PlanetReportPlanetId ==. planetId
					      , PlanetReportFactionId ==. fId ] [ Asc PlanetReportDate ]
    let planetReport = collateReport $ map entityVal loadedPlanetReports :: CollatedPlanetReport
    return $ toJSON planetReport

For those interested seeing some code, source is available at ( is situation before lots of Elm related changes that I mentioned in passing in the previous episode)

hpr2693 :: Getting started with web based game in Haskell and Elm

Released on 2018-11-28 under a CC-BY-SA license.

Haskell Stack:

Stack is a build tool for Haskell with focus on reproducible build plans, multi-package projects, and a consistent, easy-to-learn interface. With stack, one can create new project: stack new my-project yesod-sqlite (more in the quick start guide:

models is used to define shape of the data and Yesod uses it to generate datatypes and database for you. For example, to define a Star that has name, spectral type, luminosity class and link to StarSystem, one can write:

Star json
    name Text
    starSystemId StarSystemId
    spectralType SpectralType
    luminosityClass LuminosityClass

Custom types, like LuminosityClass, need mapping between datatype and database. In simple cases like this, Yesod can do that:

data LuminosityClass = Iap | Ia | Iab | Ib | II | III | IV | V | VI | VII
    deriving (Show, Read, Eq)
derivePersistField "LuminosityClass"

The "derivePersistField" part is template haskell call that will generate mapping needed.

For those interested seeing some code, source is available at ( is situation before lots of Elm related changes that I mentioned in passing in the episode)

hpr2633 :: Elm - First Impressions

Released on 2018-09-05 under a CC-BY-SA license.

hpr2618 :: Yesod - First Impressions

Released on 2018-08-15 under a CC-BY-SA license.

First place to start is probably Yesod’s web site at:

Often recommended environment for developing Haskell programs is Stack:

My road to Haskell started with Learn You a Haskell for Great Good: and going through lecture notes of CIS 194:

hpr2608 :: BattleTech

Released on 2018-08-01 under a CC-BY-SA license.

Following links might help you to get more familiar with the game.

hpr2598 :: Calculating planetary orbits in Haskell

Released on 2018-07-18 under a CC-BY-SA license.

Function signatures (it might or might not be helpful to have these at hand while listening):

  • Helpers:
    radToDeg :: Floating a => a -> a
    degToRad :: Floating a => a -> a
    clamp :: Float -> Float
  • Time:

    day :: Int -> Int -> Int -> Float -> Day Float
  • Orbital parameters:
    longitudeOfAscendingNode :: Orbit body center => body -> center -> Day d -> LongAscNode body center
    inclinationToEcliptic :: Orbit body center => body -> center -> Day d -> InclToEcl body center
    argumentOfPeriapsis :: Orbit body center => body -> center -> Day d -> ArgPeri body center
    semiMajorAxis :: Orbit body center => body -> center -> Day d -> SemiMajor body center
    eccentricity :: Orbit body center => body -> center -> Day d -> Ecc body center
    meanAnomaly :: Orbit body center => body -> center -> Day d -> MeanAno body center
  • Calculating location on orbital plane:
    eccAnomaly :: MeanAno a b -> Ecc a b -> EccAnomaly a b
    trueAnomaly :: EccAnomaly a b -> Ecc a b -> TrueAnomaly a b
    dist :: EccAnomaly a b -> Ecc a b -> SemiMajor a b -> Distance a b
  • Translating between coordinate systems:
    toEclCoord :: TrueAnomaly a b -> Distance a b -> LongAscNode a b -> ArgPeri a b -> InclToEcl a b -> EclCoord a b
    toEqCoordinates :: EclCoord body Earth -> Day Float -> EqCoord body

Some helpful links:

hpr2593 :: Intro to De Bellis Antiquitatis

Released on 2018-07-11 under a CC-BY-SA license.

In this episode tuturto paints rambles about De Bellis Antiquitatis while painting more toy soldiers, so expect long pauses and missing thoughts as he tries to do two things at the same time.

De Bellis Antiquitatis (or DBA for short):

While the original site seems to be gone, WADBAG unofficial guide to DBA can be found at:

hpr2588 :: Miniature painting

Released on 2018-07-04 under a CC-BY-SA license.

tuturto rambles about miniature painting while painting some ancient British units (horses for chariots to be specific) for De Bellis Antiquitatis.

hpr2524 :: General problem solver

Released on 2018-04-05 under a CC-BY-SA license.

Become a Correspondent