Effects let a function ask the surrounding program to do something, such as provide input, stop early, or pause. A handler decides how to answer that request. TalkTalk keeps track of these requests so callers know what a function may ask for.

Declaring and performing an effect

Effect names begin with a tick:

effect 'ask(question: String) -> String

func greeting() -> String {
	let name = 'ask(question: "What is your name?")
	"Hello, " + name
}

Calling an effect is called performing it. A function with no written effect row infers one.

Effect rows

A closed function effect can be written in three forms:

'[] is explicitly pure, 'io names one closed effect, and '[io, panic] names several. '[io, ..] requires io while leaving the rest of the row open for inference.

Effects are part of function values:

The row is a requirement of invoking the value. A closure does not freeze the handler that happened to be active when it was created.

Handling and continuing

#handle installs a handler for the subsequent part of the current block and for calls made from there:

'continue expression resumes the suspended computation, making the expression the result of the effect call. The nearest handler for the same label wins.

A handler that finishes without continuing aborts the handled computation. That makes effects suitable for exceptions as well as resumable operations:

The handler's extent is the code after the #handle statement in the same block, not code before it.

Capturing resumptions

A handler may bind the continuation as an additional final parameter. The resumption is a linear, one-shot value:

effect 'emit(value: Int) -> ()

enum Step {
	case yielded(Int, Resumption<(), Step>)
	case done
}

func generate() -> Step {
	#handle 'emit { value, continuation in Step.yielded(value, continuation) }
	'emit(value: 1)
	Step.done
}

Call resume(k: continuation, value: ()) to continue or cancel(k: continuation) to abandon it. Either operation consumes the resumption, and cancellation deterministically runs cleanup for suspended frames.

Generic effects

Effects may be generic:

effect 'echo<T>(value: T) -> T

Effect rows track separate instantiations. One handler for an effect label covers every generic instantiation in its extent, and the handler body is checked generically.

Built-in host effects

Core uses effects for input and output, memory allocation, task suspension, and panic. unreachable performs the public 'panic effect and never returns. A program may handle it explicitly; otherwise the outer host reports the panic and terminates.

Further reading

TalkTalk's design belongs to the algebraic-effects and handlers family:

The exact choices made by TalkTalk, including generic handlers and invocation-time handler lookup, are recorded in Effect semantics and implementation.