Skip to content

Instantly share code, notes, and snippets.

@danidiaz
Last active March 17, 2022 22:07
Show Gist options
  • Select an option

  • Save danidiaz/f8c260755ea81ac365a40aba2ed8b802 to your computer and use it in GitHub Desktop.

Select an option

Save danidiaz/f8c260755ea81ac365a40aba2ed8b802 to your computer and use it in GitHub Desktop.
Modeling Nix's "override" and "overrideAttrs" in Haskell
-- | Derivations in Nixpkgs are usually the result of a chain of function calls.
-- Typically, a function receives some input derivations as arguments (the "inputs
-- pattern") and it eventually calls the 'mkDerivation' function, which produces the
-- actual derivation value that is put into Nixpkgs.
--
-- Turns out that derivation values in Nixpkgs keep a "memory" of how they were produced at
-- those two stages: which functions were called, and with which arguments. They
-- also provide "hooks" that allow re-running the stages with (partially or
-- totally) different arguments, resulting in a new derivation.
--
-- The good thing about these hooks is that you don't need to supply the full
-- arguments to re-create the derivation, only the parts that you want changed.
--
-- There are two hooks:
--
-- - "override" for tweaking the call to the initial function which took the input derivations as arguments.
--
-- - "overrideAttrs" for tweaking the call to mkDerivation.
--
-- Here's an attempt at modeling something resembling that in Haskell. It doesn't aim to be a practical
-- piece of code, just a tool for thought.
module Main where
import Data.Monoid
type Traced m a = m -> a -- See Control.Comonad.Traced
-- When a function has a Monoid input, it's easy to extract a result.
extract :: Monoid m => Traced m a -> a
extract f = f mempty
-- We don't bother defining any fields.
data Derivation = Derivation deriving (Show)
newtype MkDerivationAttrs = MkDerivationAttrs {getMkDerivationAttrs :: String}
-- Tweak the attributes with a function.
type OverrideAttrs = Endo MkDerivationAttrs
mkDerivation :: MkDerivationAttrs -> Traced OverrideAttrs Derivation
mkDerivation attrs f = mkDerivation_ (appEndo f attrs)
where
mkDerivation_ :: MkDerivationAttrs -> Derivation
mkDerivation_ = undefined -- whatever. Provided by the library.
type Inputs = [Derivation] -- could be a map, or whatever
-- Tweak the input derivations with a function.
type Override = Endo Inputs
-- This callPackage doesn't do the "select the packages from the whole of nixpkgs" thing,
-- in only makes the inputs overrideable.
callPackage ::
(Inputs -> Traced x Derivation) ->
(Inputs -> Traced (Override, x) Derivation)
callPackage buildFromInputs inputs (override, overrideAttrs) =
buildFromInputs (appEndo override inputs) overrideAttrs
-- Example of derivation-building function provided by the library user.
-- The library user only concerns itself with buiding the derivation, he
-- should leave to 'callPackage' the task of making the inputs overrideable.
someDerivationFromInputs :: Inputs -> Traced OverrideAttrs Derivation
-- The implementation should make use of mkDerivation at the end.
someDerivationFromInputs inputs endos = undefined
main :: IO ()
main = do
let someDerivationFromInputs' :: Inputs -> Traced (Override, OverrideAttrs) Derivation
someDerivationFromInputs' = callPackage someDerivationFromInputs
someInput :: Derivation
someInput = Derivation
anotherInput :: Derivation
anotherInput = Derivation
print $ extract $ someDerivationFromInputs' [someInput, anotherInput]
@danidiaz
Copy link
Author

@pwn These toy Haskell models are surprisingly useful for understanding stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment