SlideShare a Scribd company logo
from Scala monadic effects
to Unison algebraic effects
Introduction to Unison’s algebraic effects (abilities)
go from a small Scala program based on the Option monad
to a Unison program based on the Abort ability
- inspired by, and part based on, a talk by Runar Bjarnason -
Runar Bjarnason
@runarorama
@philip_schwarzby
https://www.slideshare.net/pjschwarz
We start off by looking at how Runar Bjarnason explains Unison’s effect system
in his talk Introduction to the Unison programming language.
@philip_schwarz
Another thing we really wanted to be thoughtful about was unison’s
effect system, because I mean, let’s be honest, monads are awkward.
I came out and said it, monads are awkward, they come with a
syntactic overhead as well as a cognitive overhead, like, you know, a
lot of the time you spend your time trying to figure out how to lift this
thing into the monad you want, in which order is my monad
transformer stack supposed to be and things like that.
Runar Bjarnason
Cofounder, Unison Computing.
Author of Functional Programming in Scala.
@runarorama
Lambda World 2018 - Introduction to the Unison programming language - Rúnar Bjarnason
So Unison uses what’s sometimes known as algebraic
effects. We modeled our effect system in a language
called Frank, which is detailed in this paper, which is
called Do Be Do Be Do, by Sam Lindley, Conor
McBride and Craig McLaughlin, and Frank calls these
abilities, rather than effects, and so we do that, we
call them abilities.
So here is a simple State ability.
This is the ability to put and get some global state of
type s. Abilities are introduced with the ability
keyword and this defines two functions, put and get.
put takes some state of type s and it returns unit with
the State ability attached to it, and then get will give
you that s, given that you have the State ability.
When we see a thing like this in curly braces, it means
this requires that ability. So put requires the State
ability and get also requires the State ability.
So this is very similar to an Algebraic Data Type where
you are defining the type State, this ability type, and
these are the constructors of the type: put and get.
Runar Bjarnason
@runarorama
So for example we can write effectful functions
push and pop on a global stack.
So given that the state is a stack, then we have
the ability to manipulate some state that is a list
of as: we can pop and push.
So note that there is no monadic plumbing here.
These are just code blocks.
And so to pop, we get the stack, we drop one
element from the stack, we put that and then we
get the head of the stack. So that’s pop. And then
push, we just say, cons a onto the front of
whatever we get, and put that.
The reason why the pop is quoted is that only
computations can have effects, not values. So once you
have computed a value, you can no longer have effects.
So the quoting is just a nullary function that returns
whatever this evaluates to.
There is no applicative syntax or anything like that,
because we are actually overloading the function
application syntax. So in unison applicative
programming is the default. We chose that as a design
constraint.
Runar Bjarnason
@runarorama
The types will ensure that you can’t go wrong
here, that you are not talking about the
wrong thing.
So for example whereas in Scala you might
say a comes from x, b comes from y and then
c comes from z, and then you want to do f of
a, b and c.
In Unison you just say f x y z and it will figure
that out. It will do the pulling out. It will do
all the effects.
So whereas in Haskell you might have to say x bind
lambda of f of a and then bind g, in Unison you just
say g of f of x.
So that’s kind of nice, there is a low syntactic
overhead to this and there is a low cognitive
overhead to this, for the programmer.
Runar Bjarnason
@runarorama
So the programmer can just use our pop and push and write a little program that pushes and pops the stack using our State ability.
So given that we have the ability to manipulate some state of type list of Nat, we can write a stack program.
a is the head of the stack, we pop the stack and now we have mutated the stack and then if a is five then push it back, otherwise push 3 and 8.
So this looks like a little imperative program but it is actually a purely functional program.
There are no side effects here but there is also no visible effect plumbing.
Runar Bjarnason @runarorama
So then to handle the State ability, to make it actually do something, we write a handler using a handle keyword.
This here is a pure handler for the State ability and we can use that handler, at the bottom, the runStack thing uses
that handler to run the stackProgram with some initial state which is [5,4,3,2,1].
Normally this kind of stuff would be hidden away in library code. Most programmers will not be writing their own
handlers but if you have your own set of abilities, you’ll be able to write your handlers.
Runar Bjarnason @runarorama
So here the definition of state, the expression here at the bottom is like handle h of s in bang c, where the
exclamation sign means force this computation. c is some quoted computation, you can see that it is quoted in the
type, it is something of type {State s} a, and then I am saying, force that, actually evaluate it, but handle using the
handler h, or h of s, where s is the initial state coming in, it is that [5,4,3,2,1] thing.
And then the definition of h is just above and it proceeds by pattern matching on the constructors of the ability.
Runar Bjarnason @runarorama
If the call was to a get, then we end up in that case and what we get out of that pattern is k, a continuation for the
program, the rest of the program, and what is expected is that I pass the current state to k, that is we allow the
program to continue with the current state, so if there is a get then I call k of s and this is a recursive definition, I keep
trying to handle if there is any more state manipulation going on, it is actually calling the handler again, because k of s
might also need access to the state ability.
And then to put, we get a state that somebody wanted to put and we get the continuation of the program and we say
well, handle that using the state and then continue by passing the unit to the continuation.
And then in the pure case, when there is no effect, we just return the value that we ended up with.
Runar Bjarnason
@runarorama
Runar Bjarnason @runarorama
The next slide has all of the state code shown by Runar.
@philip_schwarz
When I went to run that code, I made the following changes to it:
• I updated it to reflect some minor changes to the Unison language which
have occurred since Runar gave the talk.
• Since the pop function returns Optional a, I changed stackProgram so that
it doesn’t expect pop to return an a.
• Since runStack returns a stack, i.e. a list of numbers, I changed
stackProgram to also return a stack.
• I changed a bit the pushing and popping that stackProgram does, and
added automated tests to visualise the effect of that logic on a stack.
• Since the pop function returns a quoted computation, I prefixed invocations
of pop with the exclamations sign, to force the execution of the
computations.
• I prefixed usages of put and get with State.
• I added the List.head function that pop uses
See the next slide for the resulting code
state : s -> '({State s} a) -> a
state s c =
h s cases
{ State.get -> k } ->
handle k s with h s
{ State.put s -> k } ->
handle k () with h s
{ a } -> a
handle !c with h s
ability State s where
put : s -> {State s} ()
get : {State s} s
pop : '{State [a]} (Optional a)
pop = 'let
stack = State.get
State.put (drop 1 stack)
head stack
push : a -> {State [a]} ()
push a = State.put (cons a State.get)
stackProgram : '{State [Nat]} [Nat]
stackProgram =
'let top = !pop
match top with
None ->
push 0
push 1
push 2
Some 5 ->
!pop
push 5
Some n ->
!pop
!pop
push n
push (n + n)
State.get
List.head : [a] -> Optional a
List.head a = List.at 0 a
use List head
runStack : [Nat]
runStack = state [5,4,3,2,1] stackProgram
test> topIsFive =
check(state [5,4,3,2,1] stackProgram == [5,3,2,1])
test> topIsNotFive =
check(state [6,5,4,3,2,1] stackProgram == [12,6,3,2,1])
test> topIsMissing =
check(state [] stackProgram == [2,1,0])
> runStack
⧩
[5, 3, 2, 1]
To help understand how the state function works, I made the
following changes to it:
• make the type of the h function explicit
• rename the h function to handler
• rename c to computation
• rename k to continuation
• break ‘each handle … with …’ line into two lines
The next slide shows the state function before and after the changes
and the slide after that shows the whole code again after the changes
to the state function.
state : s -> '({State s} a) -> a
state s c =
h s cases
{ State.get -> k } ->
handle k s with h s
{ State.put s -> k } ->
handle k () with h s
{ a } -> a
handle !c with h s
state : s -> '({State s} a) -> a
state s computation =
handler : s -> Request {State s} a -> a
handler s cases
{ State.get -> continuation } ->
handle continuation s
with handler s
{ State.put s -> continuation } ->
handle continuation ()
with handler s
{ a } -> a
handle !computation
with handler s
In https://www.unisonweb.org/docs/language-reference :we read the following:
.base.Request is the constructor of requests for abilities. A type Request A T is the type of values received by
ability handlers for the ability A where the current continuation requires a value of type T.
So on the right we see the state handler function taking first a state, and then a Request {State s} a, i.e. a
request for the State ability where the continuation requires a value of type a.
@philip_schwarz
state : s -> '({State s} a) -> a
state s computation =
handler : s -> Request {State s} a -> a
handler s cases
{ State.get -> continuation } ->
handle continuation s
with handler s
{ State.put s -> continuation } ->
handle continuation ()
with handler s
{ a } -> a
handle !computation
with handler s
ability State s where
put : s -> {State s} ()
get : {State s} s
stackProgram : '{State [Nat]} [Nat]
stackProgram =
'let top = !pop
match top with
None ->
push 0
push 1
push 2
Some 5 ->
!pop
push 5
Some n ->
!pop
!pop
push n
push (n + n)
State.get
> runStack
⧩
[5, 3, 2, 1]
List.head : [a] -> Optional a
List.head a = List.at 0 a
use List head
test> topIsFive =
check(state [5,4,3,2,1] stackProgram == [5,3,2,1])
test> topIsNotFive =
check(state [6,5,4,3,2,1] stackProgram == [12,6,3,2,1])
test> topIsMissing =
check(state [] stackProgram == [2,1,0])
runStack : [Nat]
runStack = state [5,4,3,2,1] stackProgram
pop : '{State [a]} (Optional a)
pop = 'let
stack = State.get
State.put (drop 1 stack)
head stack
push : a -> {State [a]} ()
push a = State.put (cons a State.get)
Now back to Runar’s talk for one more
fact about functional effects in Unison.
And yes, you can still use monads, if you want.
You don’t have to use this ability stuff.
You can still use monads and it will work just fine.
Runar Bjarnason
@runarorama
Earlier on Runar showed us a comparison between a Scala monadic for comprehension and a Unison plain
function invocation that instead relied on an ability. He also showed us a comparison between a Haskell
expression using the bind function (flatMap in Scala) and a Unison plain function invocation that again relied
on an ability.
In the rest of this slide deck, we are going to do two things:
• Firstly, we are going to look at an example of how functional effects look like in Unison when we use
monadic effects rather than algebraic effects. i.e we are going to use a monad rather than an ability. We
are going to do that by starting with a very small Scala program that uses a monad and then translating the
program into the Unison equivalent.
• Secondly, we want to see another Unison example of implementing a functional effect using an ability, so
we are going to take the above Unison program and convert it so that it uses an ability rather than a
monad.
In the process we’ll be making the following comparisons:
The state functional effect is not the easiest to understand, so to aid our understanding, the program we’ll be
looking at is simply going to do validation using the functional effect of optionality.
-- Scala program that
-- uses the Option monad
for {
a <- x
b <- y
c <- z
} yield f(a b c)
-- Unison program that
-- uses the Abort ability
???
-- Scala program that
-- uses the Option monad
x flatMap { a =>
y flatMap { b =>
z map { c =>
f(a,b,c)
}
}
}
-- Unison program that
-- uses the Optional monad
???
@philip_schwarz
The Scala program that we’ll be translating
into Unison. Is on the next slide.
@philip_schwarz
sealed trait Option[+A] {
def map[B](f: A => B): Option[B] =
this flatMap { a => Some(f(a)) }
def flatMap[B](f: A => Option[B]): Option[B] =
this match {
case Some(a) => f(a)
case None => None
}
}
case object None extends Option[Nothing]
case class Some[+A](get: A) extends Option[A]
def validateName(name: String): Option[String] =
if (name.size > 1 && name.size < 15)
Some(name)
else None
def validateSurname(surname: String): Option[String] =
if (surname.size > 1 && surname.size < 20)
Some(surname)
else None
def validateAge(age: Int): Option[Int] =
if (age > 0 && age < 112)
Some(age)
else None
case class Person(name: String, surname: String, age: Int)
def createPerson(name: String, surname: String, age: Int): Option[Person] =
for {
aName <- validateName(name)
aSurname <- validateSurname(surname)
anAge <- validateAge(age)
} yield Person(aName, aSurname, anAge)
val people: String =
potentialPeople
.foldLeft("")(((text,person) => text + "n" + toText(person)))
assert( people == "nPerson(Fred,Smith,35)nNonenNonenNone" )
println(people)
è
Person(Fred,Smith,35)
None
None
None
val potentialPeople = List(
createPerson("Fred", "Smith", 35),
createPerson( "x", "Smith", 35),
createPerson("Fred", "", 35),
createPerson("Fred", "Smith", 0)
)
def toText(option: Option[Person]): String =
option match {
case Some(person) => person.toString
case None => "None"
}
def validateName(name: String): Option[String] =
if (name.size > 1 && name.size < 15)
Some(name)
else None
def validateSurname(surname: String): Option[String] =
if (surname.size > 1 && surname.size < 20)
Some(surname)
else None
def validateAge(age: Int): Option[Int] =
if (age > 0 && age < 112)
Some(age)
else None
validateName : Text -> Optional Text
validateName name =
if (size name > 1) && (size name < 15)
then Some name
else None
validateSurname : Text -> Optional Text
validateSurname surname =
if (size surname > 1) && (size surname < 20)
then Some surname
else None
validateAge : Nat -> Optional Nat
validateAge age =
if (age > 0) && (age < 112)
then Some age
else None
Let’s begin by translating the validation functions. The
Unison equivalent of Scala’ s Option is the Optional type.
as a minor aside, if we were using the Scala built-in Option
type then we would have the option of rewriting code like this
if (age > 0 && age < 112)
Some(age)
else None
as follows
Option.when(age > 0 && age < 112)(age)
or alternatively as follows
Option.unless(age <= 0 || age > 112)(age)
sealed trait Option[+A] {
def map[B](f: A => B): Option[B] =
this flatMap { a => Some(f(a)) }
def flatMap[B](f: A => Option[B]): Option[B] =
this match {
case Some(a) => f(a)
case None => None
}
}
case object None extends Option[Nothing]
case class Some[+A](get: A) extends Option[A]
type base.Optional a = None | Some a
base.Optional.map : (a -> b) -> Optional a -> Optional b
base.Optional.map f = cases
None -> None
Some a -> Some (f a)
base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b
base.Optional.flatMap f = cases
None -> None
Some a -> f a
use .base.Optional map flatMap
Now that we have the validation functions in place, let’s look at the translation of the functional effect of optionality.
On the left hand side we have a handrolled Scala Option with map defined in terms of flatMap, and on the right hand
side we have the Unison predefined Optional type and its predefined map and flatMap functions.
type Person = { name: Text, surname: Text, age: Nat }
use .base.Optional map flatMap
createPerson : Text -> Text -> Nat -> Optional Person
createPerson name surname age =
flatMap (aName ->
flatMap (aSurname ->
map (anAge ->
Person.Person aName aSurname anAge
)(validateAge age)
)(validateSurname surname)
)(validateName name)
case class Person(name: String, surname: String, age: Int)
def createPerson(name : String, surname: String, age: Int): Option[Person] =
for {
aName <- validateName(name)
aSurname <- validateSurname(surname)
anAge <- validateAge(age)
} yield Person(aName, aSurname, anAge)
Now that we have the map and flatMap functions in place, let’s look at the translation of the
Scala for comprehension into Unison.
We are implementing the functional effect of optionality using a monad, so while in Scala we
can use the syntactic sugar of a for comprehension, in Unison there is no equivalent of the
for comprehension (AFAIK) and so we are having to use an explicit chain of flatMap and map.
type Person = { name: Text, surname: Text, age: Nat }
use .base.Optional map flatMap
createPerson : Text -> Text -> Nat -> Optional Person
createPerson name surname age =
flatMap (aName ->
flatMap (aSurname ->
map (anAge ->
Person.Person aName aSurname anAge
)(validateAge age)
)(validateSurname surname)
)(validateName name)
case class Person(name: String, surname: String, age: Int)
def createPerson(name : String, surname: String, age: Int): Option[Person] =
validateName(name) flatMap { aName =>
validateSurname(surname) flatMap { aSurname =>
validateAge(age) map { anAge =>
Person(aName, aSurname, anAge)
}
}
}
Here is the same comparison as on the previous slide but
with the Scala code explicitly using map and flatMap.
@philip_schwarz
val people: String =
potentialPeople
.foldLeft("")(((text,person) => text + "n" + toText(person)))
assert(
people == "nPerson(Fred,Smith,35)nNonenNonenNone"
)
people : Text
people = foldl
(text person -> text ++ "n" ++ (toText person))
""
potentialPeople
peopleTest = check (
people == "nPerson(Fred,Smith,35)nNonenNonenNone"
)
val potentialPeople: List[Option[Person]] =
List( createPerson("Fred", "Smith", 35),
createPerson( "x", "Smith", 35),
createPerson("Fred", "", 35),
createPerson("Fred", "Smith", 0) )
def toText(option: Option[Person]): String =
option match {
case Some(person) => person.toString
case None => "None"
}
potentialPeople: [Optional Person]
potentialPeople =
[ (createPerson "Fred" "Smith" 35),
(createPerson "x" "Smith" 35),
(createPerson "Fred" "" 35),
(createPerson "Fred" "Smith" 0) ]
toText : Optional Person -> Text
toText = cases
Some person -> Person.toText person
None -> "None”
Person.toText : Person -> Text
Person.toText person =
match person with Person.Person name surname age
-> "Person(" ++ name ++ ","
++ surname ++ ","
++ Text.toText(age) ++ ")"
Here we translate the rest of the program.
See the next slide for the Unison
translation of the whole Scala program.
@philip_schwarz
type Person = {
name: Text, surname: Text, age: Nat
}
use .base.Optional map flatMap
createPerson : Text -> Text -> Nat -> Optional Person
createPerson name surname age =
flatMap (aName ->
flatMap (aSurname ->
map (anAge ->
Person.Person aName aSurname anAge
)(validateAge age)
)(validateSurname surname)
)(validateName name)
people : Text
people = foldl
(text person -> text ++ "n" ++ (toText person))
""
potentialPeople
peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone")
type base.Optional a = None | Some a
base.Optional.map : (a -> b) -> Optional a -> Optional b
base.Optional.map f = cases
None -> None
Some a -> Some (f a)
base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b
base.Optional.flatMap f = cases
None -> None
Some a -> f a
validateName : Text -> Optional Text
validateName name =
if (size name > 1) && (size name < 15)
then Some name
else None
validateSurname : Text -> Optional Text
validateSurname surname =
if (size surname > 1) && (size surname < 20)
then Some surname
else None
validateAge : Nat -> Optional Nat
validateAge age =
if (age > 0) && (age < 112)
then Some age
else None
toText : Optional Person -> Text
toText = cases
Some person -> Person.toText person
None -> "None”
Person.toText : Person -> Text
Person.toText person =
match person with
Person.Person name surname age
-> "Person(" ++
name ++ "," ++
surname ++ "," ++
Text.toText(age) ++ ")"
potentialPeople: [Optional Person]
potentialPeople =
[(createPerson "Fred" "Smith" 35),
(createPerson "x" "Smith" 35),
(createPerson "Fred" "" 35),
(createPerson "Fred" "Smith" 0)]
On the next slide we look at some simple
automated tests for the Unison program.
test> peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone")
test> validPersonAsText = check (Person.toText (Person.Person "Fred" "Smith" 35) == "Person(Fred,Smith,35)")
test> validPerson = check (createPerson "Fred" "Smith" 35 == Some (Person.Person "Fred" "Smith" 35))
test> noValidPersonWithInvalidName = check (createPerson "F" "Smith" 35 == None)
test> noValidPersonWithInvalidSurname = check (createPerson "Fred" "" 35 == None)
test> noValidPersonWithInvalidAge = check (createPerson "Fred" "Smith" 200 == None)
test> noValidPersonWithInvalidNameSurnameAndAge = check (createPerson "" "S" 200 == None)
test> validName = check (validateName "Fred" == Some "Fred")
test> validSurname = check (validateSurname "Smith" == Some "Smith")
test> validAge = check (validateAge 35 == Some 35)
test> noInvalidName = check (validateName "" == None)
test> noInvalidSurname = check (validateSurname "X" == None)
test> noInvalidAge = check (validateAge 200 == None)
As we have seen, the Unison program currently implements the
functional effect of optionality using the Optional monad.
What we are going to do next is improve that program, make it
easier to understand, by changing it so that it implements the effect
of optionality using an ability (algebraic effect) called Abort.
Abort.toOptional : '{g, Abort} a -> {g} Optional a
Abort.toOptional computation =
handler : Request {Abort} a -> Optional a
handler = cases
{ a } -> Some a
{ abort -> _ } -> None
handle !computation
with handler
state : s -> '({State s} a) -> a
state s computation =
handler : s -> Request {State s} a -> a
handler s cases
{ State.get -> continuation } ->
handle continuation s
with handler s
{ State.put s -> continuation } ->
handle continuation ()
with handler s
{ a } -> a
handle !computation
with handler s
ability State s where
put : s -> {State s} ()
get : {State s} s
ability Abort where
abort : {Abort} a
Let’s begin by looking at the Abort ability. Although it is a predefined ability, on this slide I have refactored the original a bit so that we can
better compare it with the State ability that we saw earlier in Runar’s code.
In later slides I am going to revert to the predefined version of the ability, which being split into two functions, offers different advantages.
The Abort ability is much simpler than the State ability, that’s why I think it could be a good first example of using abilities.
To help understand how the handler of the Abort ability works, in
the next slide we look at some relevant documentation from a
similar Abort ability in the Unison language reference.
By the way, it looks like that g in the the signature of the toOptional
function somehow caters for potentially multiple abilities being in
play at the same time, but we’ll just ignore that aspect because it is
out of scope for our purposes.
ability Abort where
aborting : ()
-- Returns `a` immediately if the
-- program `e` calls `abort`
abortHandler : a -> Request Abort a -> a
abortHandler a = cases
{ Abort.aborting -> _ } -> a
{ x } -> x
p : Nat
p = handle
x = 4
Abort.aborting
x + 2
with abortHandler 0
A handler can choose to call the continuation or not, or to call it
multiple times. For example, a handler can ignore the continuation in
order to handle an ability that aborts the execution of the program.
The program p evaluates to 0. If we remove the Abort.aborting call, it
evaluates to 6.
Note that although the ability constructor is given the
signature aborting : (), its actual type is {Abort} ().
The pattern { Abort.aborting -> _ } matches when
the Abort.aborting call in p occurs. This pattern ignores its
continuation since it will not invoke it (which is how it aborts the
program). The continuation at this point is the expression _ -> x + 2.
The pattern { x } matches the case where the computation is pure
(makes no further requests for the Abort ability and the continuation is
empty). A pattern match on a Request is not complete unless this case
is handled.
from https://www.unisonweb.org/docs/language-reference
As I said on the previous slide, while the above Abort ability is similar to the one we are going
to use, it is not identical. e.g. this handler returns an a rather than an Optional a. The reason
why we are looking at this example is because the patterns in the handler are identical and
the above explanations are also useful for the Abort ability that we are going to use.
type base.Optional a = None | Some a
base.Optional.map : (a -> b) -> Optional a -> Optional b
base.Optional.map f = cases
None -> None
Some a -> Some (f a)
base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b
base.Optional.flatMap f = cases
None -> None
Some a -> f a
type base.Optional a = None | Some a
ability Abort where
abort : {Abort} a
Abort.toOptional.handler : Request {Abort} a -> Optional a
Abort.toOptional.handler = cases
{ a } -> Some a
{ abort -> _ } -> None
Abort.toOptional : '{g, Abort} a -> {g} Optional a
Abort.toOptional a =
handle !a with toOptional.handler
So here on the left are the map and flatMap functions that the program
currently uses to implement the functional effect of optionality and on the right
is the predefined Abort ability that the program is now going to use instead.
@philip_schwarz
validateName : Text -> Optional Text
validateName name =
if (size name > 1) && (size name < 15)
then Some name
else None
validateSurname : Text -> Optional Text
validateSurname surname =
if (size surname > 1) && (size surname < 20)
then Some surname
else None
validateAge : Nat -> Optional Nat
validateAge age =
if (age > 0) && (age < 112)
then Some age
else None
validateName : Text -> { Abort } Text
validateName name =
if (size name > 1) && (size name < 15)
then name
else abort
validateSurname : Text -> { Abort } Text
validateSurname surname =
if (size surname > 1) && (size surname < 20)
then surname
else abort
validateAge : Nat -> { Abort } Nat
validateAge age =
if (age > 0) && (age < 112)
then age
else abort
Here we refactor the validation functions and on the next slide
we refactor the majority of the rest of the program, leaving
the most interesting bit of refactoring for the slide after that.
people : Text
people =
foldl (text person -> text ++ "n" ++ (toText person))
""
potentialPeople
peopleTest = check (
people == "nPerson(Fred,Smith,35)nNonenNonenNone”
)
people : Text
people =
foldl (text person -> text ++ "n" ++ toText (toOptional person))
""
potentialPeople
peopleTest = check (
people == "nPerson(Fred,Smith,35)nNonenNonenNone”
)
potentialPeople : ['{Abort} Person]
potentialPeople =
[ '(createPerson "Fred" "Smith" 35),
'(createPerson "x" "Smith" 35),
'(createPerson "Fred" "" 35),
'(createPerson "Fred" "Smith" 0) ]
potentialPeople: [Optional Person]
potentialPeople =
[ (createPerson "Fred" "Smith" 35),
(createPerson "x" "Smith" 35),
(createPerson "Fred" "" 35),
(createPerson "Fred" "Smith" 0) ]
toText : Optional Person -> Text
toText = cases
Some person -> Person.toText person
None -> "None”
Person.toText : Person -> {} Text
Person.toText person =
match person with Person.Person name surname age
-> "Person(" ++ name ++ ","
++ surname ++ ","
++ Text.toText(age) ++ ")"
toText : Optional Person -> Text
toText = cases
Some person -> Person.toText person
None -> "None”
Person.toText : Person -> {} Text
Person.toText person =
match person with Person.Person name surname age
-> "Person(" ++ name ++ ","
++ surname ++ ","
++ Text.toText(age) ++ ")"
type Person = { name: Text, surname: Text, age: Nat }
createPerson : Text -> Text -> Nat -> Optional Person
createPerson name surname age =
flatMap (aName ->
flatMap (aSurname ->
map (anAge ->
Person.Person aName aSurname anAge
)(validateAge age)
)(validateSurname surname)
)(validateName name)
type Person = { name: Text, surname: Text, age: Nat }
createPerson : Text -> Text -> Nat -> { Abort } Person
createPerson name surname age =
Person.Person
(validateName name)
(validateSurname surname)
(validateAge age)
And now the most interesting bit of the refactoring.
See how much simpler the createPerson function becomes when the functional effect of
optionality is implemented not using a monad but using an ability and its handler.
This new version of the createPerson function, which uses an ability (and its associated handler) is not
only an improvement over the version that uses a monad but also over the Scala version that itself
improves on explicit monadic code by using a for comprehension.
-- Scala
for {
aName <- validateName(name)
aSurname <- validateSurname(surname)
anAge <- validateAge(age)
} yield Person(aName, aSurname, anAge)
-- Unison
Person.Person
(validateName name)
(validateSurname surname)
(validateAge age)
In Unison you just say f x y z and it
will figure that out. It will do the
pulling out. It will do all the effects.
Runar Bjarnason
@runarorama
See the next slide for all the code of the
refactored Unison program. See the subsequent
slide for associated automated tests.
@philip_schwarz
type Person = { name: Text, surname: Text, age: Nat }
createPerson : Text -> Text -> Nat -> { Abort } Person
createPerson name surname age =
Person.Person
(validateName name)
(validateSurname surname)
(validateAge age)
validateName : Text -> { Abort } Text
validateName name =
if (size name > 1) && (size name < 15)
then name
else abort
validateSurname : Text -> { Abort } Text
validateSurname surname =
if (size surname > 1) && (size surname < 20)
then surname
else abort
validateAge : Nat -> { Abort } Nat
validateAge age =
if (age > 0) && (age < 112)
then age
else abort
people : Text
people =
foldl (text person -> text ++ "n" ++ toText (toOptional person))
""
potentialPeople
peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone”)
toText : Optional Person -> Text
toText = cases
Some person -> Person.toText person
None -> "None”
Person.toText : Person -> {} Text
Person.toText person =
match person with Person.Person name surname age
-> "Person(" ++ name ++ ","
++ surname ++ ","
++ Text.toText(age) ++ ")"
potentialPeople: ['{Abort} Person]
potentialPeople =
[ '(createPerson "Fred" "Smith" 35),
'(createPerson "x" "Smith" 35),
'(createPerson "Fred" "" 35),
'(createPerson "Fred" "Smith" 0) ]
type base.Optional a = None | Some a
ability Abort where
abort : {Abort} a
Abort.toOptional.handler : Request {Abort} a -> Optional a
Abort.toOptional.handler = cases
{ a } -> Some a
{ abort -> _ } -> None
Abort.toOptional : '{g, Abort} a -> {g} Optional a
Abort.toOptional a =
handle !a with toOptional.handler
test> peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone")
test> validPersonAsText = check (Person.toText (Person.Person "Fred" "Smith" 35) == "Person(Fred,Smith,35)")
test> createValidPerson = check ((toOptional '(createPerson "Fred" "Smith" 35)) == Some((Person.Person "Fred" "Smith" 35)))
test> abortAsOptionIsNone = check (toOptional 'abort == None)
test> abortExpressionAsOptionIsNone = check (toOptional '(if false then "abc" else abort) == None)
test> nonAbortExpressionAsOptionIsSome = check (toOptional '(if true then "abc" else abort) == Some "abc")
test> notCreatePersonWithInvalidName = check (toOptional('(createPerson "F" "Smith" 35)) == toOptional('abort))
test> notCreatePersonWithInvalidSurname = check (toOptional('(createPerson "Fred" "" 35)) == toOptional('abort))
test> notCreatePersonWithInvalidAge = check (toOptional('(createPerson "Fred" "Smith" 200)) == toOptional('abort))
test> personWithInvalidNameAsOptionIsNone = check (toOptional '(createPerson "F" "Smith" 35) == None)
test> personWithInvalidSurnameAsOptionIsNone = check (toOptional '(createPerson "Fred" "" 35) == None)
test> personWithInvalidAgeAsOptionIsNone = check (toOptional '(createPerson "Fred" "Smith" 200) == None)
test> personWithAllInvalidFieldsAsOptionIsNone = check (toOptional '(createPerson "" "S" 200) == None)
test> invalidNameAsOptionIsNone = check (toOptional '(validateName "") == None)
test> invalidSurnameAsOptionIsNone = check (toOptional '(validateSurname "X") == None)
test> invalidAgeAsOptionIsNone = check (toOptional '(validateAge 200) == None)
test> validNameAsOptionIsSome = check (toOptional '(validateName "Fred") == Some "Fred")
test> validSurnameAsOptionIsSome = check (toOptional '(validateSurname "Smith") == Some "Smith")
test> validAgeAsOptionIsSome = check (toOptional '(validateAge 35) == Some 35)
From Scala Monadic Effects to Unison Algebraic Effects

More Related Content

What's hot

Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also Programs
Philip Schwarz
 
Functional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 WayFunctional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 Way
Debasish Ghosh
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in Scala
Vladimir Kostyukov
 
The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...
Philip Schwarz
 
The lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsThe lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of tests
Scott Wlaschin
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and Cats
Philip Schwarz
 
The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and Fold
Philip Schwarz
 
One Monad to Rule Them All
One Monad to Rule Them AllOne Monad to Rule Them All
One Monad to Rule Them All
John De Goes
 
Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional World
Debasish Ghosh
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
Philip Schwarz
 
Extensible Eff Applicative
Extensible Eff ApplicativeExtensible Eff Applicative
Extensible Eff Applicative
Sanshiro Yoshida
 
Methods in C#
Methods in C#Methods in C#
Methods in C#
Prasanna Kumar SM
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022
Alexander Ioffe
 
Python functions
Python functionsPython functions
Python functions
Aliyamanasa
 
Point free or die - tacit programming in Haskell and beyond
Point free or die - tacit programming in Haskell and beyondPoint free or die - tacit programming in Haskell and beyond
Point free or die - tacit programming in Haskell and beyond
Philip Schwarz
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Jorge Vásquez
 
Algebraic Thinking for Evolution of Pure Functional Domain Models
Algebraic Thinking for Evolution of Pure Functional Domain ModelsAlgebraic Thinking for Evolution of Pure Functional Domain Models
Algebraic Thinking for Evolution of Pure Functional Domain Models
Debasish Ghosh
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Philip Schwarz
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
RichardWarburton
 
Ad hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsAd hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and Cats
Philip Schwarz
 

What's hot (20)

Tagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also ProgramsTagless Final Encoding - Algebras and Interpreters and also Programs
Tagless Final Encoding - Algebras and Interpreters and also Programs
 
Functional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 WayFunctional Domain Modeling - The ZIO 2 Way
Functional Domain Modeling - The ZIO 2 Way
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in Scala
 
The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...
 
The lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsThe lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of tests
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and Cats
 
The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and Fold
 
One Monad to Rule Them All
One Monad to Rule Them AllOne Monad to Rule Them All
One Monad to Rule Them All
 
Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional World
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
 
Extensible Eff Applicative
Extensible Eff ApplicativeExtensible Eff Applicative
Extensible Eff Applicative
 
Methods in C#
Methods in C#Methods in C#
Methods in C#
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022
 
Python functions
Python functionsPython functions
Python functions
 
Point free or die - tacit programming in Haskell and beyond
Point free or die - tacit programming in Haskell and beyondPoint free or die - tacit programming in Haskell and beyond
Point free or die - tacit programming in Haskell and beyond
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorld
 
Algebraic Thinking for Evolution of Pure Functional Domain Models
Algebraic Thinking for Evolution of Pure Functional Domain ModelsAlgebraic Thinking for Evolution of Pure Functional Domain Models
Algebraic Thinking for Evolution of Pure Functional Domain Models
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
 
Ad hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsAd hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and Cats
 

Similar to From Scala Monadic Effects to Unison Algebraic Effects

Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
Philip Schwarz
 
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
Philip Schwarz
 
JavaScript: Core Part
JavaScript: Core PartJavaScript: Core Part
JavaScript: Core Part
維佋 唐
 
Types For Frontend Developers
Types For Frontend DevelopersTypes For Frontend Developers
Types For Frontend Developers
Jesse Williamson
 
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
Philip Schwarz
 
On Scala Slides - OSDC 2009
On Scala Slides - OSDC 2009On Scala Slides - OSDC 2009
On Scala Slides - OSDC 2009
Michael Neale
 
Jquery 1
Jquery 1Jquery 1
lec3forma.pptx
lec3forma.pptxlec3forma.pptx
lec3forma.pptx
Faiz Zeya
 
Applicative Functor - Part 2
Applicative Functor - Part 2Applicative Functor - Part 2
Applicative Functor - Part 2
Philip Schwarz
 
String Matching with Finite Automata,Aho corasick,
String Matching with Finite Automata,Aho corasick,String Matching with Finite Automata,Aho corasick,
String Matching with Finite Automata,Aho corasick,
8neutron8
 
Chapter 04
Chapter 04Chapter 04
Chapter 04
Terry Yoast
 
Python_Functions.pdf
Python_Functions.pdfPython_Functions.pdf
Python_Functions.pdf
MikialeTesfamariam
 
Java input Scanner
Java input Scanner Java input Scanner
Java input Scanner
Huda Alameen
 
Logo tutorial
Logo tutorialLogo tutorial
Logo tutorial
Singajogi Sudhakar
 
DIWE - Programming with JavaScript
DIWE - Programming with JavaScriptDIWE - Programming with JavaScript
DIWE - Programming with JavaScript
Rasan Samarasinghe
 
PLSQL Note
PLSQL NotePLSQL Note
PLSQL Note
Arun Sial
 
Compiler Design File
Compiler Design FileCompiler Design File
Compiler Design File
Archita Misra
 
Introduction to Java
Introduction to JavaIntroduction to Java
Introduction to Java
Ashita Agrawal
 
Introduction to java script
Introduction to java scriptIntroduction to java script
Introduction to java script
DivyaKS12
 
My-Selected-Project-Proposal
My-Selected-Project-ProposalMy-Selected-Project-Proposal
My-Selected-Project-Proposal
Bhautik Mavani
 

Similar to From Scala Monadic Effects to Unison Algebraic Effects (20)

Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
Game of Life - Polyglot FP - Haskell - Scala - Unison - Part 3
 
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
Game of Life - Polyglot FP - Haskell, Scala, Unison - Part 2 - with minor cor...
 
JavaScript: Core Part
JavaScript: Core PartJavaScript: Core Part
JavaScript: Core Part
 
Types For Frontend Developers
Types For Frontend DevelopersTypes For Frontend Developers
Types For Frontend Developers
 
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
Function Applicative for Great Good of Palindrome Checker Function - Polyglot...
 
On Scala Slides - OSDC 2009
On Scala Slides - OSDC 2009On Scala Slides - OSDC 2009
On Scala Slides - OSDC 2009
 
Jquery 1
Jquery 1Jquery 1
Jquery 1
 
lec3forma.pptx
lec3forma.pptxlec3forma.pptx
lec3forma.pptx
 
Applicative Functor - Part 2
Applicative Functor - Part 2Applicative Functor - Part 2
Applicative Functor - Part 2
 
String Matching with Finite Automata,Aho corasick,
String Matching with Finite Automata,Aho corasick,String Matching with Finite Automata,Aho corasick,
String Matching with Finite Automata,Aho corasick,
 
Chapter 04
Chapter 04Chapter 04
Chapter 04
 
Python_Functions.pdf
Python_Functions.pdfPython_Functions.pdf
Python_Functions.pdf
 
Java input Scanner
Java input Scanner Java input Scanner
Java input Scanner
 
Logo tutorial
Logo tutorialLogo tutorial
Logo tutorial
 
DIWE - Programming with JavaScript
DIWE - Programming with JavaScriptDIWE - Programming with JavaScript
DIWE - Programming with JavaScript
 
PLSQL Note
PLSQL NotePLSQL Note
PLSQL Note
 
Compiler Design File
Compiler Design FileCompiler Design File
Compiler Design File
 
Introduction to Java
Introduction to JavaIntroduction to Java
Introduction to Java
 
Introduction to java script
Introduction to java scriptIntroduction to java script
Introduction to java script
 
My-Selected-Project-Proposal
My-Selected-Project-ProposalMy-Selected-Project-Proposal
My-Selected-Project-Proposal
 

More from Philip Schwarz

Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
Philip Schwarz
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
Philip Schwarz
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Philip Schwarz
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
Philip Schwarz
 
Folding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a seriesFolding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a series
Philip Schwarz
 
Folding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a seriesFolding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a series
Philip Schwarz
 
Folding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a seriesFolding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a series
Philip Schwarz
 
Scala Left Fold Parallelisation - Three Approaches
Scala Left Fold Parallelisation- Three ApproachesScala Left Fold Parallelisation- Three Approaches
Scala Left Fold Parallelisation - Three Approaches
Philip Schwarz
 
Fusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with ViewsFusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with Views
Philip Schwarz
 
A sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in ScalaA sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in Scala
Philip Schwarz
 
A sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in ScalaA sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in Scala
Philip Schwarz
 
A sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in ScalaA sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in Scala
Philip Schwarz
 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets Cats
Philip Schwarz
 
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Philip Schwarz
 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Philip Schwarz
 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Philip Schwarz
 
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
Philip Schwarz
 
Jordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axiomsJordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axioms
Philip Schwarz
 
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Philip Schwarz
 
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Philip Schwarz
 

More from Philip Schwarz (20)

Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
Folding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a seriesFolding Cheat Sheet #3 - third in a series
Folding Cheat Sheet #3 - third in a series
 
Folding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a seriesFolding Cheat Sheet #2 - second in a series
Folding Cheat Sheet #2 - second in a series
 
Folding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a seriesFolding Cheat Sheet #1 - first in a series
Folding Cheat Sheet #1 - first in a series
 
Scala Left Fold Parallelisation - Three Approaches
Scala Left Fold Parallelisation- Three ApproachesScala Left Fold Parallelisation- Three Approaches
Scala Left Fold Parallelisation - Three Approaches
 
Fusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with ViewsFusing Transformations of Strict Scala Collections with Views
Fusing Transformations of Strict Scala Collections with Views
 
A sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in ScalaA sighting of traverse_ function in Practical FP in Scala
A sighting of traverse_ function in Practical FP in Scala
 
A sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in ScalaA sighting of traverseFilter and foldMap in Practical FP in Scala
A sighting of traverseFilter and foldMap in Practical FP in Scala
 
A sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in ScalaA sighting of sequence function in Practical FP in Scala
A sighting of sequence function in Practical FP in Scala
 
N-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets CatsN-Queens Combinatorial Puzzle meets Cats
N-Queens Combinatorial Puzzle meets Cats
 
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...
 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
 
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
Nat, List and Option Monoids -from scratch -Combining and Folding -an exampleNat, List and Option Monoids -from scratch -Combining and Folding -an example
Nat, List and Option Monoids - from scratch - Combining and Folding - an example
 
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...
 
Jordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axiomsJordan Peterson - The pursuit of meaning and related ethical axioms
Jordan Peterson - The pursuit of meaning and related ethical axioms
 
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
Defining filter using (a) recursion (b) folding (c) folding with S, B and I c...
 
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...Defining filter using (a) recursion (b) folding with S, B and I combinators (...
Defining filter using (a) recursion (b) folding with S, B and I combinators (...
 

Recently uploaded

Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Julian Hyde
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
ToXSL Technologies
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
dakas1
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
mz5nrf0n
 
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian CompaniesE-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
Quickdice ERP
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
rodomar2
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
Lecture 2 - software testing SE 412.pptx
Lecture 2 - software testing SE 412.pptxLecture 2 - software testing SE 412.pptx
Lecture 2 - software testing SE 412.pptx
TaghreedAltamimi
 
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
safelyiotech
 
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling ExtensionsUI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
Peter Muessig
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 
Preparing Non - Technical Founders for Engaging a Tech Agency
Preparing Non - Technical Founders for Engaging  a  Tech AgencyPreparing Non - Technical Founders for Engaging  a  Tech Agency
Preparing Non - Technical Founders for Engaging a Tech Agency
ISH Technologies
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
Massimo Artizzu
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
Patrick Weigel
 
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
Bert Jan Schrijver
 
What next after learning python programming basics
What next after learning python programming basicsWhat next after learning python programming basics
What next after learning python programming basics
Rakesh Kumar R
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
Rakesh Kumar R
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
sjcobrien
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
Green Software Development
 

Recently uploaded (20)

Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
 
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian CompaniesE-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
Lecture 2 - software testing SE 412.pptx
Lecture 2 - software testing SE 412.pptxLecture 2 - software testing SE 412.pptx
Lecture 2 - software testing SE 412.pptx
 
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
Safelyio Toolbox Talk Softwate & App (How To Digitize Safety Meetings)
 
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling ExtensionsUI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 
Preparing Non - Technical Founders for Engaging a Tech Agency
Preparing Non - Technical Founders for Engaging  a  Tech AgencyPreparing Non - Technical Founders for Engaging  a  Tech Agency
Preparing Non - Technical Founders for Engaging a Tech Agency
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
 
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
J-Spring 2024 - Going serverless with Quarkus, GraalVM native images and AWS ...
 
What next after learning python programming basics
What next after learning python programming basicsWhat next after learning python programming basics
What next after learning python programming basics
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
 

From Scala Monadic Effects to Unison Algebraic Effects

  • 1. from Scala monadic effects to Unison algebraic effects Introduction to Unison’s algebraic effects (abilities) go from a small Scala program based on the Option monad to a Unison program based on the Abort ability - inspired by, and part based on, a talk by Runar Bjarnason - Runar Bjarnason @runarorama @philip_schwarzby https://www.slideshare.net/pjschwarz
  • 2. We start off by looking at how Runar Bjarnason explains Unison’s effect system in his talk Introduction to the Unison programming language. @philip_schwarz
  • 3. Another thing we really wanted to be thoughtful about was unison’s effect system, because I mean, let’s be honest, monads are awkward. I came out and said it, monads are awkward, they come with a syntactic overhead as well as a cognitive overhead, like, you know, a lot of the time you spend your time trying to figure out how to lift this thing into the monad you want, in which order is my monad transformer stack supposed to be and things like that. Runar Bjarnason Cofounder, Unison Computing. Author of Functional Programming in Scala. @runarorama Lambda World 2018 - Introduction to the Unison programming language - Rúnar Bjarnason
  • 4. So Unison uses what’s sometimes known as algebraic effects. We modeled our effect system in a language called Frank, which is detailed in this paper, which is called Do Be Do Be Do, by Sam Lindley, Conor McBride and Craig McLaughlin, and Frank calls these abilities, rather than effects, and so we do that, we call them abilities. So here is a simple State ability. This is the ability to put and get some global state of type s. Abilities are introduced with the ability keyword and this defines two functions, put and get. put takes some state of type s and it returns unit with the State ability attached to it, and then get will give you that s, given that you have the State ability. When we see a thing like this in curly braces, it means this requires that ability. So put requires the State ability and get also requires the State ability. So this is very similar to an Algebraic Data Type where you are defining the type State, this ability type, and these are the constructors of the type: put and get. Runar Bjarnason @runarorama
  • 5. So for example we can write effectful functions push and pop on a global stack. So given that the state is a stack, then we have the ability to manipulate some state that is a list of as: we can pop and push. So note that there is no monadic plumbing here. These are just code blocks. And so to pop, we get the stack, we drop one element from the stack, we put that and then we get the head of the stack. So that’s pop. And then push, we just say, cons a onto the front of whatever we get, and put that. The reason why the pop is quoted is that only computations can have effects, not values. So once you have computed a value, you can no longer have effects. So the quoting is just a nullary function that returns whatever this evaluates to. There is no applicative syntax or anything like that, because we are actually overloading the function application syntax. So in unison applicative programming is the default. We chose that as a design constraint. Runar Bjarnason @runarorama
  • 6. The types will ensure that you can’t go wrong here, that you are not talking about the wrong thing. So for example whereas in Scala you might say a comes from x, b comes from y and then c comes from z, and then you want to do f of a, b and c. In Unison you just say f x y z and it will figure that out. It will do the pulling out. It will do all the effects. So whereas in Haskell you might have to say x bind lambda of f of a and then bind g, in Unison you just say g of f of x. So that’s kind of nice, there is a low syntactic overhead to this and there is a low cognitive overhead to this, for the programmer. Runar Bjarnason @runarorama
  • 7. So the programmer can just use our pop and push and write a little program that pushes and pops the stack using our State ability. So given that we have the ability to manipulate some state of type list of Nat, we can write a stack program. a is the head of the stack, we pop the stack and now we have mutated the stack and then if a is five then push it back, otherwise push 3 and 8. So this looks like a little imperative program but it is actually a purely functional program. There are no side effects here but there is also no visible effect plumbing. Runar Bjarnason @runarorama
  • 8. So then to handle the State ability, to make it actually do something, we write a handler using a handle keyword. This here is a pure handler for the State ability and we can use that handler, at the bottom, the runStack thing uses that handler to run the stackProgram with some initial state which is [5,4,3,2,1]. Normally this kind of stuff would be hidden away in library code. Most programmers will not be writing their own handlers but if you have your own set of abilities, you’ll be able to write your handlers. Runar Bjarnason @runarorama
  • 9. So here the definition of state, the expression here at the bottom is like handle h of s in bang c, where the exclamation sign means force this computation. c is some quoted computation, you can see that it is quoted in the type, it is something of type {State s} a, and then I am saying, force that, actually evaluate it, but handle using the handler h, or h of s, where s is the initial state coming in, it is that [5,4,3,2,1] thing. And then the definition of h is just above and it proceeds by pattern matching on the constructors of the ability. Runar Bjarnason @runarorama
  • 10. If the call was to a get, then we end up in that case and what we get out of that pattern is k, a continuation for the program, the rest of the program, and what is expected is that I pass the current state to k, that is we allow the program to continue with the current state, so if there is a get then I call k of s and this is a recursive definition, I keep trying to handle if there is any more state manipulation going on, it is actually calling the handler again, because k of s might also need access to the state ability. And then to put, we get a state that somebody wanted to put and we get the continuation of the program and we say well, handle that using the state and then continue by passing the unit to the continuation. And then in the pure case, when there is no effect, we just return the value that we ended up with. Runar Bjarnason @runarorama Runar Bjarnason @runarorama
  • 11. The next slide has all of the state code shown by Runar. @philip_schwarz
  • 12.
  • 13. When I went to run that code, I made the following changes to it: • I updated it to reflect some minor changes to the Unison language which have occurred since Runar gave the talk. • Since the pop function returns Optional a, I changed stackProgram so that it doesn’t expect pop to return an a. • Since runStack returns a stack, i.e. a list of numbers, I changed stackProgram to also return a stack. • I changed a bit the pushing and popping that stackProgram does, and added automated tests to visualise the effect of that logic on a stack. • Since the pop function returns a quoted computation, I prefixed invocations of pop with the exclamations sign, to force the execution of the computations. • I prefixed usages of put and get with State. • I added the List.head function that pop uses See the next slide for the resulting code
  • 14. state : s -> '({State s} a) -> a state s c = h s cases { State.get -> k } -> handle k s with h s { State.put s -> k } -> handle k () with h s { a } -> a handle !c with h s ability State s where put : s -> {State s} () get : {State s} s pop : '{State [a]} (Optional a) pop = 'let stack = State.get State.put (drop 1 stack) head stack push : a -> {State [a]} () push a = State.put (cons a State.get) stackProgram : '{State [Nat]} [Nat] stackProgram = 'let top = !pop match top with None -> push 0 push 1 push 2 Some 5 -> !pop push 5 Some n -> !pop !pop push n push (n + n) State.get List.head : [a] -> Optional a List.head a = List.at 0 a use List head runStack : [Nat] runStack = state [5,4,3,2,1] stackProgram test> topIsFive = check(state [5,4,3,2,1] stackProgram == [5,3,2,1]) test> topIsNotFive = check(state [6,5,4,3,2,1] stackProgram == [12,6,3,2,1]) test> topIsMissing = check(state [] stackProgram == [2,1,0]) > runStack ⧩ [5, 3, 2, 1]
  • 15. To help understand how the state function works, I made the following changes to it: • make the type of the h function explicit • rename the h function to handler • rename c to computation • rename k to continuation • break ‘each handle … with …’ line into two lines The next slide shows the state function before and after the changes and the slide after that shows the whole code again after the changes to the state function.
  • 16. state : s -> '({State s} a) -> a state s c = h s cases { State.get -> k } -> handle k s with h s { State.put s -> k } -> handle k () with h s { a } -> a handle !c with h s state : s -> '({State s} a) -> a state s computation = handler : s -> Request {State s} a -> a handler s cases { State.get -> continuation } -> handle continuation s with handler s { State.put s -> continuation } -> handle continuation () with handler s { a } -> a handle !computation with handler s In https://www.unisonweb.org/docs/language-reference :we read the following: .base.Request is the constructor of requests for abilities. A type Request A T is the type of values received by ability handlers for the ability A where the current continuation requires a value of type T. So on the right we see the state handler function taking first a state, and then a Request {State s} a, i.e. a request for the State ability where the continuation requires a value of type a. @philip_schwarz
  • 17. state : s -> '({State s} a) -> a state s computation = handler : s -> Request {State s} a -> a handler s cases { State.get -> continuation } -> handle continuation s with handler s { State.put s -> continuation } -> handle continuation () with handler s { a } -> a handle !computation with handler s ability State s where put : s -> {State s} () get : {State s} s stackProgram : '{State [Nat]} [Nat] stackProgram = 'let top = !pop match top with None -> push 0 push 1 push 2 Some 5 -> !pop push 5 Some n -> !pop !pop push n push (n + n) State.get > runStack ⧩ [5, 3, 2, 1] List.head : [a] -> Optional a List.head a = List.at 0 a use List head test> topIsFive = check(state [5,4,3,2,1] stackProgram == [5,3,2,1]) test> topIsNotFive = check(state [6,5,4,3,2,1] stackProgram == [12,6,3,2,1]) test> topIsMissing = check(state [] stackProgram == [2,1,0]) runStack : [Nat] runStack = state [5,4,3,2,1] stackProgram pop : '{State [a]} (Optional a) pop = 'let stack = State.get State.put (drop 1 stack) head stack push : a -> {State [a]} () push a = State.put (cons a State.get)
  • 18. Now back to Runar’s talk for one more fact about functional effects in Unison.
  • 19. And yes, you can still use monads, if you want. You don’t have to use this ability stuff. You can still use monads and it will work just fine. Runar Bjarnason @runarorama
  • 20. Earlier on Runar showed us a comparison between a Scala monadic for comprehension and a Unison plain function invocation that instead relied on an ability. He also showed us a comparison between a Haskell expression using the bind function (flatMap in Scala) and a Unison plain function invocation that again relied on an ability. In the rest of this slide deck, we are going to do two things: • Firstly, we are going to look at an example of how functional effects look like in Unison when we use monadic effects rather than algebraic effects. i.e we are going to use a monad rather than an ability. We are going to do that by starting with a very small Scala program that uses a monad and then translating the program into the Unison equivalent. • Secondly, we want to see another Unison example of implementing a functional effect using an ability, so we are going to take the above Unison program and convert it so that it uses an ability rather than a monad. In the process we’ll be making the following comparisons: The state functional effect is not the easiest to understand, so to aid our understanding, the program we’ll be looking at is simply going to do validation using the functional effect of optionality. -- Scala program that -- uses the Option monad for { a <- x b <- y c <- z } yield f(a b c) -- Unison program that -- uses the Abort ability ??? -- Scala program that -- uses the Option monad x flatMap { a => y flatMap { b => z map { c => f(a,b,c) } } } -- Unison program that -- uses the Optional monad ??? @philip_schwarz
  • 21. The Scala program that we’ll be translating into Unison. Is on the next slide. @philip_schwarz
  • 22. sealed trait Option[+A] { def map[B](f: A => B): Option[B] = this flatMap { a => Some(f(a)) } def flatMap[B](f: A => Option[B]): Option[B] = this match { case Some(a) => f(a) case None => None } } case object None extends Option[Nothing] case class Some[+A](get: A) extends Option[A] def validateName(name: String): Option[String] = if (name.size > 1 && name.size < 15) Some(name) else None def validateSurname(surname: String): Option[String] = if (surname.size > 1 && surname.size < 20) Some(surname) else None def validateAge(age: Int): Option[Int] = if (age > 0 && age < 112) Some(age) else None case class Person(name: String, surname: String, age: Int) def createPerson(name: String, surname: String, age: Int): Option[Person] = for { aName <- validateName(name) aSurname <- validateSurname(surname) anAge <- validateAge(age) } yield Person(aName, aSurname, anAge) val people: String = potentialPeople .foldLeft("")(((text,person) => text + "n" + toText(person))) assert( people == "nPerson(Fred,Smith,35)nNonenNonenNone" ) println(people) è Person(Fred,Smith,35) None None None val potentialPeople = List( createPerson("Fred", "Smith", 35), createPerson( "x", "Smith", 35), createPerson("Fred", "", 35), createPerson("Fred", "Smith", 0) ) def toText(option: Option[Person]): String = option match { case Some(person) => person.toString case None => "None" }
  • 23. def validateName(name: String): Option[String] = if (name.size > 1 && name.size < 15) Some(name) else None def validateSurname(surname: String): Option[String] = if (surname.size > 1 && surname.size < 20) Some(surname) else None def validateAge(age: Int): Option[Int] = if (age > 0 && age < 112) Some(age) else None validateName : Text -> Optional Text validateName name = if (size name > 1) && (size name < 15) then Some name else None validateSurname : Text -> Optional Text validateSurname surname = if (size surname > 1) && (size surname < 20) then Some surname else None validateAge : Nat -> Optional Nat validateAge age = if (age > 0) && (age < 112) then Some age else None Let’s begin by translating the validation functions. The Unison equivalent of Scala’ s Option is the Optional type.
  • 24. as a minor aside, if we were using the Scala built-in Option type then we would have the option of rewriting code like this if (age > 0 && age < 112) Some(age) else None as follows Option.when(age > 0 && age < 112)(age) or alternatively as follows Option.unless(age <= 0 || age > 112)(age)
  • 25. sealed trait Option[+A] { def map[B](f: A => B): Option[B] = this flatMap { a => Some(f(a)) } def flatMap[B](f: A => Option[B]): Option[B] = this match { case Some(a) => f(a) case None => None } } case object None extends Option[Nothing] case class Some[+A](get: A) extends Option[A] type base.Optional a = None | Some a base.Optional.map : (a -> b) -> Optional a -> Optional b base.Optional.map f = cases None -> None Some a -> Some (f a) base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b base.Optional.flatMap f = cases None -> None Some a -> f a use .base.Optional map flatMap Now that we have the validation functions in place, let’s look at the translation of the functional effect of optionality. On the left hand side we have a handrolled Scala Option with map defined in terms of flatMap, and on the right hand side we have the Unison predefined Optional type and its predefined map and flatMap functions.
  • 26. type Person = { name: Text, surname: Text, age: Nat } use .base.Optional map flatMap createPerson : Text -> Text -> Nat -> Optional Person createPerson name surname age = flatMap (aName -> flatMap (aSurname -> map (anAge -> Person.Person aName aSurname anAge )(validateAge age) )(validateSurname surname) )(validateName name) case class Person(name: String, surname: String, age: Int) def createPerson(name : String, surname: String, age: Int): Option[Person] = for { aName <- validateName(name) aSurname <- validateSurname(surname) anAge <- validateAge(age) } yield Person(aName, aSurname, anAge) Now that we have the map and flatMap functions in place, let’s look at the translation of the Scala for comprehension into Unison. We are implementing the functional effect of optionality using a monad, so while in Scala we can use the syntactic sugar of a for comprehension, in Unison there is no equivalent of the for comprehension (AFAIK) and so we are having to use an explicit chain of flatMap and map.
  • 27. type Person = { name: Text, surname: Text, age: Nat } use .base.Optional map flatMap createPerson : Text -> Text -> Nat -> Optional Person createPerson name surname age = flatMap (aName -> flatMap (aSurname -> map (anAge -> Person.Person aName aSurname anAge )(validateAge age) )(validateSurname surname) )(validateName name) case class Person(name: String, surname: String, age: Int) def createPerson(name : String, surname: String, age: Int): Option[Person] = validateName(name) flatMap { aName => validateSurname(surname) flatMap { aSurname => validateAge(age) map { anAge => Person(aName, aSurname, anAge) } } } Here is the same comparison as on the previous slide but with the Scala code explicitly using map and flatMap. @philip_schwarz
  • 28. val people: String = potentialPeople .foldLeft("")(((text,person) => text + "n" + toText(person))) assert( people == "nPerson(Fred,Smith,35)nNonenNonenNone" ) people : Text people = foldl (text person -> text ++ "n" ++ (toText person)) "" potentialPeople peopleTest = check ( people == "nPerson(Fred,Smith,35)nNonenNonenNone" ) val potentialPeople: List[Option[Person]] = List( createPerson("Fred", "Smith", 35), createPerson( "x", "Smith", 35), createPerson("Fred", "", 35), createPerson("Fred", "Smith", 0) ) def toText(option: Option[Person]): String = option match { case Some(person) => person.toString case None => "None" } potentialPeople: [Optional Person] potentialPeople = [ (createPerson "Fred" "Smith" 35), (createPerson "x" "Smith" 35), (createPerson "Fred" "" 35), (createPerson "Fred" "Smith" 0) ] toText : Optional Person -> Text toText = cases Some person -> Person.toText person None -> "None” Person.toText : Person -> Text Person.toText person = match person with Person.Person name surname age -> "Person(" ++ name ++ "," ++ surname ++ "," ++ Text.toText(age) ++ ")" Here we translate the rest of the program.
  • 29. See the next slide for the Unison translation of the whole Scala program. @philip_schwarz
  • 30. type Person = { name: Text, surname: Text, age: Nat } use .base.Optional map flatMap createPerson : Text -> Text -> Nat -> Optional Person createPerson name surname age = flatMap (aName -> flatMap (aSurname -> map (anAge -> Person.Person aName aSurname anAge )(validateAge age) )(validateSurname surname) )(validateName name) people : Text people = foldl (text person -> text ++ "n" ++ (toText person)) "" potentialPeople peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone") type base.Optional a = None | Some a base.Optional.map : (a -> b) -> Optional a -> Optional b base.Optional.map f = cases None -> None Some a -> Some (f a) base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b base.Optional.flatMap f = cases None -> None Some a -> f a validateName : Text -> Optional Text validateName name = if (size name > 1) && (size name < 15) then Some name else None validateSurname : Text -> Optional Text validateSurname surname = if (size surname > 1) && (size surname < 20) then Some surname else None validateAge : Nat -> Optional Nat validateAge age = if (age > 0) && (age < 112) then Some age else None toText : Optional Person -> Text toText = cases Some person -> Person.toText person None -> "None” Person.toText : Person -> Text Person.toText person = match person with Person.Person name surname age -> "Person(" ++ name ++ "," ++ surname ++ "," ++ Text.toText(age) ++ ")" potentialPeople: [Optional Person] potentialPeople = [(createPerson "Fred" "Smith" 35), (createPerson "x" "Smith" 35), (createPerson "Fred" "" 35), (createPerson "Fred" "Smith" 0)]
  • 31. On the next slide we look at some simple automated tests for the Unison program.
  • 32. test> peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone") test> validPersonAsText = check (Person.toText (Person.Person "Fred" "Smith" 35) == "Person(Fred,Smith,35)") test> validPerson = check (createPerson "Fred" "Smith" 35 == Some (Person.Person "Fred" "Smith" 35)) test> noValidPersonWithInvalidName = check (createPerson "F" "Smith" 35 == None) test> noValidPersonWithInvalidSurname = check (createPerson "Fred" "" 35 == None) test> noValidPersonWithInvalidAge = check (createPerson "Fred" "Smith" 200 == None) test> noValidPersonWithInvalidNameSurnameAndAge = check (createPerson "" "S" 200 == None) test> validName = check (validateName "Fred" == Some "Fred") test> validSurname = check (validateSurname "Smith" == Some "Smith") test> validAge = check (validateAge 35 == Some 35) test> noInvalidName = check (validateName "" == None) test> noInvalidSurname = check (validateSurname "X" == None) test> noInvalidAge = check (validateAge 200 == None)
  • 33. As we have seen, the Unison program currently implements the functional effect of optionality using the Optional monad. What we are going to do next is improve that program, make it easier to understand, by changing it so that it implements the effect of optionality using an ability (algebraic effect) called Abort.
  • 34. Abort.toOptional : '{g, Abort} a -> {g} Optional a Abort.toOptional computation = handler : Request {Abort} a -> Optional a handler = cases { a } -> Some a { abort -> _ } -> None handle !computation with handler state : s -> '({State s} a) -> a state s computation = handler : s -> Request {State s} a -> a handler s cases { State.get -> continuation } -> handle continuation s with handler s { State.put s -> continuation } -> handle continuation () with handler s { a } -> a handle !computation with handler s ability State s where put : s -> {State s} () get : {State s} s ability Abort where abort : {Abort} a Let’s begin by looking at the Abort ability. Although it is a predefined ability, on this slide I have refactored the original a bit so that we can better compare it with the State ability that we saw earlier in Runar’s code. In later slides I am going to revert to the predefined version of the ability, which being split into two functions, offers different advantages. The Abort ability is much simpler than the State ability, that’s why I think it could be a good first example of using abilities. To help understand how the handler of the Abort ability works, in the next slide we look at some relevant documentation from a similar Abort ability in the Unison language reference. By the way, it looks like that g in the the signature of the toOptional function somehow caters for potentially multiple abilities being in play at the same time, but we’ll just ignore that aspect because it is out of scope for our purposes.
  • 35. ability Abort where aborting : () -- Returns `a` immediately if the -- program `e` calls `abort` abortHandler : a -> Request Abort a -> a abortHandler a = cases { Abort.aborting -> _ } -> a { x } -> x p : Nat p = handle x = 4 Abort.aborting x + 2 with abortHandler 0 A handler can choose to call the continuation or not, or to call it multiple times. For example, a handler can ignore the continuation in order to handle an ability that aborts the execution of the program. The program p evaluates to 0. If we remove the Abort.aborting call, it evaluates to 6. Note that although the ability constructor is given the signature aborting : (), its actual type is {Abort} (). The pattern { Abort.aborting -> _ } matches when the Abort.aborting call in p occurs. This pattern ignores its continuation since it will not invoke it (which is how it aborts the program). The continuation at this point is the expression _ -> x + 2. The pattern { x } matches the case where the computation is pure (makes no further requests for the Abort ability and the continuation is empty). A pattern match on a Request is not complete unless this case is handled. from https://www.unisonweb.org/docs/language-reference As I said on the previous slide, while the above Abort ability is similar to the one we are going to use, it is not identical. e.g. this handler returns an a rather than an Optional a. The reason why we are looking at this example is because the patterns in the handler are identical and the above explanations are also useful for the Abort ability that we are going to use.
  • 36. type base.Optional a = None | Some a base.Optional.map : (a -> b) -> Optional a -> Optional b base.Optional.map f = cases None -> None Some a -> Some (f a) base.Optional.flatMap : (a -> Optional b) -> Optional a -> Optional b base.Optional.flatMap f = cases None -> None Some a -> f a type base.Optional a = None | Some a ability Abort where abort : {Abort} a Abort.toOptional.handler : Request {Abort} a -> Optional a Abort.toOptional.handler = cases { a } -> Some a { abort -> _ } -> None Abort.toOptional : '{g, Abort} a -> {g} Optional a Abort.toOptional a = handle !a with toOptional.handler So here on the left are the map and flatMap functions that the program currently uses to implement the functional effect of optionality and on the right is the predefined Abort ability that the program is now going to use instead. @philip_schwarz
  • 37. validateName : Text -> Optional Text validateName name = if (size name > 1) && (size name < 15) then Some name else None validateSurname : Text -> Optional Text validateSurname surname = if (size surname > 1) && (size surname < 20) then Some surname else None validateAge : Nat -> Optional Nat validateAge age = if (age > 0) && (age < 112) then Some age else None validateName : Text -> { Abort } Text validateName name = if (size name > 1) && (size name < 15) then name else abort validateSurname : Text -> { Abort } Text validateSurname surname = if (size surname > 1) && (size surname < 20) then surname else abort validateAge : Nat -> { Abort } Nat validateAge age = if (age > 0) && (age < 112) then age else abort Here we refactor the validation functions and on the next slide we refactor the majority of the rest of the program, leaving the most interesting bit of refactoring for the slide after that.
  • 38. people : Text people = foldl (text person -> text ++ "n" ++ (toText person)) "" potentialPeople peopleTest = check ( people == "nPerson(Fred,Smith,35)nNonenNonenNone” ) people : Text people = foldl (text person -> text ++ "n" ++ toText (toOptional person)) "" potentialPeople peopleTest = check ( people == "nPerson(Fred,Smith,35)nNonenNonenNone” ) potentialPeople : ['{Abort} Person] potentialPeople = [ '(createPerson "Fred" "Smith" 35), '(createPerson "x" "Smith" 35), '(createPerson "Fred" "" 35), '(createPerson "Fred" "Smith" 0) ] potentialPeople: [Optional Person] potentialPeople = [ (createPerson "Fred" "Smith" 35), (createPerson "x" "Smith" 35), (createPerson "Fred" "" 35), (createPerson "Fred" "Smith" 0) ] toText : Optional Person -> Text toText = cases Some person -> Person.toText person None -> "None” Person.toText : Person -> {} Text Person.toText person = match person with Person.Person name surname age -> "Person(" ++ name ++ "," ++ surname ++ "," ++ Text.toText(age) ++ ")" toText : Optional Person -> Text toText = cases Some person -> Person.toText person None -> "None” Person.toText : Person -> {} Text Person.toText person = match person with Person.Person name surname age -> "Person(" ++ name ++ "," ++ surname ++ "," ++ Text.toText(age) ++ ")"
  • 39. type Person = { name: Text, surname: Text, age: Nat } createPerson : Text -> Text -> Nat -> Optional Person createPerson name surname age = flatMap (aName -> flatMap (aSurname -> map (anAge -> Person.Person aName aSurname anAge )(validateAge age) )(validateSurname surname) )(validateName name) type Person = { name: Text, surname: Text, age: Nat } createPerson : Text -> Text -> Nat -> { Abort } Person createPerson name surname age = Person.Person (validateName name) (validateSurname surname) (validateAge age) And now the most interesting bit of the refactoring. See how much simpler the createPerson function becomes when the functional effect of optionality is implemented not using a monad but using an ability and its handler.
  • 40. This new version of the createPerson function, which uses an ability (and its associated handler) is not only an improvement over the version that uses a monad but also over the Scala version that itself improves on explicit monadic code by using a for comprehension. -- Scala for { aName <- validateName(name) aSurname <- validateSurname(surname) anAge <- validateAge(age) } yield Person(aName, aSurname, anAge) -- Unison Person.Person (validateName name) (validateSurname surname) (validateAge age) In Unison you just say f x y z and it will figure that out. It will do the pulling out. It will do all the effects. Runar Bjarnason @runarorama
  • 41. See the next slide for all the code of the refactored Unison program. See the subsequent slide for associated automated tests. @philip_schwarz
  • 42. type Person = { name: Text, surname: Text, age: Nat } createPerson : Text -> Text -> Nat -> { Abort } Person createPerson name surname age = Person.Person (validateName name) (validateSurname surname) (validateAge age) validateName : Text -> { Abort } Text validateName name = if (size name > 1) && (size name < 15) then name else abort validateSurname : Text -> { Abort } Text validateSurname surname = if (size surname > 1) && (size surname < 20) then surname else abort validateAge : Nat -> { Abort } Nat validateAge age = if (age > 0) && (age < 112) then age else abort people : Text people = foldl (text person -> text ++ "n" ++ toText (toOptional person)) "" potentialPeople peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone”) toText : Optional Person -> Text toText = cases Some person -> Person.toText person None -> "None” Person.toText : Person -> {} Text Person.toText person = match person with Person.Person name surname age -> "Person(" ++ name ++ "," ++ surname ++ "," ++ Text.toText(age) ++ ")" potentialPeople: ['{Abort} Person] potentialPeople = [ '(createPerson "Fred" "Smith" 35), '(createPerson "x" "Smith" 35), '(createPerson "Fred" "" 35), '(createPerson "Fred" "Smith" 0) ] type base.Optional a = None | Some a ability Abort where abort : {Abort} a Abort.toOptional.handler : Request {Abort} a -> Optional a Abort.toOptional.handler = cases { a } -> Some a { abort -> _ } -> None Abort.toOptional : '{g, Abort} a -> {g} Optional a Abort.toOptional a = handle !a with toOptional.handler
  • 43. test> peopleTest = check (people == "nPerson(Fred,Smith,35)nNonenNonenNone") test> validPersonAsText = check (Person.toText (Person.Person "Fred" "Smith" 35) == "Person(Fred,Smith,35)") test> createValidPerson = check ((toOptional '(createPerson "Fred" "Smith" 35)) == Some((Person.Person "Fred" "Smith" 35))) test> abortAsOptionIsNone = check (toOptional 'abort == None) test> abortExpressionAsOptionIsNone = check (toOptional '(if false then "abc" else abort) == None) test> nonAbortExpressionAsOptionIsSome = check (toOptional '(if true then "abc" else abort) == Some "abc") test> notCreatePersonWithInvalidName = check (toOptional('(createPerson "F" "Smith" 35)) == toOptional('abort)) test> notCreatePersonWithInvalidSurname = check (toOptional('(createPerson "Fred" "" 35)) == toOptional('abort)) test> notCreatePersonWithInvalidAge = check (toOptional('(createPerson "Fred" "Smith" 200)) == toOptional('abort)) test> personWithInvalidNameAsOptionIsNone = check (toOptional '(createPerson "F" "Smith" 35) == None) test> personWithInvalidSurnameAsOptionIsNone = check (toOptional '(createPerson "Fred" "" 35) == None) test> personWithInvalidAgeAsOptionIsNone = check (toOptional '(createPerson "Fred" "Smith" 200) == None) test> personWithAllInvalidFieldsAsOptionIsNone = check (toOptional '(createPerson "" "S" 200) == None) test> invalidNameAsOptionIsNone = check (toOptional '(validateName "") == None) test> invalidSurnameAsOptionIsNone = check (toOptional '(validateSurname "X") == None) test> invalidAgeAsOptionIsNone = check (toOptional '(validateAge 200) == None) test> validNameAsOptionIsSome = check (toOptional '(validateName "Fred") == Some "Fred") test> validSurnameAsOptionIsSome = check (toOptional '(validateSurname "Smith") == Some "Smith") test> validAgeAsOptionIsSome = check (toOptional '(validateAge 35) == Some 35)