A dynamic type for Haskell.
Want to do dynamically typed programming in Haskell sometimes? Here you go!
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.
import Dynamic
Don't forget to enable OverloadedStrings
:
: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:
> 1
1
> "Hello, World!"
"Hello, World!"
Arrays and objects have handy functions to make them:
> fromList [1,2]
[
1,
2
]
> fromDict [ ("k", 1), ("v", 2) ]
{
"k": 1,
"v": 2
}
Get object keys or array or string indexes via !
:
> fromDict [ ("k", 1), ("v", 2) ] ! "k"
1
> fromList [1,2] ! 1
2
> "foo" ! 2
"o"
Web requests!
> 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!
> 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:
> 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:
> 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!
> map (*2) $ toList $ fromJson "[\"1\",true,123]"
[2,*** Exception: DynamicTypeError "Can't treat bool as number."
Woops...
> map (*2) $ toList $ fromJson "[\"1\",123]"
[2,246]
That's better!
Heterogenous lists are what life is about:
> 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.
> 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!
> [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...
> "Wat" <> 1 <> "!" <> Null
"Wat1!"
Suspicious?
It's real! This code runs just fine:
silly a =
if a > 0
then toJsonFile "out.txt" "Hi"
else toJsonFile "out.txt" (5 + "a")
That passes the dynamic typing test.
Mix and match your regular Haskell functions
Here's an exporation of my Monzo (bank account) data.
Load up the JSON output:
> monzo <- fromJsonFile "monzo.json"
Preview what's in it:
> take 100 $ show monzo
"{\n \"transactions\": [\n {\n \"amount\": 10000,\n \"dedupe_id\": \"com.monzo.f"
> toKeys monzo
["transactions"]
OK, just transactions. How many?
> length $ toList $ monzo!"transactions"
119
What keys do I get in each transaction?
> 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
?
> (!"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?
> sum $ map ((/100) . (!"amount")) $ take 5 $ toList $ monzo!"transactions"
468.65
What categories are there?
> 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.
> 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!