Exercise 1b: ‘Research Stages’
Here’s some F# code shared on the internet that people use to prove how cool the F# language is:
let getResearchStages (project: Project) = // -> option<list<string>>
optional {
let! phase = project.experimentalPhase
let stage = phase.phaseName
let latest = phase.latestExperiment
match latest with
| None -> return [ stage ]
| Some latest -> return [ stage, latest.experimentName ]
}
Your goal is to write a getResearchStages function in Effekt.
Let’s start with the data definitions:
record Project(experimentalPhase: Option[Phase])
record Phase(phaseName: String, latestExperiment: Option[Experiment])
record Experiment(experimentName: String)
Now we’ll need to define the equivalents to optional and !.
Let’s first think about what they do:
optional { ... }- is a handler for some block which can trigger an early exit effect
- if it does, it returns
None() - if not, it wraps the result into a
Some(...)
- if it does, it returns
- is a handler for some block which can trigger an early exit effect
let! x = y(could be written asval x = y.unwrap()in Rust, for example)- is a helper function which tries to unwrap a
Option[T]- if it’s a
Some(...), it returns the contents - if it’s a
None(), then it performs an early exit effect caught byoptional.
- if it’s a
- is a helper function which tries to unwrap a
This provides a possible plan of action:
- define an early exit effect
- define
optionalas a function that receives a block - define the
!helper under some name of your choice - transcribe the F# example above in Effekt:
Go ahead!
// put code described above ^ here:
def getResearchStages(project: Project) = {
<> // TODO
}
Here’s some test data:
def output(res: Option[List[String]]): String = res match {
case None() => "None()"
case Some(results) => "Some(" ++ results.join(", ") ++ ")"
}
def test1() = {
val project1 = Project(None())
getResearchStages(project1).output
}
def test2() = {
val phaseWithExperiment = Phase("Phase with experiment", Some(Experiment("Foucault pendulum")))
val project2 = Project(Some(phaseWithExperiment))
getResearchStages(project2).output
}
def test3() = {
val phaseWithoutExperiment = Phase("Phase without an experiment", None())
val project3 = Project(Some(phaseWithoutExperiment))
getResearchStages(project3).output
}
Here’s where you can call getResearchStages on the three possible inputs:
test1()