Skuld.Comp.CompBlock (skuld v0.1.12)
View SourceThe comp macro for monadic do-notation style effect composition.
Provides comp, defcomp, and defcompp macros that transform
arrow notation (<-) into Skuld.Comp.bind chains.
Usage
import Skuld.Comp.CompBlock
comp do
x <- State.get()
y = x + 1
_ <- State.put(y)
return(y)
endSyntax
x <- effect()- bind the result of an effectful computationx = expr- pure variable binding (unchanged)return(value)- lift a pure value (optional - values are auto-lifted)- Last expression is auto-lifted if not already a computation
Auto-Lifting
Non-computation values are automatically wrapped in pure():
comp do
x <- State.get()
_ <- if x > 5, do: Writer.tell(:big) # nil auto-lifted when false
x * 2 # final expression auto-lifted (no return needed)
endElse Clause
You can add an else clause to handle pattern match failures in <- bindings:
comp do
{:ok, x} <- maybe_returns_error()
return(x)
else
{:error, reason} -> return({:failed, reason})
other -> return({:unexpected, other})
endWhen a pattern in <- fails to match, the else clause handles the unmatched value.
Catch Clause
You can add a catch clause for error handling:
comp do
x <- State.get()
_ <- if x < 0, do: Throw.throw(:negative), else: Comp.pure(:ok)
return(x * 2)
catch
:negative -> return(0)
other -> return({:error, other})
endWhen an error is thrown, it's matched against the catch patterns. If no pattern matches and there's no catch-all, the error is re-thrown.
Combined Else and Catch
Both clauses can be used together. The else must come before catch:
comp do
{:ok, x} <- might_fail_match()
_ <- might_throw_error(x)
return(x)
else
{:error, reason} -> return({:match_failed, reason})
catch
:some_error -> return(:caught_throw)
endSemantic ordering: catch(else(body)). This means:
elsehandles pattern match failures from the main computationcatchhandles throws from both the main computation AND the else handler
Installing Handlers
Use the pipe operator with Module.with_handler/2 to install scoped handlers:
comp do
x <- State.get()
y <- Reader.ask()
return(x + y)
end
|> State.with_handler(0)
|> Reader.with_handler(:config)Handlers are applied in order (first in pipe = innermost).
Function Definitions
defcomp increment() do
x <- State.get()
_ <- State.put(x + 1)
return(x + 1)
end
defcompp private_helper() do
ctx <- Reader.ask()
return(ctx.value)
endFunction definitions also support else and catch:
defcomp safe_get() do
{:ok, x} <- fetch_value()
return(x)
else
{:error, _} -> return(:default)
catch
:serious_error -> return(:fallback)
end
Summary
Functions
Create a computation using do-notation style syntax.
Define a public function whose body is a comp block.
Define a private function whose body is a comp block.
Functions
Create a computation using do-notation style syntax.
Transforms arrow bindings (<-) into Skuld.Comp.bind chains.
Regular assignments (=) are preserved as-is.
Supports optional else and catch clauses.
Example
comp do
x <- State.get()
y = x * 2
_ <- State.put(y)
return(y)
endWith else and catch clauses:
comp do
{:ok, x} <- risky_operation()
return(x)
else
{:error, reason} -> return({:failed, reason})
catch
:error -> return(:default)
end
Define a public function whose body is a comp block.
Supports optional else and catch clauses.
Example
defcomp fetch_and_increment() do
x <- State.get()
_ <- State.put(x + 1)
return(x)
end
defcomp safe_fetch() do
{:ok, x} <- dangerous_op()
return(x)
else
{:error, _} -> return(:default)
catch
:error -> return(:fallback)
end
Define a private function whose body is a comp block.
Supports optional else and catch clauses.
Example
defcompp internal_helper() do
x <- Reader.ask()
return(x.config)
end