Estoy aprendiendo F #. Comencé FP con Haskell, y tengo curiosidad por esto.
Como F # es lenguaje .NET, parece más razonable para mí declarar una interfaz como Mappable
, al igual que la Functor
clase de tipo haskell .
Pero como en la imagen de arriba, las funciones de F # se separan y se implementan por sí mismas. ¿Cuál es el propósito del diseño de dicho diseño? Para mí, presentar Mappable.map
e implementar esto para cada tipo de datos sería más conveniente.
The community is here to help you with specific coding, algorithm, or language problems.
también abarcaría preguntas de diseño de lenguaje, siempre que se cumplan los demás criterios.map
y otras funciones comunes de orden superior para cada tipo de colección. Una interfaz ayudaría poco, ya que todavía requiere que cada tipo de colección proporcione una implementación separada.Respuestas:
Sí, una pregunta muy directa en la superficie. Pero si te tomas el tiempo para pensarlo hasta el final, te sumerges en las profundidades de la teoría de tipos inconmensurable. Y la teoría de los tipos también te mira a ti.
Primero, por supuesto, ya has descubierto correctamente que F # no tiene clases de tipo, y es por eso. Pero propones una interfaz
Mappable
. Ok, veamos eso.Digamos que podemos declarar tal interfaz. ¿Te imaginas cómo se vería su firma?
¿Dónde
f
está el tipo que implementa la interfaz? ¡Oh espera! ¡F # tampoco tiene eso! Aquíf
hay una variable de tipo de clase más alta, y F # no tiene clase de clase más alta. No hay forma de declarar una funciónf : 'm<'a> -> 'm<'b>
o algo así.Pero bueno, digamos que también superamos ese obstáculo. Y ahora tenemos una interfaz
Mappable
que puede ser implementado porList
,Array
,Seq
y el fregadero de la cocina. ¡Pero espera! ¡Ahora tenemos un método en lugar de una función, y los métodos no componen bien! Veamos cómo agregar 42 a cada elemento de una lista anidada:Mira: ¡ahora tenemos que usar una expresión lambda! No hay forma de pasar esta
.map
implementación a otra función como valor. Efectivamente, el final de "funciones como valores" (y sí, lo sé, usar un lambda no se ve muy mal en este ejemplo, pero créanme, se pone muy feo)Pero espera, todavía no hemos terminado. ¡Ahora que es una llamada al método, la inferencia de tipos no funciona! Debido a que la firma de tipo de un método .NET depende del tipo de objeto, el compilador no puede inferir ambos. Este es en realidad un problema muy común que enfrentan los novatos cuando interactúan con bibliotecas .NET. Y la única cura es proporcionar una firma de tipo:
¡Oh, pero esto todavía no es suficiente! Aunque he proporcionado una firma para
nestedList
sí mismo, no he proporcionado una firma para el parámetro de lambdal
. ¿Cuál debería ser esa firma? ¿Dirías que debería serfun (l: #Mappable) -> ...
? Ah, y ahora finalmente llegamos a los tipos de rango N, ya ves,#Mappable
es un atajo para "cualquier tipo'a
tal que'a :> Mappable
", es decir, una expresión lambda que es genérica.O, alternativamente, podríamos volver a la mayor amabilidad y declarar el tipo de
nestedList
más precisamente:Pero bueno, dejemos de lado la inferencia de tipos por ahora y volvamos a la expresión lambda y cómo ahora no podemos pasar
map
como valor a otra función. Digamos que extendemos un poco la sintaxis para permitir algo como lo que hace Elm con los campos de registro:¿Cuál sería el tipo de
.map
ser? ¡Tendría que ser un tipo restringido , como en Haskell!Wow, esta bién. Dejando a un lado el hecho de que .NET ni siquiera permite que tales tipos existan, efectivamente, ¡acabamos de recuperar las clases de tipos!
Pero hay una razón por la cual F # no tiene clases de tipo en primer lugar. Muchos aspectos de esa razón se describen anteriormente, pero una forma más concisa de expresarlo es: simplicidad .
Para ver, esta es una bola de hilo. Una vez que tenga clases de tipo, debe tener restricciones, mayor amabilidad, rango-N (o al menos rango-2), y antes de darse cuenta, está solicitando tipos impredecibles, funciones de tipo, GADT y todos los El resto
Pero Haskell paga un precio por todas las golosinas. Resulta que no hay una buena manera de inferir todas esas cosas. Los tipos de clase superior funcionan, pero las restricciones ya no. Rango N: ni siquiera sueñe con eso. E incluso cuando funciona, obtienes errores de tipo que debes tener un doctorado para entender. Y es por eso que en Haskell se le anima a poner firmas tipográficas en todo. Bueno, no todo , todo , pero realmente casi todo. Y donde no pones firmas tipográficas (por ejemplo, adentro
let
ywhere
) - sorpresa-sorpresa, esos lugares están realmente monomorfizados, por lo que esencialmente estás de vuelta en la simplista tierra F #.En F #, por otro lado, las firmas de tipo son raras, principalmente solo para documentación o para interoperabilidad .NET. Fuera de esos dos casos, puede escribir un programa complejo grande en F # y no usar una firma de tipo una vez. La inferencia de tipos funciona bien, porque no hay nada demasiado complejo o ambiguo para que lo maneje.
Y esta es la gran ventaja de F # sobre Haskell. Sí, Haskell te permite expresar cosas súper complejas de una manera muy precisa, eso es bueno. Pero F # te permite ser muy flojo, casi como Python o Ruby, y aún así el compilador te atrapará si tropiezas.
fuente