F# for the cloud worker - part 2 - AWS SDK for .NET
Tags: fsharp, functional, cloud, aws
Published: 2020-09-20 19:49:40

In [part 1](https://dev.to/eriklz/f-for-the-cloud-worker-part-1-26c6) we got our feets wet with some initial
simple Hello Cloud scripts, using F#. This was then expanded to do actual connectivity to AWS and list S3
buckets in an AWS account.

In this part, we will sidetrack a bit into the topic of developer workflow and then continue with more exploration of AWS services and get a bit more into F#, with scripts
to retrieve server information. This will be a step-but-step process, start simple and add features.

My description here aims at documenting a workflow where the script is developed, since I do believe that it is important to establish an (enjoyable) approach to develop code, in whatever language that may be. In this case, it is F#.

## Developer workflow

### IDE integration with F# Interactive REPL

In part 1 I mentioned only briefly about different IDEe (integrated development environments) with F# support
and my own preference with Jetbrains tools. I think a key element of a good developer workflow is a fast feedback
loop and ease of experimentation - a REPL-type environment like FSI is essential for this.
For me, I think the REPL-driven development process in Clojure has set kind of a gold standard for how things
should be in that area. Functional languages provide a good foundation for allowing such workflows, given
the focus on immutability and functional composition.

In both Jetbrains Rider, Visual Studio and Visual Studio Code (with the Ionide F# plugin) you can send expressions to
FSI through a simple keyboard shortcut or menu command and the expression will be evaluated in FSI and you can
also interact with the FSI REPL directly. This makes it quite easy to interact and experiment with code as you
write it and can get essentially immediate feedback.

It is a little bit different still from what I am used to with Clojure, but quite good and useful to fiddle around and test things.

Below is a very simple example with Hello Cloud of interaction with the REPL in Visual Studio Code, which I have switched to from Jetbrains Rider for the time being. For F# script development, I found the VS Code experience more enjoyable.

![Interacting with the F# REPL in the IDE](https://dev-to-uploads.s3.amazonaws.com/i/avuiba6bxxzdbs5c8j7g.gif)

* I can select the **hello** function and then press _Alt-Enter_ to send the selection to F# Interactive - which is started automatically if it is not already running. The selection is evaluated in the REPL.
* I have a few example calls at the bottom of the file, inside a comment block. If I press _Alt-Enter_ without selecting anything, it will send the contents of the line to the REPL and it gets evaluated and I see the result immediately.
* This way there is a kind of scratch area for doing immediate feedback testing, which also is persisted

This is not a replacement for actual unit testing test suites, but a nice low overhead complement.

### Using F# 5 features

In [part 1]((https://dev.to/eriklz/f-for-the-cloud-worker-part-1-26c6)) we took advantage of a new feature of F# 5, which allows us to specify dependencies to our scripts, which will be downloaded 
by the .NET package manager (NuGet) automatically, if needed.

This is quite handy when developing standalone scripts for performing tasks towards our cloud provider
of choice, like our simple `HelloS3.fsx` script in the previous blog post. While it worked fine to _run_
these scripts, we did not look at how this was supported in our developer workflow. 
If you had worked with these scripts in some IDE with F# support, it may not have understood these new commands
for referencing dependencies. So in this case, make sure that the F# support in your editor/IDE of choice supports F# 5.

### AWS credentials handling

The next part in enabling a workflow in the IDE with the F# Interactive REPL for communicating with a cloud provider is how to access the credentials set up. Previously we relied on being able to set environment variables to
be able to access the correct AWS profile.

We can do the same in the IDE. However, I did not find a way to configure which environment variables to set when 
an F# Interactive REPL process is started and we just send the selected code to the REPL.
It is possible to set environment variables like AWS_PROFILE by setting the environment variable when we start the 
IDE, e.g. when starting Visual Studio Code from the command line.
Processes started by Visual Studio Code will inherit the environment variables. So it can be started like this:

```bash
prompt> AWS_PROFILE=myprofile code
```

The path to Visual Studio Code needs to be added to the search path for the command line first also.
It is not ideal, but works. 

It is also possible to add code in the scripts to set the necessary environment variable or fetch credentials 
from an AWS profile explicitly 

Adding a line

```fsharp
do Environment.SetEnvironmentVariable ("AWS_PROFILE", "myprofile")
```

before instantiating any client object would be one simple way to get basically the same effect - but probably not hardcode the value... It is a manageable problem anyway.

**Note:** The function Environment.SetEnvironmentVariable is something that is provided by .NET Core and is not something specific to F#. The way the function is called can give a hint there. It takes two parameters, but instead of being called as 

```fsharp
Environment.SetEnvironmentVariable "AWS_PROFILE" "myprofile"
``` 

it is called as 

```fsharp
Environment.SetEnvironmentVariable ("AWS_PROFILE", "myprofile")
```

It is in fact not two parameters that are sent to the function call, but instead a single parameter which is a [tuple](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/tuples) with two values. 
Since the other official .NET languages do not support partial function application like F# do and have to provide all required
parameters in a call, I guess this is the approach taken in F# to interoperate with functions adapted for those other languages.

## ShowServers.fsx

Our next task here is to show some information about virtual machines in AWS (a.k.a. EC2 instances). The first iteration should be simple, just show something about every EC2 instance in an AWS account in a specific region. We have the [AWS SDK for .NET API docs](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/Index.html) and we can do a similar starting point that we did with `HelloS3.fsx` in [part 1](https://dev.to/eriklz/f-for-the-cloud-worker-part-1-26c6).

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System.Environment
open Amazon.EC2
open Amazon.EC2.Model

let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }
    
let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    response
    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let response = showServers [| "erik" |]
*)
```
The structure is fairly similar to our previous S3-based example - create a client object for EC2 and call the function that
should return EC2 instance information. At this point, we also just return that result.
An addition is to take a string array as arguments, which we could pass to the script if called, and use the first 
argument as the AWS profile name, if it is provided.

The workflow I use here is that the code here is sent from the editor to the F# Interactive REPL and try out various things
as the code is developed. The comment block at the end contains a few expressions that I add there while developing and testing
the code.

So we can use one expression with the REPL to call **showServers** and see if it works (see below).

![First tests](https://dev-to-uploads.s3.amazonaws.com/i/hhzp75e1ne8k33ofaiiw.gif)

We can use the autocompletion of the editor (VS Code) to see what fields we have in the response and check what is in there.
It seems from the first tests here that the call to AWS was successful and we have a field _Reservations_ which is a list of
_Reservation_ - and that list seems to be empty. Which in this case is correct, since the account and region I connected to
does not have any EC2 instances at all.

So time to create some...

---

### EC2 instances created

I created two EC2 instances and now when calling the function and inspecting the result, this is the result:

![Two instances returned](https://dev-to-uploads.s3.amazonaws.com/i/2f0d5a7bl6vpekev87b4.png)

There are two _Reservation_ and each seems to contain a sequence of _Instance_, which should be the actual machines.
The rest of the information under _Reservation_ we do not bother with for the time being. So we iterate over the list
of _Reservation_ objects and in each of these, we get all the _Instance_ objects. The result from that should be a single
list of _Instance_ objects.

A new update of the code adds this type of functionality:

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System.Environment
open Amazon.EC2
open Amazon.EC2.Model


let getInstances (reservations: Reservation list) =
    reservations |> List.collect (fun x -> List.ofSeq x.Instances)


let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }


let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    if response.Reservations.Count = 0 then
        List.empty<Instance>
    else
        (List.ofSeq response.Reservations) |> getInstances

    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let mylist = List.ofSeq response.Reservations
  let response = showServers [| "erik" |]
*)
```

The _Reservations_ list is converted to a generic F# list and then provided as input to the new function **getInstances**.
This function calls the **List.collect** function, which iterates over a list and calls a function for each element
in the list. The result from that function call is expected to be a list itself. This list of lists is then flattened to a single list.
The function that is called is an _anonymous function_ which we have defined using the **fun** keyword as
`(fun x -> List.ofSeq x.Instances)`. The function takes a single argument, which in our case is a _Reservation_ object.
From this object, we retrieve the list of instances from the _Instances_ field and convert that to a generic F# list and
return that as the result of the function.

In our case we have also added a condition to only call **getInstances** if the reservations list is not empty, otherwise, we return an empty list of type _Instance_. With two instances we can see that we get some appropriate data back.

![Instance info is returned](https://dev-to-uploads.s3.amazonaws.com/i/7ep2tye1kw4fdxm5dkl6.png)

### Extracting instance informastion

So now when we see that we can get _Instance_ objects it is time to extract some information out of it. There is a lot of
information available for an instance, but not all of that may be useful at first glance. A couple of things comes to mind
that could be useful:

* The id of the instance
* The name of the instance, if that exists
* The type of instance (EC2 has type names that have specific properties such as CPU, RAM, etc)
* The private IP address and/or DNS name
* When it was launched
* The current state (running, stopped etc)

We can expand on that list later, but this looks like a reasonable starting point. So we should then have some kind of record in which we can store the information we want for each instance. A first stab at defining such a record structure could be:

```fsharp
type InstanceInfo =
    { InstanceId:       string
      Name:             string
      InstanceType:     string
      PrivateIpAddress: string
      LaunchTime:       DateTime
      State:            string }
```

Most of it is strings to start with right now. We could arguably refine this further and we will, but this is what we start with. For this then, we need a function that creates an _InstanceInfo_ record from an _Instance_ object. For all these fields
except the **Name** field, this is trivial. The **Name** field is the more tricky one since it is not mandatory and is
represented through a tag on the instance which is called "Name".
So first just skip the Name field issue and leave it empty and get an initial version. Notice here that the curly brackets
are used to group the fields of the _InstanceInfo_ record instance we create - it is not any code brackets as in some other languages! Notice also that we have not specified anywhere that this is an _InstanceInfo_ that is returned - F# figures that
out by itself given the shape of the record. 

```fsharp
let getInstanceInfo (instance: Instance) =
    {
        InstanceId = instance.InstanceId
        Name = ""
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }
```

and test that in the REPL:

![Test getInstanceInfo](https://dev-to-uploads.s3.amazonaws.com/i/27kvu40rxl6j7ebjqnkz.png)

so now deal with the issue with the Name field - if we keep the Name field as an empty string if the Name tag does not
exist, then we need to iterate through the list of tags and if there is a tag with its key-value "Name", then we take the
value of that tag and set that as the **Name** field.

#### Finding the _Name_ tag

Let us break down what we want to do here into a few steps:

1. We want to be able to iterate over all tags, so we want an iterable structure. We have a .NET:y list right now,
   but want to make it a more F#:y one. It could be handled already as in F# sequence, or we could have an F# list or an F# array. 
2. Once we have the iterable structure, we want to go through it and for each element check if it is the Name tag. If that is the case, we return the value of the tag. if we do not find the Name tag, we return an empty string.
3. We use the result of the search above to initiate the Name field returned by **getInstanceInfo**.

Note here that we want to work with immutable data, so some constructs that may be more common in other object-oriented and imperative languages we will not use - even if F# technically allows us to do that. One example there is that we will not create
a default structure of InstanceInfo with an empty Name field and then after that populate it with a different value. We will
set the value to the correct one right from the start.

Among the EC2 instances I created, I happened to name one of them "Tiny Instance". We can create a list of _Tag_ objects
and check the content in the REPL:

![Check tags list](https://dev-to-uploads.s3.amazonaws.com/i/e0x2kictdf5et31pddpm.png)

Cool, the first instance was that particular EC2 instance it seems! How do we then find the value of the Name tag?
It does not have to be at the first position in the list, even though it is so in this case.
The **List** module has a few functions that we could use to find it - one way would be to get the length and then
iterate over each element using an index to access each element. This is not optimal, even though this may be a way
to approach it in some other languages.

Another way though it breaks down the problem into primary cases:

1. What do we do if get an empty list? We return an empty string.
2. What do we do if the first element in the list is the Name tag? We return the value of that tag.

These two cases we can handle using what is called a _match expression_. Let us construct a function **getTagValue**
which handles those naive cases:

```fsharp
let getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value else ""
```

The _match expression_ is a quite powerful construct. Between **match** and **with** the expression that should be
matched is put. Then there is a list of pattern expressions to match that with, prefixed with **|** operator and after the
**->** operator the result expression that is to be executed if there is a match is put.

So, in this case, we specify an empty list ( **[]** ) and if there is a match for that, we return an empty string.
The second case was if the first item in the list was the name tag. In the pattern expression, we specify the cons
operator ( **::** ) with two names, one on each side - _head_ and _tail_. The cons operator is a way to describe a
list as _the first element and the rest of the list_. In this has, **head** is the first element in the list and **tail**
is the rest of the list. F# will do this matching and assign the first element to the name **head** and the rest of the list
to the name **tail**.

So we can then check if the first element is the Name tag and if so, return the value. A Tag object has two fields, one named
_Key_ and one named _Value_. So we simply check if the _Key_ field is equal to the tag name we are looking for and then return
its value. Otherwise, we return the empty string.

This seems to work just fine:

![Get the value of the Name tag](https://dev-to-uploads.s3.amazonaws.com/i/1plepmrag2ifnq39j1sx.png)

So that is great and we have solved the problem for two easy cases. But what if the list of tags has multiple tags and
the Name tag is not in the first position?
Well, we can then just simply extend our solution slightly. In our pattern matching, we had the situation where the second pattern would give us _the rest of the list_. This means if we apply the same logic to the rest of the list, we will
either return an empty string if the rest of the list is empty or return the tag value if the Name tag is the first element
of the rest of the list. I.e. we get a recursive function, which can call itself. 
There is only a tiny bit of modification to make this work:

```fsharp
let rec getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value  else getTagValue key tail
```

We add a **rec** keyword after the **let** keyword to indicate that the function will be recursive and then we simply
just call the function again in the else clause. Done! Now we can find the Name tag at any position in the list if it exists.
We then can update **getInstanceInfo** function to use this:

```fsharp
let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }
```

and the complete script at this point is then:

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System
open System.Environment
open Amazon.EC2
open Amazon.EC2.Model


type InstanceInfo =
    { InstanceId:       string
      Name:             string
      InstanceType:     string
      PrivateIpAddress: string
      LaunchTime:       DateTime
      State:            string }

      
let rec getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value  else getTagValue key tail


let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }


let getInstances (reservations: Reservation list) =
    reservations |> List.collect (fun x -> List.ofSeq x.Instances)


let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }
    

let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    if response.Reservations.Count = 0 then
        List.empty<Instance>
    else
        (List.ofSeq response.Reservations) |> getInstances

    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let mylist = List.ofSeq response.Reservations
  let response = showServers [| "erik" |]
  let iis = showServers [| "erik" |] |> List.map getInstanceInfo
  getInstanceInfo response.[0]
  let instance = response.[0]
  let tags = List.ofSeq instance.Tags
  getTagValue "Name" tags
*)
```

### Show the result

Now we can get the result as a list of _InstanceInfo_ objects. We can look at the result from that in the REPL, but would
perhaps be a bit nicer with some kind of print option. So we can make a function to print the contents of the
_InstanceInfo_ list. A simple way to print stuff to the screen that we already have used is the **printfn** function,
so let us use that. We can print strings fine with **printfn**, but there is no built-in formatting code for _DateTime_
objects. So we can make a simple function to convert the _DateTime_ to string, in a format we prefer.
I like the _yyyy-mm-dd hh:mm:ss_ format, so the formatting function should do that:

```fsharp
let yyyymmddhhmmss (dt: DateTime) =
    dt.ToString ("yyyy-MM-dd HH:mm:ss")
```

Next, let us make a function using **printfn** that prints some headers for the fields and then
the content of the _InstanceInfo_ list, one item per row. The function is not expected to return anything,
we just want the side-effect of printing. Therefore to clarify this the return type of the function is
declared as **unit**. It is similar to _void_ in some other languages, but there is an actual value for that type - not an absence of type as in some other languages.

```fsharp
let printInstanceInfo (instanceInfos: InstanceInfo list) : unit =
    if instanceInfos.IsEmpty then
        printfn "No instance info available!"
    else
        printfn "%-20s %-20s %-16s %-16s %-20s %-10s" 
            "InstanceId" "Name" "InstanceType" "Private IP" "LaunchTime" "State"
        for ii in instanceInfos do
            printfn "%-20s %-20s %-16s %-16s %-20s %-10s"
                ii.InstanceId ii.Name ii.InstanceType ii.PrivateIpAddress (yyyymmddhhmmss ii.LaunchTime) ii.State
```

First handle the case if the list is empty, then take care of non-empty lists. There we print a heading first
and then iterate over the list, printing each item. The heading is created in the same way as the items, using
the same approach to make it easier to manage to format. This works well:

![InstanceInfo print](https://dev-to-uploads.s3.amazonaws.com/i/z6pd478c5tkpy79ydhrt.png)

Note though that the format string is actually not a regular string type, so it cannot be replaced with a simple
named value string. 

We tweak the main function **showServers** a bit to use this function also and then we get the current result of the script
as this:

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System
open System.Environment
open Amazon.EC2
open Amazon.EC2.Model


type InstanceInfo =
    { InstanceId:       string
      Name:             string
      InstanceType:     string
      PrivateIpAddress: string
      LaunchTime:       DateTime
      State:            string }


let rec getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value  else getTagValue key tail


let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }


let yyyymmddhhmmss (dt: DateTime) =
    dt.ToString ("yyyy-MM-dd HH:mm:ss")


let printInstanceInfo (instanceInfos: InstanceInfo list) : unit =
    if instanceInfos.IsEmpty then
        printfn "No instance info available!"
    else
        printfn  "%-20s %-20s %-16s %-16s %-20s %-10s"
            "InstanceId" "Name" "InstanceType" "Private IP" "LaunchTime" "State"
        for ii in instanceInfos do
            printfn "%-20s %-20s %-16s %-16s %-20s %-10s"
                ii.InstanceId ii.Name ii.InstanceType ii.PrivateIpAddress (yyyymmddhhmmss ii.LaunchTime) ii.State


let getInstances (reservations: Reservation list) =
    reservations |> List.collect (fun x -> List.ofSeq x.Instances)


let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }
    

let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    let infolist = 
        if response.Reservations.Count = 0 then
            List.empty<Instance>
        else
            (List.ofSeq response.Reservations) |> getInstances
    infolist |> List.map getInstanceInfo |> printInstanceInfo

    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let mylist = List.ofSeq response.Reservations
  let response = showServers [| "erik" |]
  let iis = showServers [| "erik" |] |> List.map getInstanceInfo
  showServers [| "erik" |] |> List.map getInstanceInfo |> printInstanceInfo 
  getInstanceInfo response.[0]
  let instance = response.[0]
  let tags = List.ofSeq instance.Tags
  getTagValue "Name" tags
*)
```

We can call the new **showServers** from the REPL:

![Final showServers in REPL](https://dev-to-uploads.s3.amazonaws.com/i/bffx6iut35xb4ayag96k.png)

or we can run the script from the command line:

![Call ShowServers.fsx](https://dev-to-uploads.s3.amazonaws.com/i/7h9r516l4wycga8a19hi.png)

### The trouble with types

The type we have used in our script to represent instance information (_InstanceInfo_) is for the
most part using just plain strings to represent different values. With the very limited scope, our
script has now, that might not be a big issue. But if we go much beyond what we have now, then this
may potentially become a problem once the codebase grows larger.
We have a few different fields in _InstanceInfo_ that are not just plain strings:

* The _InstanceId_ is an identifier with a specific format of the string, e.g. "my dog is cute" would not be a valid value here.
* _InstanceType_ is actually a string representation of multiple entities, each with a limited range of values
* _PrivateIpAddress_ will only be something with a valid IP address representation. e.g. "123.234.234.123".
* _State_ will also have a limited set of values it can have, e.g. "running", "stopped", "terminated" etc.

The good thing is that the type system in F# can help us catch issues that might happen if we would
just use primitive type values like strings for everything. It is beyond the scope of this post
to build out full-blown types with validation of formats and all possible cases for all the fields
we included, but I want to include just a very simple addition that at least makes it clear in
code what a string should represent if we use it.

Let us take the _PrivateIpAddress_ field. This is an IP address, so let's make an IPAddress type and use that instead in our _InstanceInfo_ record:

```fsharp
type IpAddress = IpAddress of string

type InstanceInfo =
    { InstanceId:       string
      Name:             string
      InstanceType:     string
      PrivateIpAddress: IpAddress
      LaunchTime:       DateTime
      State:            string }
```

The type declaration for **IpAddress** may look a bit strange - does it somehow refer to itself?
The **IpAddress** to the left of the equal sign is the _type name_. The **IpAddress** on the right side of the equal sign is part of the _type value_. It essentially says that a value of the type **IpAddress** must consist of an identifier named _IpAddress_ and then a value of type string.

We then change the type of the _PrivateIpAddress_ field to use this. If you try to run the script
now, it will fail and you will have multiple errors. The first one is where we assign the value
to this field in the function. Instead of:

```fsharp
let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }
```

we will not be able to assign the string from `instance.PrivateIpAddress` directly, we need to
tell it that the string is to be used for the **IpAddress** type. So we need to add the _identifier_ that is part of the type value:

```fsharp
let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = IpAddress instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }
```

This is then good for the assignment of the value. But there will still be errors elsewhere, the
call to print the values will fail, because now the value to print is not a string type, but an IpAddress type!

One way we can address this (there are multiple ways) is to define a conversion function from
our IpAddress type to a string and then use that function when we are going to print the value.
This is a simple one-liner, which we put next to our type definition so we have them grouped together:

```fsharp
type IpAddress = IpAddress of string
let toString (IpAddress s)  = s
```

The **toString** function takes one parameter that consists of two parts, the _identifier_ IpAddress and s. Then the function will simply return s. Given that our _type_ IpAddress is defined
with an _identifier_ IpAddress and then a string value, the compiler will know that s will be of
type string in this case.

Then we need to change our printfn call to use this conversion function:

```fsharp
        for ii in instanceInfos do
            printfn "%-20s %-20s %-16s %-16s %-20s %-10s"
                ii.InstanceId ii.Name ii.InstanceType (toString ii.PrivateIpAddress) (yyyymmddhhmmss ii.LaunchTime) ii.State
```

This is a small change to make it a bit more explicit in the code that it is an IP address we are
dealing with. So now our complete script looks like this:

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System
open System.Environment
open Amazon.EC2
open Amazon.EC2.Model

type IpAddress = IpAddress of string
let toString (IpAddress a)  = a

type InstanceInfo =
    { InstanceId:       string
      Name:             string
      InstanceType:     string
      PrivateIpAddress: IpAddress
      LaunchTime:       DateTime
      State:            string }


let rec getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value  else getTagValue key tail


let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateIpAddress = IpAddress instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }


let yyyymmddhhmmss (dt: DateTime) =
    dt.ToString ("yyyy-MM-dd HH:mm:ss")


let printInstanceInfo (instanceInfos: InstanceInfo list) : unit =
    if instanceInfos.IsEmpty then
        printfn "No instance info available!"
    else
        printfn  "%-20s %-20s %-16s %-16s %-20s %-10s"
            "InstanceId" "Name" "InstanceType" "Private IP" "LaunchTime" "State"
        for ii in instanceInfos do
            printfn "%-20s %-20s %-16s %-16s %-20s %-10s"
                ii.InstanceId ii.Name ii.InstanceType (toString ii.PrivateIpAddress) (yyyymmddhhmmss ii.LaunchTime) ii.State


let getInstances (reservations: Reservation list) =
    reservations |> List.collect (fun x -> List.ofSeq x.Instances)


let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }
    

let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    let infolist = 
        if response.Reservations.Count = 0 then
            List.empty<Instance>
        else
            (List.ofSeq response.Reservations) |> getInstances
    infolist |> List.map getInstanceInfo |> printInstanceInfo

    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let mylist = List.ofSeq response.Reservations
  let response = showServers [| "erik" |]
  let iis = showServers [| "erik" |] |> List.map getInstanceInfo
  showServers [| "erik" |] |> List.map getInstanceInfo |> printInstanceInfo 
  getInstanceInfo response.[0]
  let instance = response.[0]
  let tags = List.ofSeq instance.Tags
  getTagValue "Name" tags
*)
```

Is that worth the effort? Let us say that we want to expand our code to include either a private
IP address or a DNS name, but not both. Can we do that in a way that let us be clear what is there
and capture potential issues?

Let us change our _type_ IpAddress to be a type that can represent either an IP address or a DNS name. We can do that easily:

```fsharp
type IpAddressOrDns = 
    | IpAddress of string
    | DnsName of string
```

Now we have _type_ **IpAddressOrDns** that have two possible type values; _either_ an IP address or a DNS name. Each of these cases is represented by an _identifier_ (**IpAddress** or **DnsName**) and then a value of string type.

Once we create this change you will see that F# will report an error on the _toString_ function we defined, which likely will look like the one in the picture below:

![Case error for toString function](https://dev-to-uploads.s3.amazonaws.com/i/cuxvu0of8a71st1lo6as.png)

The F# compiler knows that this pattern is from our **IpAddressOrDns** type and it also knows
that you have not handled all cases of what that type can be, what case you have not taken care of here and tells you what it is.

So we need to update **toString** to handle both cases:

```fsharp
let toString v  = 
    match v with
    | IpAddress s -> s
    | DnsName s -> s
```

We do not specify the cases we have in the parameter declaration, we just set a parameter name.
Instead, we rely on pattern matching in the function itself.
We do not need to specify any type information here explicitly, since the compiler knows what
type *v* is and what type *s* is since it knows how the type **IpAddressOrDns** is defined. The code compiles now since we have covered all possible cases for the **IpAddressOrDns** type.

We need to change the type of the field in _InstanceInfo_ from **IpAddress** to **IpAddressOfDns** and then the script will work again. But we may also want to rename the field _PrivateIpAddress_ to perhaps _PrivateHostAddress_, if that is a field that actually should be able to contain either
an IP address or a DNS name. 

With this update the script code looks like this:

```fsharp
#!/usr/bin/env dotnet fsi

// Get the AWS SDK packages needed
#r "nuget: AWSSDK.Core"
#r "nuget: AWSSDK.EC2"

open System
open System.Environment
open Amazon.EC2
open Amazon.EC2.Model

type IpAddressOrDns = 
    | IpAddress of string
    | DnsName of string

let toString v  = 
    match v with
    | IpAddress s -> s
    | DnsName s -> s

type InstanceInfo =
    { InstanceId:         string
      Name:               string
      InstanceType:       string
      PrivateHostAddress: IpAddressOrDns
      LaunchTime:         DateTime
      State:              string }


let rec getTagValue key (tags: Tag list) =
    match tags with
    | [] -> ""
    | head :: tail -> if head.Key = key then head.Value  else getTagValue key tail


let getInstanceInfo (instance: Instance) =
    let tags = List.ofSeq instance.Tags
    {
        InstanceId = instance.InstanceId
        Name = getTagValue "Name" tags
        InstanceType = instance.InstanceType.Value
        PrivateHostAddress = IpAddress instance.PrivateIpAddress
        LaunchTime = instance.LaunchTime
        State = instance.State.Name.Value
    }


let yyyymmddhhmmss (dt: DateTime) =
    dt.ToString ("yyyy-MM-dd HH:mm:ss")


let printInstanceInfo (instanceInfos: InstanceInfo list) : unit =
    if instanceInfos.IsEmpty then
        printfn "No instance info available!"
    else
        printfn  "%-20s %-20s %-16s %-21s %-20s %-10s"
            "InstanceId" "Name" "InstanceType" "Private Host Address" "LaunchTime" "State"
        for ii in instanceInfos do
            printfn "%-20s %-20s %-16s %-21s %-20s %-10s"
                ii.InstanceId ii.Name ii.InstanceType (toString ii.PrivateHostAddress) (yyyymmddhhmmss ii.LaunchTime) ii.State


let getInstances (reservations: Reservation list) =
    reservations |> List.collect (fun x -> List.ofSeq x.Instances)


let describeInstances (client: AmazonEC2Client) =
    async {
        let! response = client.DescribeInstancesAsync() |> Async.AwaitTask
        return response
    }
    

let showServers (args: string[]) =
    if args.Length > 0 then
        do SetEnvironmentVariable ("AWS_PROFILE", args.[0])
    let client = new AmazonEC2Client()
    let response = (describeInstances client) |> Async.RunSynchronously
    let infolist = 
        if response.Reservations.Count = 0 then
            List.empty<Instance>
        else
            (List.ofSeq response.Reservations) |> getInstances
    infolist |> List.map getInstanceInfo |> printInstanceInfo

    
showServers (fsi.CommandLineArgs |> Array.skip 1)

(*
  showServers [| "erik" |]
  GetEnvironmentVariable("AWS_PROFILE")
  let mylist = List.ofSeq response.Reservations
  let response = showServers [| "erik" |]
  let iis = showServers [| "erik" |] |> List.map getInstanceInfo
  showServers [| "erik" |] |> List.map getInstanceInfo |> printInstanceInfo 
  getInstanceInfo response.[0]
  let instance = response.[0]
  let tags = List.ofSeq instance.Tags
  getTagValue "Name" tags
*)
```

That is it for now!

**Side note:** If you get an error like this below, it means that whatever function you are calling has been compiled against
a different version of the record than you are passing to it when you are interacting with the REPL. This would not be an issue in Clojure, but a statically typed language this may happen in the REPL interaction.

In that case, you need to make sure you re-evaluate all the code pieces that have been affected by your type change!

![Type version error](https://dev-to-uploads.s3.amazonaws.com/i/ho4unq8pql48j8yy77ie.png)

## Final remarks

After struggling a bit with getting an enjoyable experience in developing F# scripts, I decided to focus
a bit more on workflow aspects in this post, before digging a bit deeper into cloud use cases.

I believe that a good REPL and good use of a REPL is a key factor to enjoyable development workflow.
Clojure is really great in that area and F# grows on me as well here.
I think both of these languages do a good job here and they are both perhaps more focused on an editor<->REPL integration workflow, more than extended interactive sessions in the REPL itself.

Other languages which I think have pretty nice REPL experience include [Julia](https://julialang.org/) and [R](https://www.r-project.org/). If there are further improvements to the REPL experience for F# I think both of these may serve well as inspiration. Julia was probably inspired by R in what to include in the REPL, is my guess.

I hope this was at least somewhat useful for some of you!

In [part 3](https://dev.to/eriklz/f-for-the-cloud-worker-part-3-aws-lambda-542n) we are going to start with some F# code using AWS Lambda.

### Source code

The source code in the blog posts in this series is posted to this Github repository:

https://github.com/elzcloud/fsharp-for-cloud-worker