Atualmente, estou estudando o Haskell e tento entender um projeto que usa o Haskell para implementar algoritmos criptográficos. Depois de ler on - line Aprenda um Haskell para o Bem , comecei a entender o código nesse projeto. Então eu descobri que estou preso no seguinte código com o símbolo "@":
-- | Generate an @n@-dimensional secret key over @rq@.
genKey :: forall rq rnd n . (MonadRandom rnd, Random rq, Reflects n Int)
=> rnd (PRFKey n rq)
genKey = fmap Key $ randomMtx 1 $ value @n
Aqui o randomMtx é definido da seguinte maneira:
-- | A random matrix having a given number of rows and columns.
randomMtx :: (MonadRandom rnd, Random a) => Int -> Int -> rnd (Matrix a)
randomMtx r c = M.fromList r c <$> replicateM (r*c) getRandom
E o PRFKey está definido abaixo:
-- | A PRF secret key of dimension @n@ over ring @a@.
newtype PRFKey n a = Key { key :: Matrix a }
Todas as fontes de informação que encontro dizem que @ é o padrão, mas aparentemente esse código não é o caso. Verifiquei o tutorial on-line, os blogs e até o relatório do idioma Haskell 2010 em https://www.haskell.org/definition/haskell2010.pdf . Simplesmente não há resposta para essa pergunta.
Mais trechos de código podem ser encontrados neste projeto usando @ desta maneira também:
-- | Generate public parameters (\( \mathbf{A}_0 \) and \(
-- \mathbf{A}_1 \)) for @n@-dimensional secret keys over a ring @rq@
-- for gadget indicated by @gad@.
genParams :: forall gad rq rnd n .
(MonadRandom rnd, Random rq, Reflects n Int, Gadget gad rq)
=> rnd (PRFParams n gad rq)
genParams = let len = length $ gadget @gad @rq
n = value @n
in Params <$> (randomMtx n (n*len)) <*> (randomMtx n (n*len))
Agradeço profundamente qualquer ajuda nisso.
fonte
Respostas:
Esse
@n
é um recurso avançado do Haskell moderno, que geralmente não é coberto por tutoriais como o LYAH, nem pode ser encontrado no Relatório.É chamado de aplicativo de tipo e é uma extensão de idioma do GHC. Para entendê-lo, considere esta função polimórfica simples
A chamada intuitiva
dup
funciona da seguinte maneira:a
x
do tipo escolhido anteriormentea
dup
então responde com um valor do tipo(a,a)
Em certo sentido, são
dup
necessários dois argumentos: o tipoa
e o valorx :: a
. No entanto, o GHC geralmente é capaz de inferir o tipoa
(por exemplox
, de ou do contexto em que estamos usandodup
), portanto, geralmente passamos apenas um argumento paradup
, a saberx
. Por exemplo, temosAgora, e se queremos passar
a
explicitamente? Bem, nesse caso, podemos ativar aTypeApplications
extensão e escreverObserve os
@...
argumentos que carregam tipos (não valores). Isso é algo que existe apenas no tempo de compilação - no tempo de execução, o argumento não existe.Por que queremos isso? Bem, às vezes não há por
x
perto, e queremos estimular o compilador a escolher o certoa
. Por exemploOs aplicativos de tipo geralmente são úteis em combinação com outras extensões que inviabilizam a inferência de tipo para o GHC, como tipos ambíguos ou famílias de tipos. Não vou discutir isso, mas você pode simplesmente entender que, às vezes, você realmente precisa ajudar o compilador, especialmente ao usar recursos avançados de tipo.
Agora, sobre o seu caso específico. Não tenho todos os detalhes, não conheço a biblioteca, mas é muito provável que você
n
represente um tipo de valor de número natural no nível de tipo . Aqui estamos mergulhando em extensões bastante avançadas, como as mencionadas acimaDataKinds
, talvezGADTs
, e algumas máquinas de escrever. Embora eu não possa explicar tudo, espero que eu possa fornecer algumas informações básicas. Intuitivamente,toma como argumento
@n
, um tipo de tempo de compilação natural, que não é passado no tempo de execução. Em vez de,takes
@n
(tempo de compilação), juntamente com uma prova quen
satisfaça a restriçãoC n
. O último é um argumento de tempo de execução, que pode expor o valor real den
. De fato, no seu caso, acho que você tem algo que se parece vagamenteque essencialmente permite que o código traga o nível de tipo natural para o nível de termo, acessando essencialmente o "tipo" como um "valor". (O tipo acima é considerado um "ambíguo", a propósito - você realmente precisa
@n
desambiguar.)Finalmente: por que alguém deveria querer passar
n
no nível de tipo se depois convertemos isso para o nível de termo? Não seria mais fácil simplesmente escrever funções comoem vez do mais pesado
A resposta honesta é: sim, seria mais fácil. No entanto, ter
n
no nível de tipo permite ao compilador executar mais verificações estáticas. Por exemplo, você pode querer um tipo para representar "módulo inteiron
" e permitir a adição deles. Tendofunciona, mas não há verificação disso
x
ey
são do mesmo módulo. Podemos adicionar maçãs e laranjas, se não tomarmos cuidado. Poderíamos escrevero que é melhor, mas ainda permite ligar
foo 5 x y
mesmo quandon
não é5
. Não é bom. Em vez de,impede que as coisas dêem errado. O compilador verifica estaticamente tudo. O código é mais difícil de usar, sim, mas de certa forma dificultar o uso é o ponto principal: queremos tornar impossível para o usuário tentar adicionar algo do módulo errado.
Concluindo: estas são extensões muito avançadas. Se você é iniciante, precisará progredir lentamente em direção a essas técnicas. Não desanime se você não conseguir compreendê-los após apenas um breve estudo, isso leva algum tempo. Faça um pequeno passo de cada vez, resolva alguns exercícios para cada recurso para entender o objetivo. E você sempre terá o StackOverflow quando estiver parado :-)
fonte