Translate

jueves, 11 de junio de 2020

Guards, Guards!


Haskell proporciona una notación para definir funciones basadas en valores predicados.

fx
  | predicate1 = expression1
  | predicate2 = expression2
  | predicate3 = expression3

Por ejemplo, el valor absoluto de un número es su magnitud, es decir, ignorar su signo. Podría definir una función para calcular el valor absoluto con un condicional if / then / else

absolute x = if (x<0) then (-x) else x

o con guards

absolute x
  | x<0 = -x
  | otherwise = x

Observe cómo no hay un signo igual en la primera línea de la definición de la función, pero hay un signo igual después de cada guard.

La opción por default debe ser la ultima. 

Guards son más fáciles de leer que if/then/else y más si hay más de dos resultados condicionales

Por ejemplo, piense en anotar en el deporte del golf. Para un solo hoyo, un jugador realiza varios golpes. Hay un puntaje "par" para el hoyo, que es el número esperado de golpes.

holeScore :: Int -> Int -> String
holeScore strokes par
  | strokes < par = show (par-strokes) ++ " under par"
  | strokes == par = "level par"
  | strokes > par = show(strokes-par) ++ " over par"

¿Cómo podríamos arreglar esto? Tal vez podríamos convertir la Guard final en otra cosa y también refactorizar con una cláusula where.

holeScore :: Int -> Int -> String
holeScore strokes par
  | score < 0 = show (abs score) ++ " under par"
  | score == 0 = "level par"
  | otherwise = show(score) ++ " over par"
 where score = strokes-par

Observe que la variable de puntaje definida en la cláusula where está dentro del alcance de los tres Guards.

Un valor con un tipo de datos algebraico puede tener una de varias formas diferentes, como una hoja o un nodo, en el caso de las estructuras de árbol. Por lo tanto, para procesar dicho valor necesitamos varios segmentos de código, uno para cada forma posible. La expresión de caso examina el valor y elige la cláusula correspondiente. Es como un Guard, pero selecciona en función de la forma del valor, es decir, coincide con el patrón.

Aquí hay un tipo de datos de suma para mis mascotas.

data Pet = Cat | Dog | Fish

Y así es como saludo a mis mascotas.

hello :: Pet -> String
hello x = 
  case x of
    Cat -> "meeow"
    Dog -> "woof"
    Fish -> "bubble"

Tenga en cuenta que a cada patrón le sigue una flecha y luego un valor. También tenga en cuenta que cada patrón está alineado verticalmente. ¡La sangría realmente importa en Haskell!

Bien, ahora supongamos que queremos hacer que el tipo de datos sea un poco más sofisticado. Agreguemos un loro con un nombre de tipo cadena.

data Pet = Cat | Dog | Fish | Parrot String

hello :: Pet -> String
hello x = 
  case x of
    Cat -> "meeow"
    Dog -> "woof"
    Fish -> "bubble"
    Parrot name -> "pretty " ++ name

Ahora el patrón incluye una variable, que está asociada con el valor concreto para el nombre del Parrot.

hello (Parrot "polly")

De la misma manera que hay un caso general para los Guards, podemos tener un patrón general para un caso. Es el carácter de subrayado, que significa "no me importa" o "coincide con nada"

Entonces podríamos redefinir hola como:

hello :: Pet -> String
hello x =
  case x of
    Parrot name -> "pretty " ++ name
    _ -> "grunt"