Nim can be Haskell This is an example from the real project. This code reads
application config from json with defaults handling in functional style. It
uses `data` macro from the `nimboost` library to generate immutable data
structures, constructors, `copy` operators, and functional types and control
structures from `nimfp` library.
import json,
future,
fp,
boost.typeutils
data HttpConfig, exported, copy:
host: string
port: int
data HttpConfigDef, exported:
host = "localhost".some
port = 8280.some
data SmtpConfig, exported:
host: string
port: Option[int]
credentials: Option[tuple[user: string, pass: string]]
emailFrom: string
emailTo: string
data SmtpConfigDef, exported:
host = string.none
port = int.none.some
credentials = none(tuple[user: string, pass: string]).some
emailFrom = string.none
emailTo = string.none
data DbConfig, exported:
host: string
port: int
dbName: string
user: Option[string]
password: Option[string]
createTables: bool
data DbConfigDef, exported:
host = "localhost".some
port = 5432.some
dbName = string.none
user = string.none.some
password = string.none.some
createTables = true.some
data LogConfig, exported, copy:
file: Option[string]
debug: bool
data LogConfigDef, exported:
file = string.none.some
debug = false.some
data ServiceConfig, exported:
http: HttpConfig
db: DbConfig
smtp: Option[SmtpConfig]
log: LogConfig
data ServiceConfigDef, exported:
http = initHttpConfigDef()
db = initDbConfigDef()
smtp = initSmtpConfigDef()
log = initLogConfigDef()
proc defaultConfig: ServiceConfigDef = initServiceConfigDef()
proc getConfig(d: HttpConfigDef): Option[HttpConfig] = act do:
h <- d.host
p <- d.port
yield initHttpConfig(host = h, port = p)
proc getConfig(d: SmtpConfigDef): Option[SmtpConfig] = act do:
h <- d.host
p <- d.port
c <- d.credentials
f <- d.emailFrom
t <- d.emailTo
yield initSmtpConfig(host = h, port = p, credentials = c, email>From = f,
emailTo = t)
proc getConfig(d: DbConfigDef): Option[DbConfig] = act do:
h <- d.host
p <- d.port
db <- d.dbName
user <- d.user
pass <- d.password
createTables <- d.createTables
yield initDbConfig(host = h, port = p, dbName = db, user = user, password
= pass, createTables = createTables)
proc getConfig(d: LogConfigDef): Option[LogConfig] = act do:
file <- d.file
debug <- d.debug
yield initLogConfig(file = file, debug = debug)
proc getConfig(d: ServiceConfigDef): Option[ServiceConfig] = act do:
http <- d.http.getConfig
db <- d.db.getConfig
smtp <- d.smtp.getConfig
log <- d.log.getConfig
yield initServiceConfig(http = http, db = db, smtp = smtp.some, log = log)
template getOption(n: JsonNode, name: untyped): untyped =
mixin defValue
const notFound = "Required parameter \"" & astToStr(name) & "\" not found"
when type(defValue.name.elemType) is Option:
var x: defValue.name.elemType
(n.some.rightS >>= (mget(astToStr(name)) >=> mvalue(type(x.get))))
.optionT.map((v: type(x.get)) => v.some).getOrElseF(() =>
defValue.name.asEither(notFound))
else:
(n.some.rightS >>= (mget(astToStr(name)) >=>
mvalue(defValue.name.elemType)))
.optionT.getOrElseF(() => defValue.name.asEither(notFound))
template getOptionT(n: JsonNode, name: untyped): untyped =
mixin defValue
n.getOption(name).map((v: type(defValue.name.get)) => v.some).optionT
proc parseHttpConfig(
node: Option[JsonNode],
defValue: HttpConfigDef
): EitherS[HttpConfig] =
node.map do(n: JsonNode) -> auto:
act do:
host <- n.getOption(host)
port <- n.getOption(port)
yield initHttpConfig(host = host, port = port)
.getOrElse(defValue.getConfig.asEither("HTTP configuration not found"))
proc parseDbConfig(
node: Option[JsonNode],
defValue: DbConfigDef
): EitherS[DbConfig] =
node.map do(n: JsonNode) -> auto:
act do:
host <- n.getOption(host)
port <- n.getOption(port)
dbName <- n.getOption(dbName)
user <- n.getOption(user)
password <- n.getOption(password)
createTables <- n.getOption(createTables)
yield initDbConfig(host = host, port = port, dbName = dbName, user =
user, password = password, createTables = createTables)
.getOrElse(defValue.getConfig.asEither("Database configuration not
found"))
proc parseSmtpConfig*(
node: Option[JsonNode],
defValue: SmtpConfigDef
): EitherS[Option[SmtpConfig]] =
node.map do(n: JsonNode) -> auto:
act do:
host <- n.getOption(host)
port <- n.getOption(port)
credentials <- act do:
u <- n.mget("user").optionT.flatMapF((v: JsonNode) => value(string,
v))
p <- n.mget("pass").optionT.flatMapF((v: JsonNode) => value(string,
v))
yield (user: u, pass: p)
.run
emailFrom <- n.getOption(emailFrom)
emailTo <- n.getOption(emailTo)
yield initSmtpConfig(host = host, port = port, credentials =
credentials, emailFrom = emailFrom, emailTo = emailTo)
.sequence
proc parseLogConfig(
node: Option[JsonNode],
defValue: LogConfigDef
): EitherS[LogConfig] =
node.map do(n: JsonNode) -> auto:
act do:
file <- n.getOption(file)
debug <- n.getOption(debug)
yield initLogConfig(file = file, debug = debug)
.getOrElse(defValue.getConfig.asEither("Log configuration not found"))
proc parseConfig*(
node: Option[JsonNode],
defValue: ServiceConfigDef = defaultConfig(),
overrideHttpHost = string.none,
overrideHttpPort = int.none,
overrideLogFile = string.none,
overrideLogDebug = bool.none
): EitherS[ServiceConfig] = act do:
httpNode <- node.mget("http")
http <- act do:
h1 <- parseHttpConfig(httpNode, defValue.http).mapLeft(e => "HTTP
configuration: " & e)
h2 <- overrideHttpHost.map(v => h1.copyHttpConfig(host =
v)).getOrElse(h1).rightS
yield overrideHttpPort.map(v => h2.copyHttpConfig(port =
v)).getOrElse(h2)
dbNode <- node.mget("db")
db <- parseDbConfig(dbNode, defValue.db).mapLeft(e => "Database
configuration: " & e)
smtpNode <- node.mget("smtp")
smtp <- parseSmtpConfig(smtpNode, defValue.smtp).mapLeft(e => "SMTP
configuration: " & e)
logNode <- node.mget("log")
log <- act do:
l1 <- parseLogConfig(logNode, defValue.log).mapLeft(e => "Log
configuration: " & e)
l2 <- overrideLogFile.map(v => l1.copyLogConfig(file =
v.some)).getOrElse(l1).rightS
yield overrideLogDebug.map(v => l2.copyLogConfig(debug =
v)).getOrElse(l2)
yield initServiceConfig(http = http, db = db, smtp = smtp, log = log)