Uma função é um código reutilizável que fornece instruções para realizar uma ação, como Dance() ou Sleep() e produz saídas diferentes com base na entrada que você fornece.
As funções fornecem abstração para comportamentos, o que significa que essas funções reutilizáveis ocultam os detalhes de implementação que não são relevantes para outras partes do seu código e que você não precisa ver.
Vamos usar o exemplo de pedir um prato do menu em um restaurante como exemplo de funções e abstração. A função para fazer o pedido pode ser mais ou menos assim:
OrderFood(MenuItem : string) : food = {...}Ao pedir comida em um restaurante, você diz ao garçom qual prato do menu deseja comer, OrderFood("Lamen"). Você não sabe como o restaurante preparará o prato, mas espera receber algo que seja considerado comida após o pedido. Outros clientes podem pedir pratos diferentes do menu e também esperam receber seus pedidos.
É por isso que as funções são úteis: basta definir essas instruções em um só lugar, nesse caso, definindo o que deve acontecer quando alguém pede comida. Você pode então reutilizar a função em diferentes contextos, como, por exemplo, para cada cliente do restaurante que faz pedidos fora do menu.
As seções abaixo descrevem como criar uma função e como usar uma função depois que ela é definida.
Como definir funções
A assinatura da função declara o nome (identificador) da função, a entrada (parâmetros) e a saída (resultado) da função.
As funções em Verse também podem ter especificadores que especificam como usar ou implementar uma função.
O corpo da função é um bloco de código que define o que ela faz ao ser chamada.
As seções abaixo explicam esses conceitos com mais detalhes.
A sintaxe da função fica assim:
Identifier(parameter1 : type, parameter2 : type) <specifier> : type = {}Parâmetros
Um parâmetro é uma variável de entrada declarada em uma assinatura de função e usada no corpo da função. Ao chamar uma função, você deve atribuir valores aos parâmetros, se houver algum. Os valores atribuídos são chamados de argumentos de função.
Uma função pode não ter parâmetros, por exemplo, Sleep(), ou quantos parâmetros você precisar. Você declara um parâmetro no registro da função especificando um identificador e um tipo entre os parênteses (). Se tiver vários parâmetros, eles deverão ser separados por vírgulas ,.
Por exemplo:
Example(Parameter1 : int, Parameter2 : string) : string = {}Todos os argumentos a seguir são válidos:
Foo():void = {}
Bar(X:int):int = X
Baz(X:int, ?Y:int, ?Z:int = 0) = X + Y + ZA sintaxe ?Y:int define um argumento nomeado com o nome Y do tipo int.
A sintaxe ?Z:int = 0 define um argumento nomeado chamado Z do tipo int que não precisa ser fornecido quando a função é chamada, mas usa 0 como seu valor se não for fornecido.
Resultado
O resultado é a saída de uma função quando essa função é chamada. O tipo de retorno especifica que tipo de valor você pode esperar da função se ela for executada com sucesso.
Se não quiser que sua função tenha um resultado, poderá definir o tipo de retorno como void. Funções com void como tipo de retorno sempre retornam o valor false, mesmo quando você especifica uma expressão de resultado no corpo da função.
Especificadores
Além dos especificadores na função que descrevem o comportamento da função definida, podem haver especificadores no identificador (o nome) dessa função. Por exemplo:
Foo<public>(X:int)<decides>:int = X > 0Esse exemplo define uma função chamada Foo que é acessível ao público e tem o efeito decides. Os especificadores após a lista de parâmetros e antes do tipo de retorno descrevem a semântica da função e contribuem para o tipo da função resultante. Os especificadores no nome da função indicam somente o comportamento relacionado ao nome da função definida, como sua visibilidade.
Corpo da função
O corpo da função é um bloco de código que define o que ela faz. A função usa qualquer parâmetro definido na assinatura da função do corpo da função para criar um resultado.
Uma função retorna automaticamente o valor produzido pela última expressão executada.
Por exemplo:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2A função Bar() retorna 1 ou 2, dependendo se Foo[] falhar.
Para forçar o retorno de um valor específico (e a saída imediata da função), use a expressão return.
Por exemplo:
Minimum(X:int, Y:int):int =
if (X < Y):
return X
return YA expressão return X sairá da função Minimum, retornando o valor contido em x para o autor da chamada da função. Observação: se expressões return explícitas não forem usadas aqui, a função retornará a última expressão executada por padrão. Isso faria com que o valor de Y sempre retornasse, podendo dar um resultado incorreto.
Efeitos
Os efeitos em uma função descrevem comportamentos adicionais que podem ser adotados pela função quando ela é chamada. Especificamente, o efeito decides em uma função indica que ela pode falhar de uma forma que o autor da chamada talvez precise manipular (ou propagá-la para seu autor da chamada que também será marcado como decides).
Por exemplo:
Fail()<decides>:void = false?Define uma função que sempre falha. Qualquer chamador precisaria manipular ou propagar a falha. Observe que, na sintaxe, o efeito é descrito como um especificador na função. O tipo de uma função com esse efeito pode ser muito parecido com a definição da função por meio da macro de tipo:
type{_()<decides>void}Funções com o efeito decides também podem retornar um valor caso a função tenha sucesso.
Por exemplo:
First(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:t =
var ReturnOption:?t = false
for (Element : Array, F[Element], not ReturnOption?):
set ReturnOption = option{Element}
ReturnOption?Essa função determina o valor da primeira matriz que resulta em uma execução bem-sucedida da função decides fornecida. Essa função usa um tipo de opção para armazenar o valor de retorno e decidir se a função é bem-sucedida ou falha, pois instruções de retorno explícitas de dentro de um contexto de falha não são permitidas.
Você também pode combinar falha com expressões for. Uma função decides com uma expressão de falha dentro da expressão for tem sucesso somente se todas as iterações da expressão for tiverem sucesso.
Por exemplo:
All(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts><decides>:void =
for (Element : Array):
F[Element]A função tem sucesso apenas se todos os elementos da matriz resultarem em sucesso para a função F. Se qualquer entrada da matriz resultar em falha para a função F, a função All resultará em falha.
Você também pode usar combinar falha com uma expressão for para aplicar um filtro a uma entrada com base em quais entradas resultam em sucesso ou falha.
Por exemplo:
Filter(Array:[]t, F(:t)<transacts><decides>:void where t:type)<transacts>:[]t =
for (Element : Array, F[Element]):
ElementEssa função retorna uma matriz com apenas os elementos da matriz que resultam no sucesso da função F.
Funções de chamada
Uma chamada de função é uma expressão que avalia (conhecida como chamar ou invocar) uma função.
Existem duas formas de chamadas de funções no Verse:
FunctionName(Arguments): essa forma exige que a chamada da função seja bem-sucedida e possa ser usada em qualquer contexto.FunctionName[Arguments]: essa forma significa que a chamada da função pode falhar. Para usar este formulário, a função deve ser definida com o especificadordecidese chamada em um contexto de falha.
A invocação é realizada usando parênteses quando a função não tem o efeito decides. Por exemplo:
Foo()
Bar(1)
Baz(1, ?Y := 2)
Baz(3, ?Y := 4, ?Z := 5)
Baz(6, ?Z := 7, ?Y := 8)Observe como os argumentos são nomeados, por exemplo ? Y:int, são transmitidos fazendo referência ao nome prefixado por ? e fornecendo um valor à direita de :=. Observe também que o argumento nomeado ? Z é opcional. É importante ressaltar que a ordem dos argumentos nomeados no site da chamada é irrelevante, exceto por qualquer efeito colateral que possa ocorrer ao produzir o valor do argumento nomeado.
Para invocar uma função que tenha o efeito decides, colchetes devem ser usados. O que permite que a indexação de matrizes, que incorre no efeito decides, siga uma sintaxe semelhante à das funções marcadas com o efeito decides. Por exemplo:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2Desempacotamento de tuplas
Uma função que aceita vários argumentos é indistinguível quando invocada a partir de uma função que aceita um único argumento de tupla com elementos dos mesmos tipos como vários argumentos. A falta de distinção quando invocada também se aplica ao tipo de cada função: elas têm o mesmo tipo.
Por exemplo:
Second(:any, X:t where t:type):t = XO que é equivalente a:
Second(X:tuple(any, t) where t:type):t = X(1)Ambas podem ser invocadas como:
X := 1
Y := 2
Second(X, Y)ou
X:tuple(int, int) = (1, 2)
Second(X)Ambos satisfazem o tipo type{_(:any, :t where t:type):t}.
O tipo de função
O tipo de uma função é composto por seu tipo de parâmetro (potencialmente definido como uma tupla descompactada), seu efeito e seu tipo de resultado. Por exemplo:
type{_(:type1, :type2)<effect1>:type3}Esse é o tipo de uma função que recebe dois argumentos de type1 e type2 (ou equivalentemente um argumento do tipo tuple(type1, type2)), produz o efeito effect1 e retorna um valor do tipo type3.
Sobrecarga
Várias funções podem compartilhar o mesmo nome, desde que não haja argumentos que atendam a mais de uma dessas funções. O que é chamado de sobrecarga.
Por exemplo:
Next(X:int):int = X + 1
Next(X:float):float = X + 1
int_list := class:
Head:int
Tail:?int_list = false
Next(X:int_list)<decides>:int_list = X.Tail?Não há sobreposição nos argumentos que qualquer uma dessas funções aceita. A função correta a ser invocada pode ser resolvida de forma inequívoca pelos tipos fornecidos. Por exemplo:
Next(0)
Next(0.0)
Next(int_list{Head := 0, Tail := int_list{Head := 1}})No entanto, o seguinte não é permitido:
First(X:int, :any):int = X
First(X:[]int)<decides>int = X[0]Isso ocorre porque tuplas e matrizes têm uma relação de subtipagem: a matriz é um supertipo de tupla quando o tipo base da matriz é um supertipo de todos os tipos de elementos da tupla. Por exemplo:
X := (1, 2)
First(X)Neste exemplo, a chamada para First pode ser atendida por qualquer definição de First. No caso de classes e interfaces, nenhuma sobrecarga pode ocorrer, pois uma classe pode ser modificada posteriormente para implementar uma interface ou duas classes podem ser alteradas para terem uma relação de herança. Em vez disso, a substituição de métodos deve ser usada. Por exemplo,
as_int := interface:
AsInt():int
ToInt(X:as_int):int = X.AsInt()
thing1 := class(as_int):
AsInt():int = 1
thing2 := class(as_int):
AsInt():int = 2