sábado, 13 de enero de 2018

Comparando F# con C# parte 3

Vamos ha hacer un programa que baje una pagina en F# y en C# para comparar.

Veamos la implementación en F# :

// open es similar al using
open System.Net
open System
open System.IO

// Obtener los contenidos de una página web
let fetchUrl callback url =     
    let req = WebRequest.Create(Uri(url))
    use resp = req.GetResponse()
    use stream = resp.GetResponseStream()
    use reader = new IO.StreamReader(stream)
    callback reader url

Repasemos este código:

  • El "open" en la parte superior nos permite escribir "WebRequest" en lugar de "System.Net.WebRequest". Es similar a un encabezado "using System.Net" en C#.
  • A continuación, definimos la función fetchUrl, que toma dos argumentos, un callback para procesar la pagina y la URL a buscar.
  • Luego, creamos una Uri con la URL. F# tiene una estricta comprobación de tipos, por lo tanto, si en  hubiéramos escrito: let req = WebRequest.Create(url), el compilador se habría quejado de que no sabía qué versión de WebRequest.Create usar.
  • Al declarar los valores de respuesta, transmisión y lectura, se usa la palabra clave "use" en lugar de "let". Esto solo se puede usar junto con las clases que implementan IDisposable. Le dice al compilador que elimine automáticamente el recurso cuando se sale del alcance. Esto es equivalente a la palabra clave "using" C#.
  • La última línea llama a la función callback con StreamReader y la url como parámetros. Note que nunca especificamos el timpo del callback. 


Ahora veamos la implementación equivalente de C#:

class WebPageDownloader
{
    public TResult FetchUrl<TResult>(
        string url,
        Func<string, StreamReader, TResult> callback)
    {
        var req = WebRequest.Create(url);
        using (var resp = req.GetResponse())
        {
            using (var stream = resp.GetResponseStream())
            {
                using (var reader = new StreamReader(stream))
                {
                    return callback(url, reader);
                }
            }
        }
    }
}

Como de costumbre, la versión C # tiene más 'ruido'.

  • Hay diez líneas solo para llaves, y existe la complejidad visual de 5 niveles de anidación *
  • Todos los tipos de parámetros deben declararse explícitamente, y el tipo TResult genérico debe repetirse tres veces.
Podemos probar el codigo de la siguiente manera : 


let myCallback (reader:IO.StreamReader) url = 
    let html = reader.ReadToEnd()
    let html1000 = html.Substring(0,1000)
    printfn "Downloaded %s. First 1000 is %s" url html1000
    html      // return all the html

//test
let google = fetchUrl myCallback "http://google.com"

Finalmente, tenemos que recurrir a una declaración de tipo para el parámetro del lector (reader:IO.StreamReader). Esto es necesario porque el compilador F# no puede determinar el tipo del parámetro "reader" automáticamente.

Una característica muy útil de F# es que puede "pasar" parámetros en una función para que no tengan que pasarse en todo momento. Esta es la razón por la que el parámetro url se colocó en último lugar en lugar de primero, como en la versión de C#. La devolución de llamada se puede configurar una vez, mientras que la URL varía de llamada a llamada.

// build a function with the callback "baked in"
let fetchUrl2 = fetchUrl myCallback 

// test
let google = fetchUrl2 "http://www.google.com"
let bbc    = fetchUrl2 "http://news.bbc.co.uk"

// test with a list of sites
let sites = ["http://www.bing.com";
             "http://www.google.com";
             "http://www.yahoo.com"]

// process each site in the list
sites |> List.map fetchUrl2 

La última línea (usando List.map) muestra cómo la nueva función se puede usar fácilmente junto con las funciones de procesamiento de listas para descargar una lista completa a la vez.

Aquí está el código de prueba C# equivalente:

[Test]
public void TestFetchUrlWithCallback()
{
    Func<string, StreamReader, string> myCallback = (url, reader) =>
    {
        var html = reader.ReadToEnd();
        var html1000 = html.Substring(0, 1000);
        Console.WriteLine(
            "Downloaded {0}. First 1000 is {1}", url,
            html1000);
        return html;
    };

    var downloader = new WebPageDownloader();
    var google = downloader.FetchUrl("http://www.google.com",
                                      myCallback);
            
    // test with a list of sites     var sites = new List<string> {
        "http://www.bing.com",
        "http://www.google.com",
        "http://www.yahoo.com"};

    // process each site in the list   
   sites.ForEach(site => downloader.FetchUrl(site, myCallback));
}

De nuevo, el código tiene más ruido que el código F#, con muchas referencias explícitas de tipo. Lo que es más importante, el código C# no le permite pasar fácilmente algunos de los parámetros en una función, por lo que la devolución de llamada se debe referenciar explícitamente cada vez.