7. Effects, without pretending printing is pure
Run 07-effects.bend. Expect:
Hello, functional adventurer!
A pure function calculates a value. An IO computation
describes effects which the runtime performs. Bend is pure, but purity
does not mean we are forbidden to say hello.
def greeting(name: String) -> String:
"Hello, " ++ name ++ "!"
def greet(name: String) -> IO(String):
do IO<String>:
return greeting(name)
def main() -> IO(Unit):
do IO<Unit>:
text : String <- greet("functional adventurer")
IO.print(text)
greeting is the shared pure function used by both the
browser panel and the IO wrapper. ++ joins strings.
return wraps a pure result in the monad; it is not an
imperative early exit. <- binds the result of an effect.
Bend requires the bind's type annotation. IO.print is a
Unit step; Unit is the no-interesting-result
type.
Haskell companion:
greet :: String -> IO String greet name = pure ("Hello, " ++ name ++ "!") main :: IO () main = do text <- greet "functional adventurer" putStrLn textThe sequencing idea is familiar, but Bend spells
Unitrather than()and explicitly names the monad/result in thedoheader. Haskell usually infers the binding's type.
The same idea works for
Maybe
The file also defines:
def add_strs(a: String, b: String) -> Maybe<&2, U32>:
do Maybe<&2, U32>:
x : U32 <- U32.read(a)
y : U32 <- U32.read(b)
return (x + y : U32)
U32.read returns an optional result. If either parse is
None{}, the whole block returns None{}.
Otherwise, it returns Some{sum}. This is a tidy pipeline
with failure, not an exception secretly hiding behind a type.
To try it, replace the file's main with:
def main() -> Maybe<&2, U32>:
add_strs("40", "2")
Expect a Some containing 42. Then try
"biscuits" instead of "2"; expect
None.
Haskell companion: this resembles
dooverMaybe, usingreadMaybefromText.Read. Bend desugars the block through namedMaybe.bindandMaybe.puredefinitions, including the explicit&2argument. It is not relying on Haskell'sMonadtype-class instance inference. The same naming convention works for custom monads.
IO handles are affine too: effects on a file or socket hand back its
handle beside the result. Concurrency (IO.fork /
IO.join) is a separate concern from the pure parallel calls
in the next chapter.
Try it: print a second message after the greeting.
Then run both Maybe experiments. Notice that the greeting
function itself never prints anything.