martes, abril 15, 2008

Convertir de CSV a String Array

La primera vez pensé que era cuestión de

File.ReadAllLines(Filename).Map(\x -> Split(","))

Pero eso no cubre los casos "raros". Y eso es que según la wiki y mas específicamente la RFC 4180 esta el caso de poder incluir separadores dentro de una entrada. Poder incluir saltos de línea e incluir comillas dobles (").

En F# resulta muy fácil. Uno solo hecha mano de FSLex y FSYacc.

La definición del lexer:


    8 rule token = parse

    9 | ',' {SEPARATOR}

   10 | '"' {QUOTE}

   11 | '\n'| '\r' '\n' {NEWLINE (Lexing.lexeme lexbuf)}

   12 | eof {EOF}

   13 | _ {OTHER (Lexing.lexeme lexbuf)}



El Parser:


   17 start:

   18     Line  {$1}

   19 

   20 Line:

   21     Entry  {[$1]}

   22     | Entry SEPARATOR Line  {$1::$3}

   23 

   24 Entry:

   25     | Text                   {$1}

   26     | QUOTE QuotedText QUOTE {$2}

   27 

   28 Text:

   29                         {new System.Text.StringBuilder()}

   30     | Text OTHER        {$1.Append($2)}

   31 

   32 SubField:

   33     | OTHER                {$1}

   34     | SEPARATOR            {","}

   35     | NEWLINE               {$1}

   36 

   37 QuotedText:

   38                             {new System.Text.StringBuilder()}

   39     | QuotedText SubField    {$1.Append($2)}

   40     | QuotedText QUOTE QUOTE {$1.Append(@"""")}



Y listo.

Haciendo esto me tropecé con unas cosas que se me había olvidado que existían.

Reduce-Reduce conflict y Shift-Reduce conflict. Inicialmente estaba haciendo parsing de todo el archivo. Pero que pasa si la primera línea está mala? mejor hacer el trabajo bajo demanda (Record por Record). Eso resuelve Los conflictos
Uno hace un Wrapper de 10 líneas y después puede uno generar una Secuencia (una especie de Generador, o lista por comprensión)


   26 let readCSV (filename:string) encoding =

   27     seq {use text = new StreamReader(filename)

   28         let csv = new CSVParser(text, encoding)

   29         while not csv.EndOfStream do

   30             yield csv.ReadRecord()

   31     }



Quiero verlos haciendo esto en Java!!

martes, abril 08, 2008

Parentheses are evil

A veces la legibilidad del código es subestimada, sin embargo es tal vez de las cosas mas importantes.

Últimamente he estado trabajando mucho en F# y me he topado por accidente con pedazos código que me gustan no por lo elegantes sino por su legibilidad.

Digamos que tenemos un código en un pseudo-Java-C#


   35 public static IElementsContainer logout(IElementsContainer ie){

   36   IElementsContainer logout = Helpers.elementById("LoginStatus1", ie);

   37   if(logout.Exists){

   38     logout.click();

   39   }

   40   return ie;     

   41 }



Y lo comparamos con esto:


   38 let logout ie =

   39     let logout = elementById "LoginStatus1" ie

   40     if logout.Exists then click logout

   41     ie



Me voy a hacer el bobo con el ahorro en caracteres. Pregunto... ¿Soy el único al que le parece la linea 40 del segundo ejemplo hermosa?

Esta linea es posible solo porque en F# como en muchos lenguajes funcionales, se utilizan funciones a la Curry. Se podría reproducir el efecto en lenguajes imperativos como Ruby dado que los paréntesis resultan opcionales. El ejemplo anterior es sencillo, pero no es nada sorprendente.

Ahora un caso típico, Digamos que tenemos un objeto que posee un estado y que debemos realizar una serie de operaciones sobre este que cambian su estado. Y supongamos que el orden es importante. Las funciones retornan una referencia al objeto modificado.

Esta sería la forma cerda:


   40 

   41 public IElementContainer login(String user, String passwd,

   42                                IElementContainer ie){

   43 

   44     return clickById("Login1_LoginButton",

   45             typeById("Login1_Password", passwd ,

   46             typeById("Login1_UserName", user ,

   47               logout(ie)));

   48 

   49 }



Legibilidad Nula. Pero digamos que el que escribió eso se sintió bien porque "se ahorro los punto y comas":

Ahora hagamoslo mas "imperativo".


   41 public IElementContainer login(String user, String passwd,

   42                                IElementContainer ie){

   43     ie.logout(ie);

   44     ie.typeById("Login1_UserName",user);

   45     ie.typeById("Login1_Password", passwd); 

   46     ie.clickById("Login1_LoginButton");

   47     return ie;

   48 }



Digamos que legible. Y F#? podemos implementar algo parecido a la primera solución pero que se deja leer en el mismo sentido que ocurren los sucesos.


   41     let login user passwd ie =

   42         logout ie 

   43         |> typeById "Login1_UserName" user

   44         |> typeById "Login1_Password" passwd 

   45         |> clickById "Login1_LoginButton"



Él operador |> es como el pipe de shell. El resultado se lo pasa a la funcion de la derecha.

<3


PD: Los ejemplos son sacados de mu ultima experiencia trabajando con F#, Watin y NUnit. WatiN es un API para Testing Funcional (Funcional de Funcionalidad no de FP). Pese a la poca documentación de WatiN lo recomiendo en forma enorme.