Estou aprendendo F #. Comecei o FP com Haskell e fiquei curioso por isso.
Como o F # é a linguagem .NET, parece-me mais razoável declarar a interface como Mappable
, assim como a Functor
classe do tipo haskell .
Mas, como na figura acima, as funções F # são separadas e implementadas por si só. Qual é o objetivo do projeto? Para mim, introduzir Mappable.map
e implementar isso para cada tipo de dados seria mais conveniente.
The community is here to help you with specific coding, algorithm, or language problems.
também abrangeria questões de design de linguagem, desde que os outros critérios fossem atendidos.map
e outras funções comuns de ordem superior para cada tipo de coleção. Uma interface ajudaria pouco, pois ainda requer que cada tipo de coleção forneça uma implementação separada.Respostas:
Sim, uma pergunta muito direta na superfície. Mas se você dedicar um tempo para refletir até o fim, entrará nas profundezas da teoria dos tipos incomensurável. E a teoria dos tipos também olha para você.
Primeiro, é claro, você já descobriu corretamente que o F # não tem classes de tipo, e é por isso. Mas você propõe uma interface
Mappable
. Ok, vamos analisar isso.Digamos que podemos declarar tal interface. Você pode imaginar como seria a assinatura?
Onde
f
é o tipo que implementa a interface. Oh espere! F # também não tem isso! Aquif
está uma variável do tipo com classificação mais alta e o F # não possui classificação mais alta. Não há como declarar uma funçãof : 'm<'a> -> 'm<'b>
ou algo parecido.Mas tudo bem, digamos que superamos esse obstáculo também. E agora temos uma interface
Mappable
que pode ser implementada porList
,Array
,Seq
e da pia da cozinha. Mas espere! Agora, temos um método em vez de uma função, e os métodos não compõem bem! Vamos dar uma olhada em adicionar 42 a cada elemento de uma lista aninhada:Olha: agora temos que usar uma expressão lambda! Não há como passar por isso
.map
implementação para outra função como um valor. Efetivamente, o final de "funciona como valores" (e sim, eu sei, usar um lambda não parece muito ruim neste exemplo, mas confie em mim, fica muito feio)Mas espere, ainda não terminamos. Agora que é uma chamada de método, a inferência de tipo não funciona! Como a assinatura de tipo de um método .NET depende do tipo do objeto, não há como o compilador inferir os dois. Na verdade, esse é um problema muito comum para iniciantes quando interoperam com bibliotecas .NET. E a única solução é fornecer uma assinatura de tipo:
Ah, mas isso ainda não é suficiente! Mesmo que eu tenha fornecido uma assinatura para
nestedList
si mesmo, não forneci uma assinatura para o parâmetro do lambdal
. Qual deve ser essa assinatura? Você diria que deveria serfun (l: #Mappable) -> ...
? Ah, e agora finalmente conseguimos classificar os tipos N, como você vê,#Mappable
é um atalho para "qualquer tipo'a
que'a :> Mappable
" - ou seja, uma expressão lambda que é genérica.Ou, alternativamente, poderíamos voltar à bondade superior e declarar o tipo de
nestedList
mais precisamente:Mas ok, vamos deixar de lado a inferência de tipo por enquanto e voltar à expressão lambda e como agora não podemos passar
map
como valor para outra função. Digamos que estendamos um pouco a sintaxe para permitir algo como o que Elm faz com os campos de registro:Qual seria o tipo de
.map
ser? Teria que ser um tipo restrito , como em Haskell!Uau OK. Pondo de lado o fato de que o .NET nem sequer permite a existência de tais tipos, efetivamente acabamos de recuperar as classes de tipo!
Mas há uma razão para o F # não ter classes de tipo em primeiro lugar. Muitos aspectos desse motivo estão descritos acima, mas uma maneira mais concisa de colocá-lo é: simplicidade .
Para você ver, este é um novelo de lã. Depois de ter classes de tipo, você deve ter restrições, maior gentileza, classificação N (ou pelo menos classificação 2) e, antes que você perceba, está solicitando tipos impredicativos, funções de tipo, GADTs e todas as resto disso.
Mas Haskell paga um preço por todos os presentes. Acontece que não há uma boa maneira de inferir todas essas coisas. Tipos de tipos mais altos meio que funcionam, mas as restrições já meio que não. Rank-N - nem sonhe com isso. E mesmo quando funciona, você obtém erros de tipo que você precisa ter um PhD para entender. E é por isso que em Haskell você é gentilmente incentivado a colocar assinaturas de tipo em tudo. Bem, nem tudo - tudo , mas realmente quase tudo. E onde você não coloca assinaturas de tipo (por exemplo, dentro
let
ewhere
) - surpresa-surpresa, esses lugares são realmente monomorfizados, então você está basicamente de volta à terra simplista de F #.No F #, por outro lado, as assinaturas de tipo são raras, principalmente apenas para documentação ou para interoperabilidade .NET. Fora desses dois casos, você pode escrever um programa grande e complexo em F # e não usar uma assinatura de tipo uma vez. A inferência de tipo funciona bem, porque não há nada muito complexo ou ambíguo para ele lidar.
E essa é a grande vantagem do F # sobre o Haskell. Sim, Haskell permite expressar coisas super complexas de uma maneira muito precisa, isso é bom. Mas o F # permite que você seja muito insensível, quase como Python ou Ruby, e ainda assim o compilador o pega se você tropeçar.
fonte