Articulo de referencia

Flix (lenguaje de programación)

{{cite web |url=https://github.com/flix/flix/blob/master/LICENSE.md|title=Apache License 2.0|date=27 July 2022 |via=[[GitHub]]}} "},"website":{"wt":"{{URL|https://flix.dev/}}"},...

Flix es un lenguaje de programación funcional , imperativo y lógico desarrollado en la Universidad de Aarhus , con financiación del Fondo de Investigación Independiente de Dinamarca [ 2 ] y por una comunidad de colaboradores de código abierto [ 3 ] . El lenguaje Flix admite tipos de datos algebraicos , coincidencia de patrones , polimorfismo paramétrico , currificación , funciones de orden superior , registros extensibles [ 4 ] , concurrencia basada en canales y procesos , y eliminación de llamadas de cola . Dos características notables de Flix son su sistema de tipos y efectos [ 5 ] y su compatibilidad con restricciones Datalog de primera clase [ 6 ] .

El sistema de tipos y efectos de Flix admite la inferencia de tipos al estilo Hindley-Milner . El sistema separa el código puro del impuro: si una expresión se tipifica como pura, no puede producir un efecto en tiempo de ejecución. Las funciones de orden superior pueden exigir que se les proporcionen argumentos de función puros (o impuros). El sistema de tipos y efectos admite el polimorfismo de efectos [ 7 ] [ 8 ] , lo que significa que el efecto de una función de orden superior puede depender del efecto o los efectos de sus argumentos.

Flix admite programas Datalog como valores de primera clase . Un valor de programa Datalog, es decir, una colección de hechos y reglas Datalog, puede pasarse a funciones y devolverse desde ellas, almacenarse en estructuras de datos y combinarse con otros valores de programa Datalog. El modelo mínimo de un valor de programa Datalog puede calcularse y es en sí mismo un valor de programa Datalog. De esta manera, Flix puede considerarse un lenguaje de metaprogramación para Datalog. Flix admite la negación estratificada y el compilador de Flix garantiza la estratificación en tiempo de compilación. [ 9 ] Flix también admite una forma enriquecida de restricciones Datalog donde los predicados tienen semántica reticular . [ 10 ] [ 11 ] [ 12 ] [ 13 ]

Descripción general

Flix es un lenguaje de programación de la familia ML . Su sistema de tipos y efectos se basa en Hindley-Milner con varias extensiones, incluyendo polimorfismo de filas y unificación booleana . La sintaxis de Flix está inspirada en Scala y utiliza palabras clave cortas y llaves . El modelo de concurrencia de Flix está inspirado en Go y se basa en canales y procesos . Un proceso es un hilo ligero que no comparte memoria (mutable) con otro proceso. Los procesos se comunican a través de canales, que son colas de mensajes inmutables, limitadas o ilimitadas.

Si bien muchos lenguajes de programación admiten una combinación de programación funcional e imperativa, el sistema de tipos y efectos de Flix rastrea la pureza de cada expresión, lo que permite escribir partes de un programa Flix en un estilo puramente funcional, con la pureza garantizada por el sistema de efectos.

Los programas de Flix se compilan a bytecode de JVM y son ejecutables en la Máquina Virtual de Java (JVM). [ 14 ] El compilador de Flix realiza la compilación completa del programa , elimina el polimorfismo mediante la monomorfización [ 15 ] y utiliza la eliminación de código muerto para eliminar el código inaccesible . La monomorfización evita el empaquetado de valores primitivos a costa de tiempos de compilación más largos y binarios ejecutables más grandes. Flix ofrece cierto soporte para la interoperabilidad con programas escritos en Java . [ 16 ]

Flix admite la eliminación de llamadas de cola, lo que garantiza que las llamadas a funciones en posición de cola nunca consuman espacio de pila y, por lo tanto, no puedan causar un desbordamiento de la pila de llamadas. [ 17 ] Dado que el conjunto de instrucciones de la JVM carece de soporte explícito para las llamadas de cola, dichas llamadas se emulan utilizando una forma de marcos de pila reutilizables. [ 18 ] El soporte para la eliminación de llamadas de cola es importante ya que toda la iteración en Flix se expresa a través de la recursión .

El compilador Flix no permite la mayoría de las formas de código no utilizado o redundante, incluyendo: variables locales no utilizadas, funciones no utilizadas, parámetros formales no utilizados, parámetros de tipo no utilizados y declaraciones de tipo no utilizadas; dichas construcciones no utilizadas se reportan como errores del compilador. [ 19 ] El sombreado de variables también está prohibido. La razón declarada es que el código no utilizado o redundante a menudo se correlaciona con código erróneo. [ 20 ]

Existe una extensión de Visual Studio Code para Flix. [ 21 ] La extensión se basa en el Protocolo de servidor de lenguaje , una interfaz común entre IDE y compiladores que está desarrollando Microsoft .

Flix es un software de código abierto disponible bajo la licencia Apache 2.0 .

Ejemplos

Hola Mundo

El siguiente programa imprime " ¡Hola Mundo! " cuando se compila y ejecuta:

def main (): Unidad \ IO = println ( "¡Hola Mundo!" )

La firma de tipo y efecto de la mainfunción especifica que no tiene parámetros, devuelve un valor de tipo Unity que la función tiene el efecto IO, es decir, es impura. La mainfunción es impura porque invoca a , printLineque es impura.

Tipos de datos algebraicos y coincidencia de patrones

El siguiente fragmento de programa declara un tipo de datos algebraico (TDA) llamado Shape:

enum Shape { case Circle ( Int32 ), // tiene radio de círculo case Square ( Int32 ), // tiene longitud de lado case Rectangle ( Int32 , Int32 ) // tiene altura y anchura }

El TAD tiene tres constructores: Circle, Square, y Rectangle.

El siguiente fragmento de programa utiliza la coincidencia de patrones para destruir un Shapevalor:

def área ( s : Forma ): Int32 = coincidir s { case Círculo ( r ) => 3 * ( r * r ) case Cuadrado ( w ) => w * w case Rectángulo ( h , w ) => h * w }

Funciones de orden superior

El siguiente fragmento de programa define una función de orden superior llamada twiceque, al recibir una función fde Inta, Intdevuelve una función que se aplica fa su entrada dos veces:

def twice ( f : Int32 -> Int32 ): Int32 -> Int32 = x -> f ( f ( x ))

Podemos utilizar la función twicede la siguiente manera:

dos veces ( x -> x + 1 )( 0 )

Aquí la llamada twice(x -> x + 1)devuelve una función que incrementará su argumento dos veces. Por lo tanto, el resultado de toda la expresión es 0 + 1 + 1 = 2.

Polimorfismo paramétrico

El siguiente fragmento de programa ilustra una función polimórfica que aplica una función f: a -> bsobre una lista de elementos de tipo ay devuelve una lista de elementos de tipo b:

def map ( f : a -> b , l : List [ a ]): List [ b ] = match l { case Nil => Nil case x :: xs => f ( x ) :: map ( f , xs ) }

La mapfunción recorre la lista de forma recursiva ly aplica un método fa cada elemento, construyendo una nueva lista.

Flix admite la elisión de parámetros de tipo, por lo que no es necesario que los parámetros de tipo ase bintroduzcan explícitamente.

Registros extensibles

El siguiente fragmento de programa muestra cómo construir un registro con dos campos xy y:

def point2d (): { x : Int32 , y : Int32 } = { x = 1 , y = 2 }

Flix utiliza polimorfismo de filas para tipificar los registros. La sumsiguiente función toma un registro que tiene los campos xy y(y posiblemente otros campos) y devuelve la suma de los dos campos:

def sum ( r : { x : Int32 , y : Int32 | rest }): Int = r . x + r . y

Las siguientes son todas llamadas válidas a la sumfunción:

suma ({ x = 1 , y = 2 }) suma ({ y = 2 , x = 1 }) suma ({ x = 1 , y = 2 , z = 3 })

Características destacadas

Efectos polimórficos

El sistema de tipos y efectos de Flix separa las expresiones puras de las impuras. [ 5 ] [ 22 ] [ 23 ] Se garantiza que una expresión pura sea referencialmente transparente . Una función pura siempre devuelve el mismo valor cuando se le dan los mismos argumentos y no puede tener ningún efecto secundario (observable).

Por ejemplo, la siguiente expresión es de tipo Int32y tiene el conjunto de efectos vacío {}, es decir, es pura:

1 + 2 : Int32 \ {}

mientras que la siguiente expresión tiene el IOefecto, es decir, es impura:

println ( "Hola Mundo" ) : Unidad \ IO

Una función de orden superior puede especificar que un argumento de la función debe ser puro, impuro o que es polimórfico en cuanto a sus efectos.

Por ejemplo, la definición de Set.existsrequiere que su argumento de función fsea puro:

// La sintaxis a -> Bool es una forma abreviada de a -> Bool \ {} def exists ( f : a -> Bool , xs : Set [ a ]): Bool = ...

El requisito de que fsea puro garantiza que no se filtren detalles de implementación . Por ejemplo, dado que fes puro, no se puede utilizar para determinar en qué orden se recorren los elementos del conjunto. Si ffuera impuro, tales detalles podrían filtrarse, por ejemplo, al pasar una función que también imprima el elemento actual, revelando así el orden interno de los elementos dentro del conjunto.

Una función de orden superior también puede requerir que una función sea impura.

Por ejemplo, la definición de List.foreachrequiere que su argumento de función fsea impuro:

def foreach ( f : a -> Unit \ IO , xs : List [ a ]): Unit \ IO

El requisito de que fdebe ser impuro garantiza que el código tenga sentido: no tendría sentido llamar List.foreachcon una función pura ya que siempre devuelve Unit.

El tipo y el efecto son correctos, pero no completos. Es decir, si una función es pura, no puede causar un efecto, mientras que si es impura , puede causarlo, pero no necesariamente. Por ejemplo, la siguiente expresión es impura aunque no pueda producir un efecto en tiempo de ejecución:

if ( 1 == 2 ) println ( "Hola Mundo!" ) else ()

Una función de orden superior también puede ser polimórfica en cuanto a sus efectos: sus efectos pueden depender de sus argumentos.

Por ejemplo, la definición de la biblioteca estándar List.mapes polimórfica en cuanto a efectos: [ 24 ]

def map ( f : a -> b \ e , xs : List [ a ]): List [ b ] \ e

La List.mapfunción toma una función fde elementos de tipo aa bcon efecto e. El efecto de la función map es en sí mismo e. Por consiguiente, si List.mapse invoca con una función pura, entonces toda la expresión es pura, mientras que si se invoca con una función impura, entonces toda la expresión es impura. Es polimórfica en cuanto a sus efectos.

Una función de orden superior que acepta múltiples argumentos puede combinar sus efectos.

Por ejemplo, la definición de la biblioteca estándar de composición de funciones hacia adelante es pura si ambos argumentos de su función son puros: [ 25 ]>>

def >> ( f : a -> b \ e1 , g : b -> c \ e2 ): a -> c \ e1 + e2 = x -> g ( f ( x ))

La firma de tipo y efecto se puede entender de la siguiente manera: La función toma dos argumentos de función: con efecto y con efecto . El efecto de es polimórfico en la conjunción de y . Si ambos son puros, entonces la expresión general es pura.>>fe1ge2>>e1e2

El sistema de tipos y efectos permite que expresiones de conjuntos arbitrarias controlen la pureza de los argumentos de las funciones.

Por ejemplo, es posible expresar una función de orden superior hque acepte dos argumentos de función fy gdonde los efectos de fson disjuntos de los de g:

def h ( f : a -> b \ e1 , g : b -> c \ e2 - e1 ): Unidad

Si hse llama con un argumento de función fque tiene el IOefecto, entonces gno puede tener el IOefecto.

El sistema de tipos y efectos se puede utilizar para garantizar que las expresiones de las sentencias sean útiles; es decir, que si una expresión o función se evalúa y su resultado se descarta, entonces debe tener un efecto secundario. Por ejemplo, compilando el siguiente fragmento de programa:

def main ( ): Unit \ IO = List.map ( x -> 2 * x , 1 :: 2 :: Nil ) ; println ( "Hola Mundo" );

provoca un error de compilación:

-- Error de redundancia -------------------------------------------------->> Expresión inútil: No tiene efectos secundarios y su resultado se descarta.2 | List.map(x -> 2 * x, 1 :: 2 :: Nil);  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  expresión inútil.

Porque no tiene sentido evaluar la expresión pura y luego descartar su resultado. Lo más probable es que el programador quisiera usar el resultado (o, alternativamente, la expresión es redundante y podría eliminarse). Por consiguiente, Flix rechaza este tipo de programas.List.map(x -> 2 * x, 1 :: 2 :: Nil)

Restricciones de registro de datos de primera clase

Flix admite programas Datalog como valores de primera clase. [ 6 ] [ 9 ] [ 26 ] Un programa Datalog es un programa lógico que consiste en una colección de hechos y reglas no ordenados . Juntos, los hechos y las reglas implican un modelo mínimo , una solución única para cualquier programa Datalog. En Flix, los valores de los programas Datalog se pueden pasar a funciones y devolver desde ellas, almacenar en estructuras de datos, combinar con otros valores de programas Datalog y resolver. La solución a un programa Datalog (el modelo mínimo) es en sí misma un programa Datalog. Por lo tanto, es posible construir secuencias de programas Datalog donde la solución, es decir, la "salida", de un programa Datalog se convierte en la "entrada" de otro programa Datalog.

Los siguientes hechos de arista definen un grafo:

Borde ( 1 , 2 ) . Borde ( 2 , 3 ) . Borde ( 3 , 4 ) .

Las siguientes reglas de Datalog calculan el cierre transitivo de la relación de arista:

Ruta ( x , y ) :- Arista ( x , y ). Ruta ( x , z ) :- Ruta ( x , y ), Arista ( y , z ).

El modelo mínimo de los hechos y las reglas es:

Borde ( 1 , 2 ) . Borde ( 2 , 3 ) . Borde ( 3 , 4 ) . Camino ( 1 , 2 ) . Camino ( 2 , 3 ) . Camino ( 3 , 4 ) . Camino ( 1 , 3 ) . Camino ( 1 , 4 ) . Camino ( 2 , 4 ) .

En Flix, los programas Datalog son valores. El programa anterior se puede integrar en Flix de la siguiente manera:

def main (): #{Edge(Int32, Int32), Path(Int32, Int32)} = let f = #{ Edge ( 1 , 2 ) . Edge ( 2 , 3 ) . Edge ( 3 , 4 ) . }; let p = #{ Path ( x , y ) : - Edge ( x , y ) . ​​Path ( x , z ) : - Path ( x , y ), Edge ( y , z ) . }; solve f <+> p

La variable local fcontiene un valor de programa Datalog que consta de los hechos de arista. De manera similar, la variable local pes un valor de programa Datalog que consta de las dos reglas. La f <+> pexpresión calcula la composición (es decir, la unión) de los dos programas Datalog fy p. La solveexpresión calcula el modelo mínimo del programa Datalog combinado, devolviendo los hechos de arista y ruta mostrados anteriormente.

Dado que los programas Datalog son valores de primera clase, podemos refactorizar el programa anterior en varias funciones. Por ejemplo:

def edges (): #{Edge(Int32, Int32), Path(Int32, Int32)} = #{ Edge ( 1 , 2 ) . Edge ( 2 , 3 ) . Edge ( 3 , 4 ) . }def cierre (): #{Edge(Int32, Int32), Path(Int32, Int32)} = #{ Path ( x , y ) : - Edge ( x , y ) . Path ( x , z ) : - Path ( x , y ), Edge ( y , z ) . }def run (): #{Edge(Int32, Int32), Path(Int32, Int32)} = resolver edges() <+> closure()

El cierre no dirigido del grafo se puede calcular añadiendo la regla:

Ruta ( x , y ) :- Ruta ( y , x ).

Podemos modificar la closurefunción para que acepte un argumento booleano que determine si queremos calcular el cierre dirigido o no dirigido:

def cierre ( dirigido : Bool ): #{Edge(Int32, Int32), Path(Int32, Int32)} = let p1 = #{ Path ( x , y ) : - Edge ( x , y ) . Path ( x , z ) : - Path ( x , y ), Edge ( y , z ) . }; let p2 = #{ Path ( y , x ) : - Path ( x , y ) . }; if ( dirigido ) p1 else ( p1 <+> p2 )

Composición segura en cuanto a tipos

El sistema de tipos Flix garantiza que los valores de los programas Datalog estén correctamente tipados.

Por ejemplo, el siguiente fragmento de programa no realiza comprobación de tipos:

let p1 = Edge ( 123 , 456 ) . ; let p2 = Edge ( "a" , "b" ) . ; p1 <+> p2 ;

porque en p1el tipo del Edgepredicado es Edge(Int32, Int32)mientras que en p2tiene tipo Edge(String, String). El compilador Flix rechaza tales programas por ser mal tipados.

Negación estratificada

El compilador Flix garantiza que cada valor de programa Datalog construido en tiempo de ejecución esté estratificado . La estratificación es importante porque garantiza la existencia de un modelo mínimo único en presencia de negación. Intuitivamente, un programa Datalog está estratificado si no hay recursión a través de la negación, [ 27 ] es decir, un predicado no puede depender negativamente de sí mismo. Dado un programa Datalog, se puede utilizar un algoritmo de detección de ciclos para determinar si está estratificado.

Por ejemplo, el siguiente programa de Flix contiene una expresión que no se puede estratificar:

def main (): #{Male(String), Husband(String), Bachelor(String)} = let p1 = Husband ( x ) : - Male ( x ), not Bachelor ( x ) . ; let p2 = Bachelor ( x ) : - Male ( x ), not Husband ( x ) . ; p1 <+> p2 // ilegal, no estratificado.

porque la última expresión construye un valor de programa Datalog cuyo gráfico de precedencia contiene un ciclo negativo: el Bachelorpredicado depende negativamente del Husbandpredicado que a su vez depende (positivamente) del Bachelorpredicado.

El compilador Flix calcula el grafo de precedencia para cada expresión de programa Datalog y determina su estratificación en tiempo de compilación. Si una expresión no está estratificada, el compilador rechaza el programa.

La estratificación es sólida, pero conservadora. Por ejemplo, el siguiente programa es rechazado injustamente :

def main (): #{A(Int32), B(Int32)} = if ( true ) A ( x ) : - A ( x ), not B ( x ) . else B ( x ) : - B ( x ), not A ( x ) .

El sistema de tipos asume de forma conservadora que ambas ramas de la expresión if pueden ejecutarse y, por consiguiente, infiere que puede existir un ciclo negativo entre los Apredicados B. Por lo tanto, el programa es rechazado. Esto ocurre a pesar de que, en tiempo de ejecución, la mainfunción siempre devuelve un valor de programa Datalog estratificado.

Filosofía del diseño

Flix está diseñado en torno a una colección de principios establecidos: [ 28 ]

  • Todo es una expresión . La mayoría de las construcciones de Flix, excepto las declaraciones de nivel superior, son expresiones que se evalúan a valores.
  • Suposición de mundo cerrado . El compilador de Flix asume que el código fuente de todo el programa está disponible en tiempo de compilación.
  • El código puro e impuro se separa. El sistema de tipos y efectos captura con precisión si una expresión puede producir un efecto [ 29 ].
  • El lenguaje no tiene advertencias en tiempo de compilación, solo errores.

Los principios también enumeran varias características del lenguaje de programación que se han omitido deliberadamente. En particular, Flix carece de soporte para:

  • Valores nulos . En su lugar, se recomienda el uso del tipo de datos Option .
  • Coerciones implícitas . En cambio, las conversiones de tipo deben ser realizadas explícitamente por el programador.
  • Reflexión . El programador no puede reflexionar sobre la estructura del programa en tiempo de ejecución.

Referencias

  1. "Licencia Apache 2.0" . 27 de julio de 2022 vía GitHub .
  2. "Forskningsprojekter" . Danmarks Frie Forskningsfond (en danés).
  3. "Autores de Flix" . GitHub . 27 de julio de 2022.
  4. Leijen, Daan. "Registros extensibles con etiquetas con ámbito". Tendencias en programación funcional .
  5. 1 2 Madsen, Magnus; van de Pol, Jaco (13 de noviembre de 2020). "Tipos polimórficos y efectos con unificación booleana" . Actas de la ACM sobre lenguajes de programación . 4 (OOPSLA): 1–29 . doi : 10.1145/3428222 . S2CID 227044242 . 
  6. 1 2 Madsen, Magnus; Lhoták, Ondřej (13 de noviembre de 2020). "Puntos fijos para las masas: programación con restricciones Datalog de primera clase" . Actas de la ACM sobre lenguajes de programación . 4 (OOPSLA): 125:1–125:28. doi : 10.1145/3428193 . S2CID 227107960 . 
  7. Lucassen, JM; Gifford, DK (1988). «Sistemas de efectos polimórficos». Actas del 15.º simposio ACM SIGPLAN-SIGACT sobre principios de lenguajes de programación - POPL '88 . págs. 47–57 . doi : 10.1145/73560.73564 . ISBN  0897912527. S2CID 13015611 . 
  8. Leijen, Daan (5 de junio de 2014). "Koka: Programación con tipos de efectos polimórficos de fila". Actas electrónicas en informática teórica . 153 : 100–126 . arXiv : 1406.2061 . doi : 10.4204/EPTCS.153.8 . S2CID 14902937 . 
  9. 1 2 "Programación Flix - Puntos de corrección" . flix.dev .
  10. ^ Madsen, Magnus; Sí, Ming-Ho; Lhoták, Ondřej (agosto de 2016). "De Datalog a flix: un lenguaje declarativo para puntos fijos en celosías". Avisos ACM SIGPLAN . 51 (6): 194– 208. doi : 10.1145/2980983.2908096 .
  11. Madsen, Magnus; Lhoták, Ondřej (2018). «Análisis de programas seguro y fiable con Flix». Actas del 27.º Simposio Internacional ACM SIGSOFT sobre Pruebas y Análisis de Software . págs. 38–48 . doi : 10.1145/3213846.3213847 . ISBN  9781450356992. S2CID 49427988 . 
  12. Keidel, Sven; Erdweg, Sebastian (10 de octubre de 2019). "Componentes sólidos y reutilizables para la interpretación abstracta" . Actas de la ACM sobre lenguajes de programación . 3 (OOPSLA): 1–28 . doi : 10.1145/3360602 . S2CID 203631644 . 
  13. Gong, Qing. Extensión de Datalog paralelo con Lattice . Universidad Estatal de Pensilvania.
  14. Yee, Ming-Ho (15 de septiembre de 2016). Implementación de un lenguaje funcional para Flix . Universidad de Waterloo.
  15. "Monomorfizar" . mlton.org .
  16. "Programación de Flix - Interoperabilidad" . flix.dev .
  17. Madsen, Magnus; Zarifi, Ramin; Lhoták, Ondřej (2018). «Eliminación de llamadas recursivas y representación de datos para lenguajes funcionales en la máquina virtual Java». Actas de la 27.ª Conferencia Internacional sobre Construcción de Compiladores . págs. 139–150 . doi : 10.1145/3178372.3179499 . ISBN  9781450356442. S2CID 3432962 . 
  18. Tauber, Tomáš; Bi, Xuan; Shi, Zhiyuan; Zhang, Weixin; Li, Huang; Zhang, Zhenrui; Oliveira, Bruno CDS (2015). "Llamadas de cola eficientes en memoria en la JVM con objetos funcionales imperativos". Lenguajes y sistemas de programación . Notas de clase en ciencias de la computación. Vol. 9458. págs. 11–28 . doi : 10.1007/978-3-319-26529-2_2 . ISBN   978-3-319-26528-5.
  19. "Redundancias como errores de compilación" . flix.dev .
  20. Engler, D. (octubre de 2003). "Uso de redundancias para encontrar errores". IEEE Transactions on Software Engineering . 29 (10): 915– 928. doi : 10.1109/TSE.2003.1237172 .
  21. "flix - Visual Studio Marketplace" . marketplace.visualstudio.com .
  22. "Programación Flix - Efectos" . flix.dev .
  23. "Rust Internals - Flix Polymorphic Effects" . 15 de noviembre de 2020.
  24. "La API de Flix - Lista" . api.flix.dev .
  25. "La API de Flix - Preludio" . api.flix.dev .
  26. Arntzenius, Michael; Krishnaswami, Neel (enero de 2020). "Evaluación seminativa para un lenguaje funcional de orden superior" . Actas de la ACM sobre lenguajes de programación . 4 (POPL): 1–28 . doi : 10.1145/3371090 . S2CID 208305062 . 
  27. Minker, Jack. Fundamentos de bases de datos deductivas y programación lógica . Morgan Kaufmann.
  28. "El lenguaje de programación Flix - Principios" . flix.dev . Consultado el 28 de agosto de 2020 .
  29. "Domando la impureza con efectos polimórficos" . flix.dev .
  • Sitio web oficial
  • Código fuente de la implementación de Flix , alojado en GitHub .