Tecnicamente, uma Higher Order Function é simplesmente uma função que faz pelo menos uma destas coisas:
No mundo da programação funcional, o uso tende a ser mais restrito.
O termo geralmente se refere a funções como filter, map e reduce, que aplicam uma função passada aos elementos de uma coleção.
Neste ponto do programa de estudos, já vimos várias formas de aplicar uma operação a todos os elementos de uma coleção iterável, como um Vector:
Este conceito vai focar em funções de alta ordem (familiares para quem conhece linguagens funcionais, como Haskell ou F#).
Outras abordagens possíveis incluem:
A função filter() recebe uma função passada com valor de retorno Boolean e a aplica a uma coleção.
Somente os elementos que retornam true são incluídos no valor de retorno, que é do mesmo tipo básico da entrada (veja abaixo).
julia> filter(iseven, 1:6)
3-element Vector{Int64}:
2
4
6
# String is a collection of Chars, so String in -> String out
julia> filter(!isascii, "Hrōðgār")
"ōðā"
# tuple input -> tuple output
julia> filter(iseven, (1, 2, 3, 4, 5))
(2, 4)
Com arrays multidimensionais, filter achata as dimensões de entrada e retorna um Vector: a principal exceção a qualquer regra sobre o tipo de saída corresponder ao tipo de entrada.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> filter(isodd, m)
3-element Vector{Int64}:
1
5
3
Os exemplos acima usam funções internas, mas o uso de funções anônimas é muito comum nesse contexto.
julia> filter(x -> x % 3 == 0, 1:20)
6-element Vector{Int64}:
3
6
9
12
15
18
Há também uma versão in-place, filter!(), assim como há para muitas das funções deste conceito.
A função map() transforma uma coleção aplicando uma função a cada elemento.
Em casos simples, isso pode ser parecido com broadcasting, com o formato da saída igual ao da entrada.
julia> map(√, [1, 4, 9])
3-element Vector{Float64}:
1.0
2.0
3.0
julia> map(x -> x^2 + 1, 1:4)
4-element Vector{Int64}:
2
5
10
17
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> map(√, m)
2×3 Matrix{Float64}:
1.0 1.41421 1.73205
2.0 2.23607 2.44949
map() também opera elemento a elemento sobre várias coleções.
julia> map(*, [1, 2], [3, 4])
2-element Vector{Int64}:
3
8
Conceitualmente, podemos pensar nisso como equivalente a rodar zip() sobre as várias coleções de entrada e depois map() sobre cada elemento do resultado intermediário.
Isso é apenas uma analogia aproximada, que não diz nada sobre a implementação!
Assim como com zip(), coleções de formato incompatível são truncadas para a dimensão ou dimensões da menor.
Às vezes, só os efeitos colaterais da função passada são necessários, como uma escrita em banco de dados ou um push! em um array.
Nesse caso, está disponível a função de alta ordem foreach(), que sempre retorna nothing.
A função reduce() recebe uma função de 2 argumentos e a aplica a uma coleção, levando a uma redução de dimensão.
Isso pode soar confuso no abstrato, mas pense em funções como sum() ou prod(), que recebem uma coleção e retornam um único valor.
julia> sum(1:4) # add
10
julia> prod(1:4) # multiply
24
Essas funções especiais são altamente otimizadas e devem sempre ser usadas quando disponíveis.
Outros exemplos incluem maximum() e minimum(), as funções lógicas all() e any(), e muitas funções estatísticas.
Apenas para ilustração, considere a mesma funcionalidade implementada com o reduce() mais genérico (lembre-se de que os operadores infixos + e * são, na verdade, funções internamente).
julia> reduce(+, 1:4) # add
10
julia> reduce(*, 1:4) # multiply
24
Assim como sum() e outras funções de agregação, reduce() pode receber um argumento nomeado opcional dims, para especificar a dimensão ou dimensões a reduzir.
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> reduce(+, m; dims=1)
1×3 Matrix{Int64}:
5 7 9
Esses são exemplos fáceis, porque tanto a adição quanto a multiplicação são comutativas (1+2 == 2+1) e associativas ( (1+2)+3 == 1+(2+3) ).
Isso está longe de ser universal! Até operações tão comuns quanto a subtração e a divisão são não associativas.
Há ainda o problema adicional de que erros de ponto flutuante podem se acumular ao longo de coleções grandes, então um reduce da esquerda para a direita pode produzir uma resposta um pouco diferente do que da direita para a esquerda.
A direção da função reduce de Julia depende da implementação e não é garantida.
Para controlar a direção explicitamente, existem as funções foldl() e foldr(), que começam, na teoria, na "esquerda" e na "direita", respectivamente (na verdade, no topo e na base, para um Vector).
julia> foldl(-, 1:3) # (1 - 2) - 3
-4
julia> foldr(-, 1:3) # 1 - (2 - 3)
2
Observe que essas funções são destinadas a coleções que podem ser tratadas como unidimensionais, retornando um resultado escalar.
O uso de um argumento dims não é suportado para foldl e foldr, só para reduce.
Combinar uma operação map com um reduce é muito comum em vários domínios da programação.
Poderíamos rodar map sequencialmente e depois rodar reduce sobre uma coleção intermediária.
No entanto, isso é, na melhor das hipóteses, ineficiente, e escala muito mal conforme a coleção cresce.
É fortemente recomendado usar a função combinada mapreduce() em vez disso.
Ela pode implementar um algoritmo muito mais eficiente, que intercala as operações de map e reduce.
O primeiro argumento é a função com que mapear, e o segundo argumento é o operador de reduce.
julia> mapreduce(x -> x^2 + 1, +, 1:3)
17
# equivalent to (2 + 5 + 10)
julia> sum(map(x -> x^2 + 1, 1:3))
17
Como era de se esperar, Julia também tem as funções mapfoldl() e mapfoldr() para situações em que a direção é importante.
Estamos montando um clube do queijo, que vai usar aprendizado de máquina (ML) para selecionar novos queijos e oferecê-los aos nossos clientes amantes de queijo, com base no histórico e no gosto de cada um.
Os novos membros precisam preencher uma pesquisa inicial para reunirmos alguns dados básicos no começo. Com isso, descobrimos que existe um subconjunto de clientes enfáticos que não têm nuance nas críticas. Como isso pode acabar enviesando de forma irreversível um algoritmo mais atento às nuances, existe um algoritmo separado para cuidar das necessidades deles. Pedimos que você forneça algumas funções auxiliares para organizar os dados deles.
Embora existam diferentes formas de resolver as tarefas a seguir, cada uma pode ser resolvida com uma única função de ordem superior, diferente para cada tarefa.
O sistema de avaliações é baseado em cinco estrelas, que simplesmente consistem nos números inteiros 1:5.
Os clientes enfáticos só dão notas 1 ou 5, e queremos saber se um cliente apresenta esse comportamento.
Implemente all_15(), que recebe um vetor de notas e retorna true se todas as notas forem 1 ou 5, e false caso contrário.
julia> ratings = [2, 3, 4, 4, 1];
julia> all_15(ratings)
false
julia> ratings = [1, 5, 5, 1, 5];
julia> all_15(ratings)
true
Precisamos separar os clientes mais enfáticos dos demais.
Implemente emphatics(), que recebe um dicionário de clientes e notas.
Retorna um dicionário semelhante contendo apenas aqueles que só usam avaliações de 1 ou 5 estrelas.
julia> ratings = ([2, 3, 5, 1, 1], [1, 1, 5, 5, 1], [4, 5, 5, 3, 2], [5, 5, 1, 1, 5]);
julia> names = ("c1", "c2", "c3", "c4");
julia> customers = Dict(zip(names, ratings))
Dict{String, Vector{Int64}} with 4 entries:
"c2" => [1, 1, 5, 5, 1]
"c1" => [2, 3, 5, 1, 1]
"c3" => [4, 5, 5, 3, 2]
"c4" => [5, 5, 1, 1, 5]
julia> emphatics(customers)
Dict{String, Vector{Int64}} with 2 entries:
"c2" => [1, 1, 5, 5, 1]
"c4" => [5, 5, 1, 1, 5]
Como os clientes enfáticos só usam notas 1 e 5, será computacionalmente mais conveniente se elas forem convertidas para 0 e 1.
Implemente tobinary(), que recebe um vetor de notas de clientes enfáticos.
Retorna notas binárias, em que 1 foi convertido para 0 e 5 foi convertido para 1.
julia> ratings = [1, 1, 5, 5, 1];
julia> tobinary(ratings)
5-element Vector{Int64}:
0
0
1
1
0
Nossos algoritmos usam entradas do tipo Matrix, então precisaremos transformar os dados em uma.
Implemente tobinarymatrix(), que recebe um vetor de vetores de notas de clientes enfáticos.
Retorna uma Matrix com os dados transformados, em que cada vetor de notas é uma linha da matriz.
julia> customersratings = [[1, 1, 5, 5, 1],[5, 5, 1, 1, 5]];
julia> tobinarymatrix(customersratings)
2×5 Matrix{Int64}:
0 0 1 1 0
1 1 0 0 1
Crie sua conta no Exercism para aprender e dominar Julia com 35 conceitos128 exercícios e mentoria humana de verdade, tudo de graça.