Olá Habr! Voltamos um pouco tarde das férias de Ano Novo com a continuação de nossa série de artigos sobre programação funcional. Hoje, falaremos sobre como entender as funções por meio de assinaturas e definir seus próprios tipos para assinaturas de funções. Detalhes sob o corte!

Não é óbvio, mas o F # tem duas sintaxes: para expressões regulares (significativas) e para definições de tipo. Por exemplo:
[1;2;3] // int list // Some 1 // int option // (1,"a") // int * string //
Expressões para tipos têm uma sintaxe especial que difere da sintaxe de expressões comuns. Você pode ter notado muitos exemplos dessa sintaxe ao trabalhar com o FSI (FSharp Interactive), como os tipos de cada expressão são exibidos junto com os resultados de sua execução.
Como você sabe, o F # usa um algoritmo de inferência de tipo; geralmente, você não precisa escrever tipos explicitamente no código, especialmente em funções. Mas, para trabalhar efetivamente com o F #, você precisa entender a sintaxe dos tipos, para poder definir seus próprios tipos, depurar erros de conversão de tipos e ler assinaturas de funções. Neste artigo, vou focar no uso de tipos em assinaturas de funções.
Aqui estão alguns exemplos de assinaturas de sintaxe de tipo:
// // let add1 x = x + 1 // int -> int let add xy = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit System.Console.ReadLine // unit -> string List.sum // 'a list -> 'a List.filter // ('a -> bool) -> 'a list -> 'a list List.map // ('a -> 'b) -> 'a list -> 'b list
Compreendendo funções através de assinaturas
Muitas vezes, mesmo estudando a assinatura de uma função, você pode ter uma idéia do que ela faz. Considere alguns exemplos e analise-os sucessivamente.
int -> int -> int
Essa função usa dois parâmetros int
e retorna outro int
. Provavelmente, é um tipo de funções matemáticas, como adição, subtração, multiplicação ou exponenciação.
int -> unit
Essa função usa uma unit
int
e retorna, o que significa que a função faz algo importante como efeito colateral. Porque como ele não retorna um valor útil, o efeito colateral provavelmente produz operações de gravação no IO, como log, gravação no banco de dados ou algo semelhante.
unit -> string
Essa função não aceita nada, mas retorna uma string
, o que pode significar que a função recebe uma string do ar. Como não há entrada explícita, a função provavelmente faz algo com a leitura (digamos, de um arquivo) ou geração (como uma sequência aleatória).
int -> (unit -> string)
Essa função recebe um int
e retorna outra função que retornará uma string quando chamada. Novamente, provavelmente, a função executa uma operação de leitura ou geração. É provável que a entrada inicialize de alguma forma a função de retorno. Por exemplo, a entrada pode ser o identificador do arquivo e a função retornada é semelhante a readline()
. Ou, a entrada pode ser o valor inicial de um gerador de seqüência aleatória. Não podemos dizer com certeza, mas podemos tirar algumas conclusões.
'a list -> 'a
A função aceita uma lista de qualquer tipo, mas retorna apenas um valor desse tipo. Isso pode indicar que a função agrega a lista ou seleciona um de seus elementos. Uma assinatura semelhante é List.sum
, List.max
, List.head
, etc.
('a -> bool) -> 'a list -> 'a list
Essa função usa dois parâmetros: o primeiro é uma função que converte algo em um bool
(predicado), o segundo é uma lista. O valor de retorno é uma lista do mesmo tipo. Predicados são usados para determinar se um objeto atende a um determinado critério e se essa função parece selecionar elementos de uma lista de acordo com um predicado - verdadeiro ou falso. Depois disso, ele retorna um subconjunto da lista original. Um exemplo de uma função com esta assinatura é List.filter
.
('a -> 'b) -> 'a list -> 'b list
A função usa dois parâmetros: conversão do tipo 'a
para o tipo 'b
uma lista do tipo 'a
. O valor de retorno é uma lista do tipo 'b
. É razoável supor que a função pegue cada elemento da lista 'a
ae converta-o em 'b
, usando a função passada como o primeiro parâmetro e, em seguida, retorne a lista 'b
. De fato, List.map
é o protótipo de uma função com essa assinatura.
Procure métodos de biblioteca com assinaturas
As assinaturas de funções são muito importantes para encontrar funções da biblioteca. As bibliotecas F # contêm centenas de funções, que podem ser confusas no início. Diferente das linguagens orientadas a objetos, você não pode simplesmente "inserir um objeto" através de um ponto para encontrar todos os métodos relacionados. Mas se você conhece a assinatura da função desejada, pode restringir rapidamente sua pesquisa.
Por exemplo, você tem duas listas e deseja encontrar uma função que as combine em uma. Que assinatura teria a função desejada? Teria que pegar duas listas como parâmetros e retornar uma terceira, todas do mesmo tipo:
'a list -> 'a list -> 'a list
Agora vá ao site de documentação do MSDN para o módulo List e procure por uma função semelhante. Acontece que há apenas uma função com essa assinatura:
append : 'T list -> 'T list -> 'T list
O que você precisa!
Definindo tipos nativos para assinaturas de funções
Algum dia você desejará definir seus próprios tipos para a função desejada. Isso pode ser feito usando a palavra-chave "type":
type Adder = int -> int type AdderGenerator = int -> Adder
No futuro, você pode usar esses tipos para limitar os valores dos parâmetros de função.
Por exemplo, uma segunda declaração cairá devido a uma restrição com um erro de conversão. Se o removermos (como no terceiro anúncio), o erro desaparecerá.
let a:AdderGenerator = fun x -> (fun y -> x + y) let b:AdderGenerator = fun (x:float) -> (fun y -> x + y) let c = fun (x:float) -> (fun y -> x + y)
Teste de assinaturas de funções de entendimento
Você entende bem as assinaturas de função? Verifique se você pode criar funções simples com as assinaturas abaixo. Evite especificar explicitamente os tipos!
val testA = int -> int val testB = int -> int -> int val testC = int -> (int -> int) val testD = (int -> int) -> int val testE = int -> int -> int -> int val testF = (int -> int) -> (int -> int) val testG = int -> (int -> int) -> int val testH = (int -> int -> int) -> int
Recursos Adicionais
Existem muitos tutoriais para F #, incluindo materiais para quem vem com experiência em C # ou Java. Os links a seguir podem ser úteis à medida que você avança no F #:
Várias outras maneiras de começar a aprender F # também são descritas.
Finalmente, a comunidade F # é muito amigável para iniciantes. Há um bate-papo muito ativo no Slack, suportado pela F # Software Foundation, com salas para iniciantes nas quais você pode participar livremente . É altamente recomendável que você faça isso!
Não se esqueça de visitar o site da comunidade de língua russa F # ! Se você tiver alguma dúvida sobre o aprendizado de um idioma, teremos prazer em discuti-los nas salas de bate-papo:
Sobre autores de tradução
Traduzido por @kleidemos
As mudanças de tradução e editoriais foram feitas pelos esforços da comunidade de desenvolvedores de F # de língua russa . Agradecemos também a @schvepsss e @shwars pela preparação deste artigo para publicação.