Monthly Archives: May 2020

Haskell basics – Modules

Note: I’ve writing these Haskell blog posts whilst learning Haskell, so do not take them as expert guidance. I will amend the post(s) as I learn more.

Before we begin, let’s use cabal init to create a starter project, then we’ll add our code to this project as part of this post. This will create a Main.hs with the classic “Hello World” type of sample along with the Setup.hs and the .cabal file.

Modules

Apart from the simplest applications we would probably want to package functions, types etc. into modules. Modules exist in most languages, such as those in F#, TypeScript or similar to namespaces in C# & Java. In Haskell each module is stored within it’s own file and the file begins with the following declaration

module ModuleName where

Note: module names should be in PascalCase.

Obviously replacing the ModuleName with your module name and, similar to Java, the ModuleName should be made up of the path to the module file, ending with the module name. For example let’s assume we’re going to create a Calculator module and our application folder is HaskellBasics, let’s assume we’ve created a Modules folder off of this and within that a Calculator.hs file, hence the ModuleName should look like this

module Modules.Calculator where

We need to add the module to the .cabal build file using the other-modules key, for example

executable HaskellBasics
  main-is:             Main.hs
  other-modules:       Modules.Calculator

Note: We don’t include the file extension for the Calculator module.

If we add more modules then we simply add as comma separated values in the other-modules field in the .cabal file, i.e.

  other-modules: Modules.Calculator, Modules.Output

To import modules into our Main.hs we use the import keyword, like this (we’re importing two modules here).

import Modules.Output
import Modules.Calculator

There’s a lot more we can do in terms of importing modules, for example importing only some functions, like this

import Modules.Calculator (add, sub)

We can also import all function except for some, essentially hiding the module functions, like this

import Modules.Calculator hiding (sub)

The ability to specify the functions available and those not available is useful to help with name clashes etc. Another way to handle such name clashes is to use the module qualified names, for example

main = print (Modules.Calculator.add 10 6)

We can also enforce qualified module names for each function within a module be importing out module like this

import qualified Modules.Calculator

and hence all uses of the functions within this module must be fully qualified.

This can end up requiring a lot more verbosity than we really want, so we can import a qualified module and alias it like this

import qualified Modules.Calculator as C

meaning we can use the function like this

main = print (C.add 10 6)

Note: The alias for the module name must start with a capital letter.

Setting up a Haskel development environment on Windows 10

I wanted to try out Haskell on Windows 10 and figured I’d probably use Visual Code for writing code, here’s the steps to get everything installed and configured.

The simplest way to get everything installed seems to be using chocolatey, see Getting Started.

  1. Install chocolatey
  2. Run Powershell in admin mode and then run choco install haskell-dev
  3. Finally, from Powershell run refreshenv

If all of these steps worked, then just run ghc from the Powershell or a command prompt and you should see some output from ghc.

Let’s now set-up Visual Code (I’ll assume you’ve already got Visual Code installed). There’s a few plugins/extensions for Visual Code for use with Haskell. From Visual Code extensions option…

  • Add Haskell syntax highlighter
  • Also add Haskell-linter
  • To configure the linter, from Visual Code select, File | Preferences | Settings, switch to the code view and add the following
    "haskell.hlint.executablePath": "C:\\ProgramData\\chocolatey\\lib\\ghc\\tools\\ghc-8.10.1\\bin\\ghc.exe"
    

    Chocolatey installs the files into C:\ProgramData\chocolatey\lib, however if you’ve installed ghc by some other means of within another folder then replace the folder with your location.

Finally, let’s write a Haskell “Hello World”. Within Visual Code create the file HelloWorld.hs and add the following code

main = putStrLn "Hello, world!"

Open the Visual Code integrated terminal or open your preferred terminal/shell and from the folder that you saved your HelloWorld.hs file, run

ghc HelloWorld.hs

If all goes to plan you’ll compile the Haskell file to a HelloWorld.exe and you’re done for now.

Websockets and Kestrel

In my post Websockets with Fleck we looked at using Fleck to create a websocket based server, let’s not turn our attention to integrating websockets with an ASP.NET core application using Kestrel.

This is NOT meant to implement anything near as complete as the Fleck library, but is just an example of how we might implement websockets in a Kestrel application and we’re going to try to emulate the code we had for that Fleck example.

  • Create an ASP.NET Core Web Application
  • Select the Empty template

Let’s clean out the Properties | launchSettings.json by remove the iisExpression and IIS Express profile, so mine looks like this

{
  "profiles": {
    "YOUR_APP_NAME": {
      "commandName": "Project",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Obviously keep your application name in the YOUR_APP_NAME string.

Now in Program.cs we’ll add code to allow us to use the 8181 port, so the CreateHostBuilder method should now look like this

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
      webBuilder.UseStartup<Startup>();
      webBuilder.UseUrls("http://*:8181");
    });

Delete everything within the Startup.cs’s Configure method and replace with

app.UseWebSockets();

this adds the websocket middleware.

We’re actually going to then create our own middleware to handle web socket requests, so
let’s create the file WebSocketManagerMiddleware.cs. Here’s the code…

public class WebSocketManagerMiddleware
{
  private readonly RequestDelegate _next;
  private readonly WebSocketConnection _connection;

  public WebSocketManagerMiddleware(
    RequestDelegate next, 
    WebSocketConnection connection)
  {
    _next = next;
    _connection = connection;
  }

  public async Task InvokeAsync(HttpContext context)
  {
    if (context.WebSockets.IsWebSocketRequest)
    {
      var socket = await context.WebSockets.AcceptWebSocketAsync();

      _connection.OnOpen(socket);

      await Receive(socket, (result, buffer) =>
      {
        switch (result.MessageType)
        {
          case WebSocketMessageType.Text:
            var s = Encoding.UTF8.GetString(buffer);
            _connection.OnMessage(socket, s.Substring(0, Math.Max(0, s.IndexOf('\0'))));
            break;
          case WebSocketMessageType.Binary:
            _connection.OnBinary(socket, buffer);
            break;
          case WebSocketMessageType.Close:
            _connection.OnClose(socket);
            break;
        }
      });
    }
    await _next(context);
  }

  private async Task Receive(
    WebSocket socket, 
    Action<WebSocketReceiveResult, 
    byte[]> handler)
  {
    var buffer = new byte[1024];

    while (socket.State == WebSocketState.Open)
    {
      var result = await socket.ReceiveAsync(buffer: 
        new ArraySegment<byte>(buffer),
        cancellationToken: CancellationToken.None);

      handler(result, buffer);
    }
  }
}

Middleware expects an Invoke or InvokeAsync method that returns a Task. In our example, we firstly ensure this is a websocket request before accepting the request. In this example we pass in a WebSocketConnection instance (we’ll have a look at that next), but basically this middleware intercepts the websockets and then calls the WebSocketConnection class in a manner similar to the way our Fleck server was implemented, i.e. using OnOpen, OnClose, OnMessage and OnBinary calls.

At the end of the code we pass the context through to the next piece of middleware in the pipeline.

The reason we have a WebSocketConnection class is to just give us an abstraction for creating our actual application websocket code.

Add the file WebScocketConnection.cs, this is going to expose OnOpen, OnClose etc. extension points as well as a SendAsync method for sending data to the connected client, here’s the code

public class WebSocketConnection
{
  public void Start(Action<WebSocketConnection> connection)
  {
    connection(this);
  }

  public Action<WebSocket> OnOpen { get; set; } = 
    webSocket => { };
  public Action<WebSocket> OnClose { get; set; } = 
    webSocket => { };
  public Action<WebSocket, string> OnMessage { get; set; } = 
    (webSocket, message) => { };
  public Action<WebSocket, byte[]> OnBinary { get; set; } = 
    (webSocket, bytes) => { };

  public async Task SendAsync(WebSocket socket, string message)
  {
    if (socket.State == WebSocketState.Open)
    {
      await socket.SendAsync(
        new ArraySegment<byte>(Encoding.ASCII.GetBytes(message),
          0,
          message.Length),
        WebSocketMessageType.Text,
        true,
        CancellationToken.None);
      }
    }
  }
}

Finally let’s return to Startup.cs and the Configure method, here’s the full code

var websocketServer = new WebSocketConnection();
websocketServer.Start(connection =>
{
  connection.OnOpen = socket => Console.WriteLine("OnOpen");
  connection.OnClose = socket => Console.WriteLine("OnClose");
  connection.OnMessage = async (socket, message) =>
  {
    Console.WriteLine($"OnMessage {message}");
    await connection.SendAsync(socket, $"Echo: {message}");
  };
  connection.OnBinary = (socket, bytes) => 
    Console.WriteLine($"OnBinary {Encoding.UTF8.GetString(bytes)}");
});

app.UseWebSockets();
app.UseMiddleware<WebSocketManagerMiddleware>(websocketServer);

References

WebSockets support in ASP.NET Core
Write custom ASP.NET Core middleware