Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/chrisdone/dynamic

Dynamic typing in Haskell
https://github.com/chrisdone/dynamic

analytics dynamic-typing enterprise-software haskell

Last synced: 3 months ago
JSON representation

Dynamic typing in Haskell

Lists

README

        

# dynamic

Finally, dynamically typed programming in Haskell made easy!

## Introduction

Tired of making data types in your Haskell programs just to read and
manipulate basic JSON/CSV files? Tired of writing imports? Use
`dynamic`, dynamically typed programming for Haskell!

## Load it up

Launch `ghci`, the interactive REPL for Haskell.

``` haskell
import Dynamic
```

Don't forget to enable `OverloadedStrings`:

``` haskell
:set -XOverloadedStrings
```

Now you're ready for dynamicness!

## The Dynamic type

In the `dynamic` package there is one type: `Dynamic`!

What, you were expecting something more? Guffaw!

## Make dynamic values as easy as pie!

Primitive values are easy via regular literals:

``` haskell
> 1
1
> "Hello, World!"
"Hello, World!"
```

Arrays and objects have handy functions to make them:

``` haskell
> fromList [1,2]
[
1,
2
]
> fromDict [ ("k", 1), ("v", 2) ]
{
"k": 1,
"v": 2
}
```

Get object keys or array or string indexes via `!`:

``` haskell
> fromDict [ ("k", 1), ("v", 2) ] ! "k"
1
> fromList [1,2] ! 1
2
> "foo" ! 2
"o"
```

## Web requests!

```json
> chris <- getJson "https://api.github.com/users/chrisdone" []
> chris
{
"bio": null,
"email": null,
"public_gists": 176,
"repos_url": "https://api.github.com/users/chrisdone/repos",
"node_id": "MDQ6VXNlcjExMDE5",
"following_url": "https://api.github.com/users/chrisdone/following{/other_user}",
"location": "England",
"url": "https://api.github.com/users/chrisdone",
"gravatar_id": "",
"blog": "https://chrisdone.com",
"gists_url": "https://api.github.com/users/chrisdone/gists{/gist_id}",
"following": 0,
"hireable": null,
"organizations_url": "https://api.github.com/users/chrisdone/orgs",
"subscriptions_url": "https://api.github.com/users/chrisdone/subscriptions",
"name": "Chris Done",
"company": "FP Complete @fpco ",
"updated_at": "2019-02-22T11:11:18Z",
"created_at": "2008-05-21T10:29:09Z",
"followers": 1095,
"id": 11019,
"public_repos": 144,
"avatar_url": "https://avatars3.githubusercontent.com/u/11019?v=4",
"type": "User",
"events_url": "https://api.github.com/users/chrisdone/events{/privacy}",
"starred_url": "https://api.github.com/users/chrisdone/starred{/owner}{/repo}",
"login": "chrisdone",
"received_events_url": "https://api.github.com/users/chrisdone/received_events",
"site_admin": false,
"html_url": "https://github.com/chrisdone",
"followers_url": "https://api.github.com/users/chrisdone/followers"
}
```

## Trivially read CSV files!

``` haskell
> fromCsvNamed "name,age,alive,partner\nabc,123,true,null\nabc,ok,true,true"
[{
"alive": true,
"age": 123,
"partner": null,
"name": "abc"
},{
"alive": true,
"age": "ok",
"partner": true,
"name": "abc"
}]
```

## Dynamically typed programming!

Just write code like you do in Python or JavaScript:

```haskell
> if chris!"followers" > 500 then chris!"public_gists" * 5 else chris!"name"
880
```

## Experience the wonders of dynamic type errors!

Try to treat non-numbers as numbers and you get the expected result:

``` haskell
> map (\o -> o ! "age" * 2) $ fromCsvNamed "name,age,alive,partner\nabc,123,true,null\nabc,ok,true,true"
[246,*** Exception: DynamicTypeError "Couldn't treat string as number: ok"
```

Laziness makes everything better!

``` haskell
> map (*2) $ toList $ fromJson "[\"1\",true,123]"
[2,*** Exception: DynamicTypeError "Can't treat bool as number."
```

Woops...

``` haskell
> map (*2) $ toList $ fromJson "[\"1\",123]"
[2,246]
```

That's better!

Heterogenous lists are what life is about:

``` haskell
> toCsv [ 1, "Chris" ]
"1.0\r\nChris\r\n"
```

I can't handle it!!!

## Modifying and updating records

Use `modify` or `set` to massage data into something more palatable.

``` haskell
> modify "followers" (*20) chris
{
"bio": null,
"email": null,
"public_gists": 176,
"repos_url": "https://api.github.com/users/chrisdone/repos",
"node_id": "MDQ6VXNlcjExMDE5",
"following_url": "https://api.github.com/users/chrisdone/following{/other_user}",
"location": "England",
"url": "https://api.github.com/users/chrisdone",
"gravatar_id": "",
"blog": "https://chrisdone.com",
"gists_url": "https://api.github.com/users/chrisdone/gists{/gist_id}",
"following": 0,
"hireable": null,
"organizations_url": "https://api.github.com/users/chrisdone/orgs",
"subscriptions_url": "https://api.github.com/users/chrisdone/subscriptions",
"name": "Chris Done",
"company": "FP Complete @fpco ",
"updated_at": "2019-02-22T11:11:18Z",
"created_at": "2008-05-21T10:29:09Z",
"followers": 21900,
"id": 11019,
"public_repos": 144,
"avatar_url": "https://avatars3.githubusercontent.com/u/11019?v=4",
"type": "User",
"events_url": "https://api.github.com/users/chrisdone/events{/privacy}",
"starred_url": "https://api.github.com/users/chrisdone/starred{/owner}{/repo}",
"login": "chrisdone",
"received_events_url": "https://api.github.com/users/chrisdone/received_events",
"site_admin": false,
"html_url": "https://github.com/chrisdone",
"followers_url":
"https://api.github.com/users/chrisdone/followers"
}
```

## List of numbers?

The answer is: Yes, Haskell can do that!

``` haskell
> [1.. 5] :: [Dynamic]
[1,2,3,4,5]
```

## Append things together

Like in JavaScript, we try to do our best to make something out of appending...

``` haskell
> "Wat" <> 1 <> "!" <> Null
"Wat1!"
```

## Suspicious?

It's real! This code runs just fine:

``` haskell
silly a =
if a > 0
then toJsonFile "out.txt" "Hi"
else toJsonFile "out.txt" (5 + "a")
```

That passes [the dynamic typing test](https://stackoverflow.com/a/27791387).

## Mix and match your regular Haskell functions

Here's an exporation of my Monzo (bank account) data.

Load up the JSON output:

```haskell
> monzo <- fromJsonFile "monzo.json"
```

Preview what's in it:

```haskell
> take 100 $ show monzo
"{\n \"transactions\": [\n {\n \"amount\": 10000,\n \"dedupe_id\": \"com.monzo.f"
> toKeys monzo
["transactions"]
```

OK, just transactions. How many?

```haskell
> length $ toList $ monzo!"transactions"
119
```

What keys do I get in each transaction?

```haskell
> toKeys $ head $ toList $ monzo!"transactions"
["amount","dedupe_id","attachments","can_be_made_subscription","fees","created","category","settled","can_split_the_bill","can_add_to_tab","originator","currency","include_in_spending","merchant","can_be_excluded_from_breakdown","international","counterparty","scheme","local_currency","metadata","id","labels","updated","account_balance","is_load","account_id","notes","user_id","local_amount","description"]
```

What's in `amount`?

```haskell
> (!"amount") $ head $ toList $ monzo!"transactions"
10000
```

Looks like pennies, let's divide that by 100. What's the total +/- sum
of my last 5 transactions?

```haskell
> sum $ map ((/100) . (!"amount")) $ take 5 $ toList $ monzo!"transactions"
468.65
```

What categories are there?

```haskell
> nub $ map (!"category") $ toList $ monzo!"transactions"
["general","entertainment","groceries","eating_out","shopping","expenses","bills","personal_care","cash"]
```

How many transactions did I do in each category? Let's use Data.Map to
histogram that.

```haskell
> fromDict $ M.toList $ foldl (\cats cat -> M.insertWith (+) cat 1 cats) mempty $ map (!"category") $ toList $ monzo!"transactions"
{
"personal_care": 2,
"entertainment": 8,
"bills": 3,
"general": 58,
"groceries": 16,
"shopping": 8,
"expenses": 19,
"eating_out": 4,
"cash": 1
}
>
```

Cool!