Qual é o ponto de 'const' no Prelúdio de Haskell?

92

Olhando o Prelúdio de Haskell, vejo uma função const :

const x _ = x

Não consigo encontrar nada relevante sobre esta função.

Qual é o ponto? Alguém pode dar um exemplo de onde essa função pode ser usada?

Stusmith
fonte
10
Um exemplo: backgroundColor :: Text -> Coloris for mebackgroundColor = const White
Zhen

Respostas:

83

É útil para passar para funções de ordem superior quando você não precisa de toda a flexibilidade. Por exemplo, o operador de sequência monádica >>pode ser definido em termos do operador de ligação monádica como

x >> y = x >>= const y

É um pouco mais limpo do que usar um lambda

x >> y = x >>= \_ -> y

e você pode até mesmo usá-lo sem pontos

(>>) = (. const) . (>>=)

embora eu particularmente não recomende isso neste caso.

Hammar
fonte
9
+1. Também aparece com frequência ao usar combinadores de analisador.
Fred Foo
47
Ahh, então é mais um 'gerador de função' - eu o uso com um argumento e ele me dá uma função (tomando um argumento) que sempre retorna um valor constante. Portanto, map (const 42) [1..5]resulta em [42, 42, 42, 42, 42].
stusmith
2
stusmith: É isso aí. consté útil para aplicar a um único argumento para produzir uma função onde uma é necessária (como passar para map).
Conal
8
@stusmith: Você pode usá-lo de algumas maneiras interessantes:head = foldr const (error "Prelude.head: empty list")
rampion
27

Para adicionar à excelente resposta direta de Hammar: funções humildes gostam conste idsão realmente úteis como uma função de ordem superior pela mesma razão que são fundamentais no cálculo combinador de SKI .

Não que eu ache que as funções do prelúdio de haskell foram modeladas conscientemente a partir desse sistema formal ou algo assim. É que criar abstrações ricas em haskell é muito fácil, então você frequentemente vê esses tipos de coisas teóricas emergirem como úteis na prática.

Plugue desavergonhado, mas escrevi sobre como a instância Applicative for (->)é realmente os combinadores Se aqui , se esse é o tipo de coisa que você gosta.K

Jberryman
fonte
8
Bem, os combinadores de SKI certamente influenciaram o Prelúdio. Lembro-me de discutir com Joe Fasel se o combinador S deveria ser incluído ou não.
agosto
4
A propósito, ((->) e)também é a mônada do leitor - com Readere semelhantes sendo apenas newtypeinvólucros - e a askfunção é então id, então esse é o Icombinador também. Se você olhar em vez de base BCKW original de Haskell Curry, B, K, e Wsão fmap, returne, joinrespectivamente.
CA McCann
1
O link do blog na resposta está morto. Agora deve apontar aqui: brandon.si/code/…
nsxt
22

Um exemplo simples de uso consté Data.Functor.(<$). Com esta função você pode dizer: eu tenho aqui um functor com algo chato nele, mas ao invés disso eu quero ter aquela outra coisa interessante nele, sem mudar o formato do functor. Por exemplo

import Data.Functor

42 <$ Just "boring"
--> Just 42

42 <$ Nothing
--> Nothing

"cool" <$ ["nonsense","stupid","uninteresting"]
--> ["cool","cool","cool"]

A definição é:

(<$) :: a -> f b -> f a
(<$) =  fmap . const

ou escrito não tão sem sentido:

cool <$ uncool =  fmap (const cool) uncool

Você vê como consté usado aqui para "esquecer" a entrada.

Landei
fonte
21

Não consigo encontrar nada relevante sobre esta função.

Muitas das outras respostas discutem aplicações relativamente esotéricas (pelo menos para o recém-chegado) de const. Aqui está um simples: você pode usar constpara se livrar de um lambda que leva dois argumentos, joga fora o primeiro, mas faz algo interessante com o segundo.

Por exemplo, a seguinte implementação (ineficiente, mas instrutiva) de length,

length' = foldr (\_ acc -> 1 + acc) 0

pode ser reescrito como

length' = foldr (const (1+)) 0

o que talvez seja mais elegante.

A expressão const (1+)é na verdade semanticamente equivalente a \_ acc -> 1 + acc, porque pega um argumento, joga-o fora e retorna a seção (1+) .

jub0bs
fonte
4
Levei 5 minutos para entender como isso funciona :)
Mukesh Soni
15

Outro uso é implementar funções de membro de classe que possuem um argumento fictício que não deve ser avaliado (usado para resolver tipos ambíguos). Exemplo que poderia estar em Data.bits:

instance Bits Int where
  isSigned = const True
  bitSize  = const wordSize
  ...

Ao usar const, dizemos explicitamente que estamos definindo valores constantes.

Pessoalmente, não gosto do uso de parâmetros fictícios, mas se eles forem usados ​​em uma classe, essa é uma maneira bastante boa de escrever instâncias.

Jonas Duregård
fonte
Os argumentos de proxy são realmente muito melhores e, ao direcionar o GHC recente, os aplicativos de tipo fazem o truque perfeitamente.
dfeuer
2

constpode ser apenas a implementação que você está procurando em conjunto com outras funções. Aqui está um exemplo que descobri.

Digamos que queremos reescrever uma estrutura de 2 tuplas em outra estrutura de 2 tuplas. Posso expressar isso assim:

((a,b),(c,d))  (a,(c,(5,a)))

Posso dar uma definição direta com correspondência de padrões:

f ((a,b),(c,d)) = (a,(c,(5,a)))

E se eu quiser uma solução inútil (tácita) para esse tipo de reescrita? Pensando e brincando depois, a resposta é que podemos expressar qualquer reescrita com (&&&), const, (.), fst, snd. Observe que (&&&)é de Control.Arrow.

A solução do exemplo usando essas funções é:

(fst.fst &&& (fst.snd &&& (const 5 &&& fst.fst)))

Observe a semelhança com (a,(c,(5,a))). E se substituirmos &&&por ,? Em seguida, lê-se:

(fst.fst, (fst.snd, (const 5, fst.fst)))

Observe como aé o primeiro elemento do primeiro elemento e é isso que fst.fstprojeta. Observe como cé o primeiro elemento do segundo elemento, e é isso que fst.sndprojeta. Ou seja, as variáveis ​​se tornam o caminho para sua origem.

constnos permite introduzir constantes. Interessante como o nome se alinha com o significado!

Eu, então, generalizada esta ideia com Applicative de modo que você pode escrever qualquer função num estilo inútil (contanto que você tem análise de caso disponível como funções, tais como maybe, either, bool). Novamente, constdesempenha o papel de introduzir constantes. Você pode ver este trabalho no pacote Data.Function.Tacit .

Quando você começa abstratamente, no objetivo, e depois trabalha para uma implementação, pode se surpreender com as respostas. Ou seja, qualquer função pode ser tão misteriosa quanto qualquer engrenagem de uma máquina. Se você recuar para visualizar toda a máquina, poderá entender o contexto no qual essa engrenagem é necessária.

Erisco
fonte
2

Digamos que você queira criar uma lista Nothingsigual ao comprimento de uma string. Conforme constretorna seu primeiro argumento, não importa o segundo, você pode fazer:

listOfNothings :: String -> [Maybe Char]
listOfNothings = (map . const) Nothing

ou, mais explicitamente:

listOfNothing st = map (const Nothing) st
A.Saramet
fonte
0

Digamos que você queira girar uma lista. Esta é uma maneira idiomática de fazer isso em Haskell:

rotate :: Int -> [a] -> [a] rotate _ [] = [] rotate n xs = zipWith const (drop n (cycle xs)) xs

Esta função compacta dois arrays com a função const, o primeiro sendo um array cíclico infinito, o segundo sendo o array com o qual você começou.

const atua como a verificação de limites e usa a matriz original para encerrar a matriz cíclica.

Veja: Rotacionar uma lista em Haskell

Jameis famosos
fonte
0

Não consigo encontrar nada relevante sobre esta função.

Suponha que você gostaria de gerar todas as subsequências de uma determinada lista.

Para cada elemento da lista, em um determinado ponto, você tem uma escolha de Verdadeiro (inclua-o na subsequência atual) ou Falso (não inclua). Isso pode ser feito usando a função filterM .

Como isso:

 λ> import Control.Monad
 λ> :t filterM
 filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
 λ> 

Por exemplo, queremos todas as subsequências de [1..4] .

 λ> filterM  (const [True, False])  [1..4]
 [[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
 λ> 
jpmarinier
fonte