2010-07-01 22 views
65

El compilador F # parece realizar una inferencia de tipo de una manera (bastante) estricta de arriba a abajo, de izquierda a derecha. Esto significa que debe hacer cosas como poner todas las definiciones antes de su uso, el orden de compilación de archivos es significativo y tiende a reorganizar cosas (a través del |> o lo que tenga) para evitar tener anotaciones de tipo explícito.¿Por qué la inferencia de tipo F # es tan voluble?

¿Qué tan difícil es hacerlo más flexible, y es eso planeado para una versión futura de F #? Obviamente se puede hacer, ya que Haskell (por ejemplo) no tiene tales limitaciones con una inferencia igualmente poderosa. ¿Hay algo intrínsecamente diferente en el diseño o la ideología de F # que está causando esto?

+3

De hecho, me desagrada la pregunta, pero ya se han obtenido algunas respuestas fantásticas y esclarecedoras, así que también envidié a regañadientes :) – Brian

+7

@J Cooper: "Haskell (por ejemplo) no tiene tales limitaciones con una inferencia igualmente poderosa". Haskell no está cerca de tener una inferencia de tipo igualmente poderosa cuando se consideran las impurezas o el rendimiento. Por ejemplo, la función 'piso 'de Haskell típicamente ejecuta órdenes de magnitud más lentas que cualquier otro lenguaje compilado precisamente porque su falla al inferir el tipo estático correcto lo deja recurriendo al despacho en tiempo de ejecución. Además, si dejo de eliminar la anotación de tipo de nivel superior de una función 'randIntList' que tengo aquí, entonces deja de compilar con el infame error' ambiguous type variable'. –

+7

Me gusta la pregunta porque supongo que casi todos los que acaban de comenzar a aprender F # tienen dos pensamientos: "¡WOW, F # es tan poderoso!" y "WTF, ¿por qué F # no puede hacer esta inferencia tonta?" :) –

Respuesta

49

En cuanto a "igualmente poderosa inferencia de Haskell", no creo Haskell tiene que lidiar con

  • -estilo OO subtipos dinámico (tipo clases pueden hacer algunas cosas similares, pero las clases de tipos son más fáciles de escribir/inferir)
  • sobrecarga de métodos (clases de tipos puede hacer algunas cosas similares, pero las clases de tipo son más fáciles de escribir/inferir)

Es decir, creo que F # tiene que lidiar con algunas cosas duras que Haskell no tiene. (Casi con seguridad, Haskell tiene que lidiar con cosas difíciles que F # no tiene.)

Como se menciona en otras respuestas, la mayoría de los principales lenguajes .NET tienen las herramientas de Visual Studio como una gran influencia en el diseño del lenguaje (véase, por ejemplo, cómo LINQ tiene "de ... seleccionar" en lugar de SQL-y " select ... from ", motivado al obtener intellisense de un prefijo de programa). Intellisense, garabatos de error y comprensibilidad de mensajes de error son todos factores de herramientas que informan el diseño de F #.

Puede ser posible mejorar e inferir más (sin sacrificar otras experiencias), pero no creo que esté entre nuestras principales prioridades para las versiones futuras del idioma. (Los Haskeller pueden ver la inferencia tipo F # como algo débil, pero probablemente son superados en número por los C# ers que ven la inferencia tipo F # como muy fuerte :))

También podría ser difícil extender la inferencia tipo en un moda sin interrupción; está bien cambiar los programas ilegales por legales en una versión futura, pero debe tener mucho cuidado para asegurar que los programas legales anteriores no cambien la semántica según las nuevas reglas de inferencia, y la resolución de nombres (una terrible pesadilla en todos los idiomas) es probable interactuar con cambios de inferencia de tipos de maneras sorprendentes.

+1

Véase también http://lorgonblog.wordpress.com/2009/10/25/overview-of-type-inference-in-f/ – Brian

+1

Oh, sin sobrecargar el método, es un poco más fácil para Haskell. Definición de función (tipos de parámetro y retorno) son descifrables inequívocamente. – nawfal

16

F # utiliza una compilación pase tal que sólo se puede hacer referencia a tipos o funciones que se han definido sea más temprano en el archivo que está actualmente en o aparecer en un fichero del cual se especifica anteriormente en el orden de compilación.

Recientemente le pregunté a Don Syme sobre la realización de pases de fuente múltiples para mejorar el proceso de inferencia tipo . Su respuesta fue "Sí, es posible hacer múltiples pasadas inferencia de tipos. También hay variaciones de un solo paso que generan un conjunto finito de restricciones.

Sin embargo, estos enfoques tienden a dar mensajes de error y malas pobres intellisense da como resultado un editor visual ".

http://www.markhneedham.com/blog/2009/05/02/f-stuff-i-get-confused-about/#comment-16153

+2

Interesante. Parece que no recibo una gran ayuda intellisense para las funciones sencillas de F #; sobre todo, parece entrar en juego cuando se usa Module.somefunc o se usa una clase (que básicamente elimina la inferencia de tipos). Me pregunto qué tan incrustada es esta elección de diseño. –

+1

Parece que no está creando proyectos de VS para su código, o que está dejando errores de sintaxis en su código. Mientras no haga esto, el intellisense es una gran ayuda e informará los tipos de cada variable en el mouse hover, además de indicar de inmediato los tipos de error. Cuando programo en F #, esto me hace muy ágil; es una característica de gran importancia, y el costo es solo un uso raro de |> y muy rara vez mecanografía restricciones. – RD1

18

creo que el algoritmo utilizado por F # tiene la ventaja de que es fácil (al menos aproximadamente) explicar cómo funciona, así que una vez que lo entiendes, puede tener algunas expectativas sobre el resultado.

El algoritmo siempre tendrá algunas limitaciones. Actualmente, es bastante fácil de entender. Para algoritmos más complicados, esto podría ser difícil. Por ejemplo, creo que podría encontrarse con situaciones en las que piense que el algoritmo debería ser capaz de deducir algo, pero si fuera lo suficientemente general como para cubrir el caso, sería no decidible (por ejemplo, podría seguir girando para siempre).

Otra idea sobre esto es que verificar el código de arriba hacia abajo corresponde a cómo leemos el código (al menos algunas veces). Por lo tanto, tal vez el hecho de que tenemos la tendencia a escribir el código de una manera que permite la inferencia de tipo también hace que el código sea más legible para las personas ...

+9

En su último punto, y puedo ser raro de esta manera, tiendo a agrupar las funciones relacionadas de la manera opuesta (en los idiomas que me permiten). Por ejemplo, supongamos que tengo una función complicada C, y las funciones A y B ambas usan la función C, luego ordeno mis funciones de arriba a abajo A, B, C. Supongo que me gusta leer de esa manera debido a la forma en que se desentraña la implementación (muéstrame el panorama general, luego los detalles). Dicho esto, no me han importado demasiado las fuerzas F # ordenadoras, aunque no he trabajado en grandes proyectos con ella. –

+3

El orden que prefiera es más natural con la opción llamar por nombre porque refleja el orden de evaluación. Con call-by-value, el modo F # es más natural, especialmente porque las definiciones pueden tener efectos inmediatos. – RD1

51

¿Hay algo inherentemente diferente en el diseño o la ideología de F # que está causando esto?

Sí. F # usa el tipado nominal en lugar del estructural porque es más simple y, por lo tanto, más fácil de usar por simples mortales.

consideran este F # ejemplo:

let lengths (xss: _ [] []) = Array.map (fun xs -> xs.Length) xss 

let lengths (xss: _ [] []) = xss |> Array.map (fun xs -> xs.Length) 

El primero no se compila porque el tipo de xs dentro de la función anónima no puede deducirse porque F # no puede expresar el tipo "alguna clase con un miembro de Length".

En contraste, OCaml puede expresar el equivalente directo:

let lengths xss = Array.map (fun xs -> xs#length) xss 

porque OCaml puede expresar ese tipo (que está escrito <length: 'a ..>). Tenga en cuenta que esto requiere una inferencia de tipo más poderosa que la que tienen actualmente F # o Haskell, p. OCaml puede inferir tipos de suma.

Sin embargo, se sabe que esta característica es un problema de usabilidad. Por ejemplo, si se equivoca en otro lugar del código, entonces el compilador todavía no ha deducido que se suponía que el tipo de xs era una matriz, por lo que cualquier mensaje de error que pueda proporcionar solo puede proporcionar información como "algún tipo con un miembro de longitud" y no "una matriz". Con solo un código un poco más complicado, esto rápidamente se sale de control ya que tiene tipos masivos con muchos miembros estructuralmente inferidos que no se unifican del todo, lo que lleva a mensajes de error incomprensibles (como C++/STL).

+4

Interesante. Considerando todo, ¿prefieres el tipado nominal de F # o el tipado estructural de OCaml? –

+13

El académico en mí prefiere el modo OCaml porque puede ser mucho más conciso (por ejemplo, 'System.Windows.Controles.ScrollBarVisibility.Visible' en .NET es simplemente' Visible' en OCaml). El emprendedor en mí prefiere el modo F # porque la facilidad de uso para mis clientes es esencial para la viabilidad comercial. Sin embargo, tal vez haya un mejor compromiso. –

12

La respuesta corta es que F # se basa en la tradición de SML y OCaml, mientras que Haskell proviene de un mundo ligeramente diferente de Miranda, Gofer y similares. Las diferencias en la tradición histórica son sutiles, pero omnipresentes. Esta distinción también es paralela en otros lenguajes modernos, como el Coq de tipo ML que tiene las mismas restricciones de ordenamiento que el Agda similar a Haskell que no lo hace.

Esta diferencia está relacionada con la evaluación perezosa frente a la estricta. El lado Haskell del universo cree en la pereza, y una vez que ya crees en la pereza, la idea de agregar holgazanería a cosas como la inferencia de tipo es una obviedad.Mientras que en el lado ML del universo siempre es necesaria la pereza o la recursión mutua hay que señalar explícitamente por el uso de palabras clave como con , y , rec , etc. Yo prefiero el enfoque Haskell porque da lugar a menos código repetitivo, pero hay mucha gente que piensa que es mejor hacer que estas cosas sean explícitas.

+1

@ng: "... hay muchas personas que piensan que es mejor hacer que estas cosas sean explícitas". Mucha gente preferiría anotar explícitamente la pereza que el rigor y las impurezas, porque la pereza rara vez es útil, mientras que la severidad y las impurezas son omnipresentes (incluso en el código real de Haskell). –

+5

@JonHarrop Eso es verdad. Pero cambia la semántica del código. Mucha gente discute con usted que la pereza es muy útil muy a menudo y permite diferentes enfoques para resolver un problema. Está bastante claro que la capacidad de hacer ambas cosas es lo mejor, lo que de hecho ofrece Haskell, aunque sesgado hacia el lado perezoso en lugar del lado estricto como la mayoría de los idiomas. –