Slide 31 de 74

Funções de primeira classe

Uma referência solta, e map
func double(n: Int): Int { return n * 2 }

proc main(): void {
	f := double
	echo "{f(21)} {map([1, 2, 3], double)}"
}
Aplicação parcial, e uma variante com buracos
type Msg {
	Changed { text: Str }
}

func add(a: Int, b: Int): Int { return a + b }

proc main(): void {
	mut n := 5
	addN := add(n, _)       // _ vira o parâmetro; n é tomado agora
	n = 100
	echo addN(10)           // 15 — o que foi fornecido ficou como estava

	make := Msg.Changed(_)  // uma função que constrói a variante
	echo make("digitado")
}
Escrevendo o tipo
func double(n: Int): Int { return n * 2 }
func shout(s: Str): Str { return s + "!" }

func applyTwice(f: func(Int): Int, n: Int): Int {   // como parâmetro
	return f(f(n))
}

func picked(loud: Bool): func(Str): Str {            // como retorno
	return shout
}

proc main(): void {
	func(Int): Int step = double                      // como tipo de variável
	echo applyTwice(step, 5)
	echo picked(true)("hive")
}
Um proc que pede um mutex, como valor
proc grow(names: mut Str[dyn], name: Str): void {
	append(names, name)
}

proc twice(step: proc(mut Str[dyn]): void, names: mut Str[dyn]): void {
	step(names)
	step(names)
}

proc main(): void {
	mut Str[dyn] names = ["ada"]
	twice(grow(_, "bee"), names)   // o mutex continua um buraco
	echo names
}
O que um espaço aceita
proc third(v: Str[3]): void { echo v[2] }
proc anyLength(v: Str[]): void { echo len(v) }

proc callWithThree(f: proc(Str[3]): void): void {
	f(["a", "b", "c"])
}

proc main(): void {
	callWithThree(third)        // vale: o espaço promete três
	callWithThree(anyLength)    // vale: qualquer tamanho aceita três também
	// proc(Str[]): void g = third   // erro de compilação: g pode ser chamado com um
}

Algo chamável é um valor, feito de três jeitos:

  • um nome sozinho: f := double;
  • uma aplicação parcial, uma chamada com buracos _: add(5, _) é uma função de um argumento, cada buraco virando um parâmetro na ordem escrita. O que é fornecido é calculado uma vez, onde está escrito, e guardado: mudar a variável depois não muda nada do que a função guarda;
  • uma variante com buracos: Msg.Changed(_) é uma função que constrói a variante. Msg.Changed sozinho não é um valor.

Seu tipo se escreve como uma declaração sem os nomes — func(Int): Int, proc(mut Str[dyn], Str): void — e serve como parâmetro, tipo de retorno e tipo de uma variável. Um valor func preenche um espaço de proc, nunca o contrário, e uma chamada por qualquer valor de função é conferida contra o tipo dele: quantos argumentos e o que cada um é, por posição, já que os parâmetros de um valor não têm nomes. Uma posição mut em uma aplicação parcial continua sendo um buraco, porque o que é fornecido é guardado como uma cópia que as escritas nunca alcançariam.

Um valor de função só preenche um espaço quando aceita tudo o que as chamadas daquele espaço podem lhe passar: um proc(Str[3]): void não pode ir onde se quer um proc(Str[]): void, já que esse espaço pode ser chamado com qualquer tamanho, enquanto o contrário vale. Uma função nativa, uma chamada da biblioteca e um chamável genérico não podem virar valores de jeito nenhum — embrulhe um deles em um func seu.