Make for Haskell Values pt 2 of K

A few days ago I wrote an article about Make for Haskell values. Somehow people read “Make” and ignore the “for Haskell values” part. So I’ll clarify: Control.Make is intended to be a declarative dependency injection, build, and configurations model for Haskell values.

Control.Make is intended for staged programming, where the Haskell value in question is perhaps a program described by Haskell EDSL, such as arrows. I am developing this for Sirea – an RDP implementation – where Make would be responsible for:

  • implicitly create a thread for each primary partition type
  • automatically establish communication between threads
  • create special threads for special tasks as needed – e.g. audio, rendering, monitoring the filesystem
  • automatically hook reactions to various resources, such as framerate of video or state of the file

A point is that this connectivity is non-local in RDP. A resource might be observed and influenced concurrently from multiple independent locations in one program. I needed a way to initialize and hook those resources from multiple locations in the program that did not push a lot of boiler-plate to the client. Further, I wanted a way to configure resources externally.

The main features that set Control.Make apart from other configuration techniques for Haskell, such as Oleg’s Implicit Configurations, are support for defaults and precise, demand driven construction. Defaults allow clients to opt-in to configuration gradually, controlling their descent into dependency hell. Defaults also allow libraries to extend their set of dependencies without breaking clients. Demand driven construction means that resources are only built if it seems necessary, as opposed to building resources up front in case we need them.

This weekend I’ve spent some time developing Control.Make a bit further.

I added a requirement that wasn’t in my original list: all or nothing construction. That is, I do not want a build failing halfway through due to missing or inconsistent rules. This is a sort of static resource safety property, to augment Haskell’s type safety. I would also like more flexibility in selecting rules and defaults, perhaps based on soft constraints rather than than the simplistic “first specified is first favored”. These properties have required a considerable overhaul of the prior design, to support staged computations.

On Sunday I was considering a technique with the following basic shape (still using Dynamic.Typeable magic to make it work):

runMake :: Make m (MakePromise m a) -> Either (m a) MakeFailure
addMakeRule :: (Typeable a) => Make m (MakePromise m a) -> Make m ()
make :: (Typeable a) => Make m (MakePromise m a)
makeAction :: m a -> Make m (MakePromise m a)

The idea of MakePromise is to represent a value that will be available at build time.

Unfortunately, I’ve had much difficulty understanding what should happen when I pass a MakePromise as an argument to a new make rule (make >>= addMakeRule . return).

I think the answer might be to switch from Make-the-monad to Make-the-applicative. With applicative, I might also be able to drop the promises by switching to a GADT for static analysis (since the flow of outputs is under much tighter control).

This entry was posted in Language Design, Modularity, Open Systems Programming. Bookmark the permalink.

1 Response to Make for Haskell Values pt 2 of K

  1. dmbarbour says:

    I’ve been silent on this issue for a few months now. Quick status update: for Sirea I’ve decided to delay the issue of modeling complex dependencies and configuration until I’m developing the plugins model, and I want something that can (a) work across plugins, (b) work in a live programming environment, (c) allows adaptive plugins (i.e. based on live analysis of the environment). I have some solid ideas on how to do this – it’s much easier and safer in Haskell than it was when I started it a few years ago in C++.

    Meanwhile, I have a simplified variation – non-configurable, but provides shared resources (like threads or GLUT) – based on Data.Typeable and unsafePerformIO. Basically it models a global space per instance of a Sirea application, without polluting the Haskell global space.

Leave a reply to dmbarbour Cancel reply