How to solve the errors about error union?
Record : {
time : Utc.Utc,
level : Level,
msg : Lazy.Lazy Str,
}
Err : [
FilterErr Str,
FormatterErr Str,
WriterErr Str,
]
Logger : {
filter : Filter,
writer : Writer,
formatter : Formatter,
}
defaultLogger : Logger
defaultLogger = { filter: defaultFilter, writer: defaultWriter, formatter: defaultFormatter }
Filter : Record -> Task Bool [FilterErr Str]
Writer : Str -> Task {} [WriterErr Str]
Formatter : Writer, Record -> Task {} [WriterErr Str, FormatterErr Str]
defaultFilter : Filter
defaultFilter = \record ->
minLevel = levelFromEnv {} |> Task.mapErr! \e -> FilterErr "Failed to filter log record"
Task.ok ((levelToInt minLevel) <= (levelToInt record.level))
defaultWriter : Writer
defaultWriter = \str -> Stdout.line str |> Task.mapErr \e -> WriterErr "Failed to write log record"
defaultFormatter : Formatter
defaultFormatter = \writer, record ->
msg = record.msg |> Lazy.tryInit |> Lazy.get |> Result.withDefault ""
writer "$(record.time |> Time.utcToRFC3339) $(levelToAnsiStr record.level) - $(msg)"
## do log with the given [Record]
logRecord : Logger, Record -> Task {} [LogErr Err]
logRecord = \logger, record ->
canLog = logger.filter record |> Task.mapErr! \e -> LogErr e
if canLog then
logger.writer |> logger.formatter record |> Task.mapErr \e -> LogErr e
else
Task.ok {}
compiler error report:
root in hello/hello_roc on main [!?]
❯ ./log.roc
── TYPE MISMATCH in ./log.roc ──────────────────────────────────────────────────
Something is off with the body of the defaultFormatter definition:
99│ defaultFormatter : Formatter
100│> defaultFormatter = \writer, record ->
101│> msg = record.msg |> Lazy.tryInit |> Lazy.get |> Result.withDefault ""
102│> writer "$(record.time |> Time.utcToRFC3339) $(levelToAnsiStr record.level) - $(msg)"
The body is an anonymous function of type:
(Str -> Task {} […]), { … } -> Task {} […]
But the type annotation on defaultFormatter says it should be:
(Str -> Task {} […]), { … } -> Task {} [FormatterErr Str, …]
── TYPE MISMATCH in ./log.roc ──────────────────────────────────────────────────
This 2nd argument to this function has an unexpected type:
107│ canLog = logger.filter record |> Task.mapErr! \e -> LogErr e
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The argument is an anonymous function of type:
Bool -> Task {} [LogErr [
FormatterErr Str,
WriterErr Str,
]]
But this function needs its 2nd argument to be:
Bool -> Task {} [LogErr [FilterErr Str]]
── TYPE MISMATCH in ./log.roc ──────────────────────────────────────────────────
Something is off with the body of the logRecord definition:
105│ logRecord : Logger, Record -> Task {} [LogErr Err]
106│ logRecord = \logger, record ->
107│ canLog = logger.filter record |> Task.mapErr! \e -> LogErr e
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This await call produces:
Task {} [LogErr [FilterErr Str]]
But the type annotation on logRecord says it should be:
Task {} [LogErr Err]a
────────────────────────────────────────────────────────────────────────────────
3 errors and 3 warnings found in 60 ms
.
Hi @lost22git, your hello repo looks like an interesting multilingual project :)
Two of the errors are related to open and closed tag unions, but I'm not completely sure what was going wrong behind the scenes yet, it could be related to #5660.
This version is free of compile errors:
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }
import pf.Stdout
import pf.Utc
import pf.Env
import Time
import Lazy
import Ansi
main =
Stdout.line! "Hello, World!"
Record : {
time : Utc.Utc,
level : Level,
msg : Lazy.Lazy Str,
}
Err : [
FilterErr Str,
WriterErr Str,
]
Logger a b : {
filter : Filter a,
writer : Writer,
formatter : Formatter b,
}
defaultLogger : Logger _ _
defaultLogger = { filter: defaultFilter, writer: defaultWriter, formatter: defaultFormatter }
Filter a : Record -> Task Bool [FilterErr Str]a
Writer : Str -> Task {} [WriterErr Str]
Formatter a: Writer, Record -> Task {} [WriterErr Str]a
defaultFilter : Filter _
defaultFilter = \record ->
minLevel = levelFromEnv {} |> Task.mapErr! \e -> FilterErr "Failed to filter log record"
Task.ok ((levelToInt minLevel) <= (levelToInt record.level))
defaultWriter : Writer
defaultWriter = \str -> Stdout.line str |> Task.mapErr \e -> WriterErr "Failed to write log record"
defaultFormatter : Formatter _
defaultFormatter = \writer, record ->
msg = record.msg |> Lazy.tryInit |> Lazy.get |> Result.withDefault ""
writer "$(record.time |> Time.utcToRFC3339) $(levelToAnsiStr record.level) - $(msg)"
## do log with the given [Record]
logRecord : Logger _ _, Record -> Task {} [LogErr Err]
logRecord = \logger, record ->
canLog = logger.filter record |> Task.mapErr! \e -> LogErr e
if canLog then
logger.writer |> logger.formatter record |> Task.mapErr \e -> LogErr e
else
Task.ok {}
Level : [
Debug,
Info,
Warn,
Error,
]
## convert `Level` to `Int`
levelToInt : Level -> I8
levelToInt = \lv ->
when lv is
Debug -> 0
Info -> 10
Warn -> 20
Error -> 30
## convert plain `Str` to `Level`
levelFromStr : Str -> Result Level [LevelFromStrErr Str]
levelFromStr = \s ->
when s is
"DBG" -> Ok Debug
"INF" -> Ok Info
"WAR" -> Ok Warn
"ERR" -> Ok Error
_ -> Err (LevelFromStrErr s)
## convert `Level` to plain `Str`
levelToStr : Level -> Str
levelToStr = \lv ->
when lv is
Debug -> "DBG"
Info -> "INF"
Warn -> "WAR"
Error -> "ERR"
## get `AnsiStyle`s of given `Level`
levelToAnsiStyles : Level -> List Ansi.Style
levelToAnsiStyles = \lv ->
when lv is
Debug -> [Bold, FgBlue]
Info -> [Bold, FgGreen]
Warn -> [Bold, FgYellow]
Error -> [Bold, FgRed]
## convert `Level` to ansi `Str`
levelToAnsiStr : Level -> Str
levelToAnsiStr = \lv -> Ansi.ansiStr (levelToStr lv) (levelToAnsiStyles lv)
## read `Level` from environment variable `ROC_LOG_LEVEL`
levelFromEnv : {} -> Task Level _
levelFromEnv = \_ ->
Env.var "ROC_LOG_LEVEL"
|> Task.await \s -> (levelFromStr s |> Task.fromResult)
|> Task.onErr! \_ -> Task.ok Info
Note that I removed FormatterErr Str in:
Formatter : Writer, Record -> Task {} [WriterErr Str, FormatterErr Str]
Because the error below was complaining that FormatterErr could not happen based on your code and it was right :)
The body is an anonymous function of type:
(Str -> Task {} […]), { … } -> Task {} […]
But the type annotation on defaultFormatter says it should be:
(Str -> Task {} […]), { … } -> Task {} [FormatterErr Str, …]
@Anton-4 Thank you very much for your answer, and I'm sorry that I didn't post all the code. But I still find it difficult to understand the compatibility rules of roc's error unions.
But I still find it difficult to understand the compatibility rules of roc's error unions.
That's understandable, they are a pretty unique concept.
Can you put together a small example that you find confusing? That will make it easier to discuss things.
Sidenote: do you already have the roc language server set up? You can use for example this vscode extension for it. Being able to see the inferred type of something on hover can be helpful.