• No se han encontrado resultados

5. Extensi´ on del Fold

5.3. Definici´ on del Fold utilizando Functores

Ser´ıa bueno poder declarar un fold gen´erico que sirva para definir las funciones originales y las funciones extendidas, es decir, declarar un fold cuya definici´on no cambie al extender el tipo de datos sino que permanezca est´atica. Para generalizar el

fold primero debemos abstraer, para ello vamos a seguir un enfoque basado en [16]. Vamos a redefinir el tipoTerm. Primero vamos a generalizarlo, parametriz´andolo con un tipo. Luego definimos unpunto fijo para los constructores:

abstract classTerm[A]

case classNum[A](value: Int)extendsTerm[A]

case classPlus[A](left: A, right:A)extendsTerm[A]

case classFix[F[ ]](in: F[Fix[F]])

El tipo Fix est´a paramertizado con otro constructor de tipo, que a su vez es pa- ram´etrico. Se puede ver al tipo Fix como un constructor que toma como par´ametro un constructor polim´orfico de tipos. El constructorFest´a parametrizado con un tipo que corresponde al de las expresiones de los constructores internos [16].

Tambi´en vamos a definir un sin´onimo de tipo y algunas funciones ´utiles:

typeTermInt = Fix[Term]

defnum = (n:Int) =>Fix[Term](Num(n))

defplus = (l:TermInt, r:TermInt) =>Fix[Term](Plus(l,r))

Las funciones num () y plus() sirven para construir expresiones de tipo Fix de manera mas sencilla, extract() sirve para acceder al constructor que parametriza a Fix. As´ı, podemos crear expresiones del tipo:

valn=plus(num(3),num(4))

Y podemos definir funciones sobre el tipo que utilizan pattern matching:

defcaso(t: TermInt) = extract(t)match{

caseNum(n) =>println(”Es Num!”)

casePlus(l,r) =>println(”Es Plus!”)

}

En su definici´on, el constructor Num no utiliza su par´ametro de tipo, ya que no contiene subexpresiones, s´olo contiene un n´umero entero [16].

Con todas las definiciones anteriores, surge la pregunta ¿Para qu´e redefinir nuestro tipoTermas´ı? Para poder declarar los constructores comofunctoresy as´ı generalizar el fold. Un constructor de tipos F es un functor si posee una funci´on fmap con signatura de tipo:

(A =>B) =>F[A] =>F[B] Y verifica las propiedades:

fmap(id) = id

fmap(f) compose fmap(g) = fmap(f compose g )

Un Functor puede pensarse como una estructura que puede ser mapeada. Si tengo una estructura de tipoF[A]y una funci´onA =>B,fmapme devuelve otra estructura de tipo F[B]. La funci´on fmap es una generalizaci´on de la funci´on map para listas. En Haskell existe una clase de tipos que representa a los functores, la misma est´a de- finida de la siguiente manera:

class Functorfwhere

fmap :: (a−>b)−>f a−>f b

Debemos instanciar los constructores de un tipo algebraico, definiendo la funci´on

fmap para cada uno de ellos. En Scala no existen las clases de tipos como en Haskell, tales requerimientos podemos expresarlos como untrait que declara la funci´onfmap, haciendo que los constructores del tipo implementen esetrait. Esta codificaci´on tiene un estilo m´as orientado a objetos.

Definimos eltrait:

trait Functor[F[ ]] {

deffmap[A, B](f: A =>B)(r: F[A]): F[B]

Y hacemos que nuestro tipoTermimplemente el traitFunctor, es decir, cada cons- tructor del tipoTermva a tener que implementar un m´etodofmap, el c´odigo nos queda:

abstract classTerm[A]extendsFunctor[Term]

case classNum[A](value: Int)extendsTerm[A]{

override deffmap[A, B](f: A =>B)(t: Term[A]): Num[B] = tmatch{

caseNum(n) =>Num(n)

} }

case classPlus[A](left: A, right:A)extendsTerm[A]{

override deffmap[A , B](f: A =>B)(t: Term[A]): Plus[B] = tmatch{

casePlus(l,r) =>Plus(f(l), f(r))

} }

Ahora, con todas las piezas en su lugar, podemos definir la funci´on fold gen´erica para nuestro tipoTerm:

deffoldTerm[A](f: Term[A] =>A)(t: Fix[Term]): A = tmatch{

case(Fix(in)) =>f(in.fmap(foldTerm[A](f))(in))

}

En la nueva definici´on de foldTermno se utiliza pattern matching sobre los cons- tructores, la funci´on est´a definida sobre Term, cuyos constructores est´an definidos como Functores.

Con foldTermpodemos definir las funciones sobre el tipo, para lo cual encapsulamos las funciones que parametrizar´an a foldTermen una clase, para hacerlas extensibles:

classfoldParam{

defevalParam(t: Term[Int]):Int = tmatch{

caseNum(n) =>n

casePlus(l,r) =>l + r

} }

valparam =newfoldParam

defeval = foldTerm(param.evalParam)

Ahora si queremos extender el tipo algebraico, agregamos el nuevo caso, definien- do el m´etodo fmap y extendemos las funciones que parametrizan a foldTerm como sigue:

override deffmap[A , B](f: A =>B)(t: Term[A]): Mul[B] = tmatch{

caseMul(l,r) =>Mul(f(l), f(r))

} }

defmul = (l:TermInt, r:TermInt) =>Fix[Term](Mul(l,r))

classfoldParamExtended1extendsfoldParam{

override defevalParam(t: Term[Int]): Int = t match{

caseMul(l,r) =>l∗r

casedefault =>super.evalParam(default)

} }

valparamExtended =newfoldParamExtended1

defevalExt = foldTerm(paramExtended.evalParam)

De esta manera reutilizamos el c´odigo de las funciones que parametrizan afoldTerm() y la funci´on evalExt() utiliza la misma funci´on foldTerm() que se utiliz´o para definir eval().

Con esta nueva definici´on del tipo Termutilizando un punto fijo, tambi´en podemos definir y extender funciones sin utilizarfoldTerm()siguiendo el esquema mostrado en el cap´ıtulo 3, por ejemplo definimos la funci´on termtoString() sin utilizar fold y la extendemos:

classOp{

deftermtoString(t: TermInt): String = extract(t)match{

caseNum(n) =>n.toString

casePlus(l,r) =>”(”+termtoString(l)+”+”+termtoString(r)+”)”

} }

La extensi´on para manejar el casoMul:

classOpExtendedextendsOp{

override deftermtoString(t: TermInt): String = extract(t)match{

caseMul(l,r) =>”(”+termtoString(l)+”∗”+termtoString(r)+”)”

casedefault =>super.termtoString(t)

} }

Aquella persona con una fuerte influencia de la programaci´on orientada a ob- jetos argumentar´ıa que la codificaci´on de la funci´on foldTerm() es poco elegante y redundante. El “problema” est´a en el llamadoin.fmap(in, foldTerm[A](f)),fmap es una funci´on embebida (m´etodo) del objeto in, que se le pasa como par´ametro el mismo objetoin. Este pasaje no es necesario, se hizo de esta manera para quefmaptenga una signatura de tipos similar a la que se define en la clase de tiposFunctor de Haskell. Siendo Scala un lenguaje que tambi´en es orientado a objetos, podemos acercar la

definici´on de fmap a ese paradigma para evitar ese pasaje de par´ametros, el c´odigo quedar´ıa:

trait Functor[A,F[ ]] {

this: F[A] =>

deffmaps[B](f: A =>B)(r: F[A]): F[B]

final deffmap[B](f: A =>B):F[B] = fmaps(f)(this)

}

abstract classTerm[A]extendsFunctor[A, Term]

case classNum[A](value: Int)extendsTerm[A]{

override deffmaps[B](f: A =>B)(t: Term[A]): Num[B] = tmatch{

caseNum(n) =>Num(n)

} }

case classPlus[A](left: A, right:A)extendsTerm[A]{

override deffmaps[B](f: A =>B)(t: Term[A]): Plus[B] = tmatch{

casePlus(l,r) =>Plus(f(l), f(r))

} }

Y la definici´on de foldTermse compactar´ıa un poco:

deffoldTerm[A](f: Term[A] =>A)(t: Fix[Term]): A = tmatch{

case(Fix(in)) =>f(in.fmap(foldTerm[A](f)))

}

Documento similar