Finally, dynamically typed programming in Haskell made easy!
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!
Launch ghci
, the interactive REPL for Haskell.
import Dynamic
Don't forget to enable OverloadedStrings
:
:set -XOverloadedStrings
Now you're ready for dynamicness!
In the dynamic
package there is one type: Dynamic
!
What, you were expecting something more? Guffaw!
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"
> 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"
}
> 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"
}]
Just write code like you do in Python or JavaScript:
> if chris!"followers" > 500 then chris!"public_gists" * 5 else chris!"name"
880
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!!!
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"
}
The answer is: Yes, Haskell can do that!
> [1.. 5] :: [Dynamic]
[1,2,3,4,5]
Like in JavaScript, we try to do our best to make something out of appending...
> "Wat" <> 1 <> "!" <> Null
"Wat1!"
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.
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!