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, emailFrom = 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)
If I create a "data" type without any ref or var field, can I safely pass a pointer to it to another thread, as long as it is not accessed past it's destruction in the owner thread? (I know this would be safe in the JVM, but we're not in the JVM...)
If that is the case, would you consider adding a no-ref/no-var version of data to your library?
Would there be a way to define a generic proc, such that "T" must be a "data" type?
I'm trying to work out if I could "data" to build my actor messages ...
@monster I use this when I have 2 code paths for ref and value types:
proc map*[T; U: not (ref|string|seq)](c: Container[T], f: T -> U): Container[U] =
...
proc map*[T; U: ref|string|seq](c: Container[T], f: T -> U): Container[U] =
...
Note the semicolon ; in T;U. I only had issues with ref type in the resulting container (calling the GC), so you can be really flexible.