Do you have 20+ fields of configuration for your kitchen sink API? This approach might be for you.
An approach to specifying many (required) fields, where some are defaulted. What you get:
A field which has no sensible default value (such as one enabling a
new feature like TLSSettings
) can still be
Maybe a
.
Your consumer of this API:
-- | Usage of API.
module Main where
import DBAPI
-- Omitting either username or password triggers a type error.
= connect connSpecDefaults {username = "test", password = "mypw"}
main
-- To override defaults, just specify the field e.g. port:
= connect connSpecDefaults {username = "test", password = "mypw", port = 1234}
main2
-- Thanks Aleksey Khudyakov (@pineapple_zombi) for pointing out that plain record
-- update has the same typing rules as RecordWildCards.
--
-- Old version was: ConnSpec{username="..",password="..",..} where
-- ConnSpec{..} = connSpecDefaults
Definition of an API, in this case the example is a database:
{-# LANGUAGE DataKinds #-}
-- | My database API.
module DBAPI where
import Data.Defaults
data ConnSpec p = ConnSpec
username :: !(Required p String)
{ password :: !(Required p String)
, port :: !Int -- Optional and therefore requires a default.
, host :: !String -- Optional and therefore requires a default.
,
}
connSpecDefaults :: ConnSpec Defaults
= ConnSpec {
connSpecDefaults -- Required fields are ()
= (), password = (),
username -- Defaulted fields need defaults specified
= 5432, host = "localhost"
port
}
-- Example func.
connect :: ConnSpec Complete -> IO ()
= pure () connect _
Basic defaults module to support this approach:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
-- | Providing defaults for fields in a record.
module Data.Defaults where
-- | Purpose of a data type.
data Purpose
= Defaults -- For specifying defaults.
| Complete -- For making a complete record.
-- | Required fields are not usable from a defaults spec.
type family Required (p :: Purpose) a where
Required 'Defaults a = () -- When we're defining defaults, required fields are ().
Required 'Complete a = a