GHCi - Parte 1

Posted on September 2, 2017
Tags: , ,

Nota inicial:

Esta es una serie de tutoriales dedicados a explorar el interprete de Glasgow Haskell Compiler o GHC. Bueno, ¿y por qué no empezar con algo básico de Haskell? Principalmente, porque ya conozco lo básico del lenguaje y estoy seguro que pueden encontrar esa información fácilmente buscándola en Internet. Por ejemplo: Aprende haskell por el bien de todos, los blog posts de Silly Bytes, varios video tutoriales en YouTube. Y eso solo en Español, en Inglés encontrarás miles de recursos. No esperes a que creen una máquina como la de Matrix.

Imagen 1. Neo Aprende Haskell

1. Requisitos:

Para este blog post utilicé lo siguiente:

  • stack
  • Stack resolver: lts-9.2
  • GHC-8.0.2 instalado con stack (stack setup)

2. GHCi

GHCi (Glasgow Haskell Compiler Interactive) es el interprete interactivo o REPL para Haskell. En el cual se pueden leer (Read) expresiones, evaluarlas (Eval), imprimir el resultado en pantalla (Print) y repetir estos pasos nuevamente (Loop) mientras se desee. En este blog post revisaré algunas funciones básicas que se pueden realizar con GHCi.

3. Iniciar GHCi

Desde una terminal, en la cual tengas instalado stack, ejecuta stack ghci. Utilicé stack ghci, que carga los módulos de librerías y ejecutables del proyecto de Haskell en el cual me encuentre. Además puedo añadir algunas banderas para extender los componentes que se cargan, por ejemplo: --test, y --bench. Y otros flags, de los cuales podría hacer un blog posts pequeño en algún momento (atento con eso).

$ stack ghci
Configuring GHCi with the following packages:
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /tmp/ghci6756/ghci-script
Prelude>

Y para comenzar, solicitemos algo de ayuda, ejecutando el siguiente comando:

Prelude> :?
 Commands available from the prompt:

   <statement>                 evaluate/run <statement>
   :                           repeat last command
   :{\n ..lines.. \n:}\n       multiline command
   :add [*]<module> ...        add module(s) to the current target set
   :browse[!] [[*]<mod>]       display the names defined by module <mod>
                               (!: more details; *: all top-level names)
   :cd <dir>                   change directory to <dir>
   :cmd <expr>                 run the commands returned by <expr>::IO String
   ...

   UNA LARGA DESCRIPCIÓN DESPUÉS...

   ...

Prelude>

Como se puede observar, la cantidad de información de ayuda es amplia. En este blog post mostraré la parte más básica de GHCi.

4. Lo Básico

4.1 Evaluación de expresiones

GHCi es un interprete o repl para Haskell y como tal lee expresiones, las evalua, imprime el resultado de la evaluación y repite este proceso. Por ejemplo:

Prelude> 3 + 4
7
Prelude>

En el primer punto lee la expresión 3 + 4, luego la evalua, es decir, internamente aplica la suma entre 3 y 4. Luego imprime el resultado, que es 7, y vuelve a esperar que el usuario ingrese más expresiones.

Prelude> let x = 3
Prelude> let y = 4
Prelude> let z = x + y
Prelude> z
7

En el ejemplo anterior simplemente definimos x como 3 y y como 4 y z como el resultado de la suma de ambas cantidades. Y por supuesto, también se puede definir funciones propias, estas también son expresiones. Recuerda que en Haskell todo se maneja por expresiones, más no por sentencias. Como tal vez se haya visto en lenguaje imperativos.

Prelude> let myFunc x y = x + y
Prelude> myFunc 3 4
7

4.2 Comandos básicos del prompt

Los siguientes comandos los voy a copiar y traducir directamente de What I Wish I Knew When Learning Haskell y extenderé un poco la lista con algunos comandos faltantes:

Comando Atajo Descripción
:reload :r Recargar código
:type :t Inspeccionar un tipo
:kind :k Inspeccionar un kind
:info :i Información adicional
:print :p Imprimir una expresión
:edit :e Cargar un archivo en el editor del sistema
:load :l Asignar el módulo Main en el REPL
:add :ad Cargar un archivo en el namespace del REPL
:browse :bro Navegar por todos los símbolos del REPL
:cd :cd Cambiar de directorio (provoca que se liberen los módulos cargados en REPL)

A contiación, se crea un proyecto simple con stack para jugar un poco con GHCi.

$ stack new ghci-test
...

$ stack ghci
ghci-test-0.1.0.0: initial-build-steps (lib + exe)
The following GHC options are incompatible with GHCi and have not been passed to it: -threaded
Configuring GHCi with the following packages: ghci-test
Using main module: 1. Package `ghci-test' component exe:ghci-test-exe with main-is file: /tmp/ghci-test/app/Main.hs
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Lib              ( /tmp/ghci-test/src/Lib.hs, interpreted )
Ok, modules loaded: Lib.
[2 of 2] Compiling Main             ( /tmp/ghci-test/app/Main.hs, interpreted )
Ok, modules loaded: Lib, Main.
Loaded GHCi configuration from /tmp/ghci10117/ghci-script
*Main Lib>

El proyecto tiene un componente principal que se encuentra en app/Main.hs y es un modulo de Haskell que está definido como principal en el ejecutable del archivo ghci-test.cabal. Ahora sí, a jugar un poco:

*Main Lib> -- Podemos ejecutar las funciones definidas en nuestra librería
*Main Lib> someFunc
someFunc
*Main Lib> -- En este caso, solo era una función que imprime "someFunc"
*Main Lib> -- También podemos ejecutar la función principal (main)
*Main Lib> main
someFunc
*Main Lib> -- Que en este caso solo llama a someFunc (lol)

En las siguientes expresiones se está inspeccionando el tipo de cada una de ellas.

*Main Lib> :t "Hello world!"
"Hello world!" :: [Char]
*Main Lib> :t someFunc
someFunc :: IO ()
*Main Lib> :t map
map :: (a -> b) -> [a] -> [b]

Veamos algunos kinds:

*Main Lib> :kind Maybe
Maybe :: * -> *
*Main Lib> :kind Maybe Int
Maybe Int :: *
*Main Lib> :kind Either
Either :: * -> * -> *

Yeah! Ahora algo de información adicional.

*Main Lib> :info Maybe
data Maybe a = Nothing | Just a         -- Defined in ‘GHC.Base’
instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Ord a => Ord (Maybe a) -- Defined in ‘GHC.Base’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Foldable Maybe -- Defined in ‘Data.Foldable’
instance Traversable Maybe -- Defined in ‘Data.Traversable’
instance Monoid a => Monoid (Maybe a) -- Defined in ‘GHC.Base’

*Main Lib> :info Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  {-# MINIMAL fmap #-}
        -- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

*Main Lib> :i someFunc
someFunc :: IO ()       -- Defined at /tmp/ghci-test/src/Lib.hs:6:1

Podemos ver que al utilizar :info (o :i) sobre un tipo de datos, nos da información sobre sus constructores y las instancias que están cargadas en el REPL actualmente. Si lo hacemos sobre una clase de tipos (type class) podremos observar el kind del tipo que espera y las funciones que define la clase y sus definiciones mínimas, las instancias de esa clase y el módulo en el cual están definidas. ¡Vaya que es información útil! Y al hacerlo sobre expresiones (e.g. funciones) nos mostrará el lugar en el cual están definidos. ¡Ojo!, las instancias que se muestran están siendo filtradas, para mostrar todas se debe utilizar :info! (no existe atajo para esta opción).

Y con :browse se puede ver las expresiones definidas en un módulo.

*Main Lib> :browse Lib
someFunc :: IO ()

En este caso solo se ve someFunc. Pero también se podrían ver tipos de datos (y sus constructores), clases de tipos, etc.

En otro blog post hablaré sobre :load, :add, :show y :set. Ya que me gustaría hablar sobre la forma en la cual se cargan los módulos, el namespace de ghci, las distintas opciones que nos proporciona :show, y como :set nos permite crear nuestros propios comandos. Bueno, hasta ahora he mostrado algunos de los comandos que vienen por defecto en el repl. Ojalá te haya servido de algo, tanto como a mi.

Comentarios, sugerencias, preguntas, etc. Son bienvenidos. >:-D ByE!