{"id":14008359,"url":"https://github.com/chrisdone-archive/dynamic","last_synced_at":"2025-07-24T03:32:37.253Z","repository":{"id":62435870,"uuid":"178707028","full_name":"chrisdone-archive/dynamic","owner":"chrisdone-archive","description":"Dynamic typing in Haskell","archived":true,"fork":false,"pushed_at":"2019-05-29T23:05:25.000Z","size":30,"stargazers_count":191,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-07-20T02:32:52.087Z","etag":null,"topics":["analytics","dynamic-typing","enterprise-software","haskell"],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chrisdone-archive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"patreon":"chrisdone"}},"created_at":"2019-03-31T15:33:14.000Z","updated_at":"2025-02-25T22:46:19.000Z","dependencies_parsed_at":"2022-11-01T21:30:34.141Z","dependency_job_id":null,"html_url":"https://github.com/chrisdone-archive/dynamic","commit_stats":null,"previous_names":["chrisdone/dynamic"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chrisdone-archive/dynamic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisdone-archive%2Fdynamic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisdone-archive%2Fdynamic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisdone-archive%2Fdynamic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisdone-archive%2Fdynamic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chrisdone-archive","download_url":"https://codeload.github.com/chrisdone-archive/dynamic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisdone-archive%2Fdynamic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266789222,"owners_count":23984252,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-24T02:00:09.469Z","response_time":99,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","dynamic-typing","enterprise-software","haskell"],"created_at":"2024-08-10T11:01:36.999Z","updated_at":"2025-07-24T03:32:37.033Z","avatar_url":"https://github.com/chrisdone-archive.png","language":"Haskell","funding_links":["https://patreon.com/chrisdone"],"categories":["Haskell"],"sub_categories":[],"readme":"# dynamic\n\nFinally, dynamically typed programming in Haskell made easy!\n\n## Introduction\n\nTired of making data types in your Haskell programs just to read and\nmanipulate basic JSON/CSV files? Tired of writing imports? Use\n`dynamic`, dynamically typed programming for Haskell!\n\n## Load it up\n\nLaunch `ghci`, the interactive REPL for Haskell.\n\n``` haskell\nimport Dynamic\n```\n\nDon't forget to enable `OverloadedStrings`:\n\n``` haskell\n:set -XOverloadedStrings\n```\n\nNow you're ready for dynamicness!\n\n## The Dynamic type\n\nIn the `dynamic` package there is one type: `Dynamic`!\n\nWhat, you were expecting something more? Guffaw!\n\n## Make dynamic values as easy as pie!\n\nPrimitive values are easy via regular literals:\n\n``` haskell\n\u003e 1\n1\n\u003e \"Hello, World!\"\n\"Hello, World!\"\n```\n\nArrays and objects have handy functions to make them:\n\n``` haskell\n\u003e fromList [1,2]\n[\n    1,\n    2\n]\n\u003e fromDict [ (\"k\", 1), (\"v\", 2) ]\n{\n    \"k\": 1,\n    \"v\": 2\n}\n```\n\nGet object keys or array or string indexes via `!`:\n\n``` haskell\n\u003e fromDict [ (\"k\", 1), (\"v\", 2) ] ! \"k\"\n1\n\u003e fromList [1,2] ! 1\n2\n\u003e \"foo\" ! 2\n\"o\"\n```\n\n## Web requests!\n\n```json\n\u003e chris \u003c- getJson \"https://api.github.com/users/chrisdone\" []\n\u003e chris\n{\n    \"bio\": null,\n    \"email\": null,\n    \"public_gists\": 176,\n    \"repos_url\": \"https://api.github.com/users/chrisdone/repos\",\n    \"node_id\": \"MDQ6VXNlcjExMDE5\",\n    \"following_url\": \"https://api.github.com/users/chrisdone/following{/other_user}\",\n    \"location\": \"England\",\n    \"url\": \"https://api.github.com/users/chrisdone\",\n    \"gravatar_id\": \"\",\n    \"blog\": \"https://chrisdone.com\",\n    \"gists_url\": \"https://api.github.com/users/chrisdone/gists{/gist_id}\",\n    \"following\": 0,\n    \"hireable\": null,\n    \"organizations_url\": \"https://api.github.com/users/chrisdone/orgs\",\n    \"subscriptions_url\": \"https://api.github.com/users/chrisdone/subscriptions\",\n    \"name\": \"Chris Done\",\n    \"company\": \"FP Complete @fpco \",\n    \"updated_at\": \"2019-02-22T11:11:18Z\",\n    \"created_at\": \"2008-05-21T10:29:09Z\",\n    \"followers\": 1095,\n    \"id\": 11019,\n    \"public_repos\": 144,\n    \"avatar_url\": \"https://avatars3.githubusercontent.com/u/11019?v=4\",\n    \"type\": \"User\",\n    \"events_url\": \"https://api.github.com/users/chrisdone/events{/privacy}\",\n    \"starred_url\": \"https://api.github.com/users/chrisdone/starred{/owner}{/repo}\",\n    \"login\": \"chrisdone\",\n    \"received_events_url\": \"https://api.github.com/users/chrisdone/received_events\",\n    \"site_admin\": false,\n    \"html_url\": \"https://github.com/chrisdone\",\n    \"followers_url\": \"https://api.github.com/users/chrisdone/followers\"\n}\n```\n\n## Trivially read CSV files!\n\n``` haskell\n\u003e fromCsvNamed \"name,age,alive,partner\\nabc,123,true,null\\nabc,ok,true,true\"\n[{\n    \"alive\": true,\n    \"age\": 123,\n    \"partner\": null,\n    \"name\": \"abc\"\n},{\n    \"alive\": true,\n    \"age\": \"ok\",\n    \"partner\": true,\n    \"name\": \"abc\"\n}]\n```\n\n## Dynamically typed programming!\n\nJust write code like you do in Python or JavaScript:\n\n```haskell\n\u003e if chris!\"followers\" \u003e 500 then chris!\"public_gists\" * 5 else chris!\"name\"\n880\n```\n\n## Experience the wonders of dynamic type errors!\n\nTry to treat non-numbers as numbers and you get the expected result:\n\n``` haskell\n\u003e map (\\o -\u003e o ! \"age\" * 2) $ fromCsvNamed \"name,age,alive,partner\\nabc,123,true,null\\nabc,ok,true,true\"\n[246,*** Exception: DynamicTypeError \"Couldn't treat string as number: ok\"\n```\n\nLaziness makes everything better!\n\n\n``` haskell\n\u003e map (*2) $ toList $ fromJson \"[\\\"1\\\",true,123]\"\n[2,*** Exception: DynamicTypeError \"Can't treat bool as number.\"\n```\n\nWoops...\n\n``` haskell\n\u003e map (*2) $ toList $ fromJson \"[\\\"1\\\",123]\"\n[2,246]\n```\n\nThat's better!\n\nHeterogenous lists are what life is about:\n\n``` haskell\n\u003e toCsv [ 1, \"Chris\" ]\n\"1.0\\r\\nChris\\r\\n\"\n```\n\nI can't handle it!!!\n\n## Modifying and updating records\n\nUse `modify` or `set` to massage data into something more palatable.\n\n``` haskell\n\u003e modify \"followers\" (*20) chris\n{\n    \"bio\": null,\n    \"email\": null,\n    \"public_gists\": 176,\n    \"repos_url\": \"https://api.github.com/users/chrisdone/repos\",\n    \"node_id\": \"MDQ6VXNlcjExMDE5\",\n    \"following_url\": \"https://api.github.com/users/chrisdone/following{/other_user}\",\n    \"location\": \"England\",\n    \"url\": \"https://api.github.com/users/chrisdone\",\n    \"gravatar_id\": \"\",\n    \"blog\": \"https://chrisdone.com\",\n    \"gists_url\": \"https://api.github.com/users/chrisdone/gists{/gist_id}\",\n    \"following\": 0,\n    \"hireable\": null,\n    \"organizations_url\": \"https://api.github.com/users/chrisdone/orgs\",\n    \"subscriptions_url\": \"https://api.github.com/users/chrisdone/subscriptions\",\n    \"name\": \"Chris Done\",\n    \"company\": \"FP Complete @fpco \",\n    \"updated_at\": \"2019-02-22T11:11:18Z\",\n    \"created_at\": \"2008-05-21T10:29:09Z\",\n    \"followers\": 21900,\n    \"id\": 11019,\n    \"public_repos\": 144,\n    \"avatar_url\": \"https://avatars3.githubusercontent.com/u/11019?v=4\",\n    \"type\": \"User\",\n    \"events_url\": \"https://api.github.com/users/chrisdone/events{/privacy}\",\n    \"starred_url\": \"https://api.github.com/users/chrisdone/starred{/owner}{/repo}\",\n    \"login\": \"chrisdone\",\n    \"received_events_url\": \"https://api.github.com/users/chrisdone/received_events\",\n    \"site_admin\": false,\n    \"html_url\": \"https://github.com/chrisdone\",\n    \"followers_url\":\n    \"https://api.github.com/users/chrisdone/followers\"\n}\n```\n\n## List of numbers?\n\nThe answer is: Yes, Haskell can do that!\n\n``` haskell\n\u003e [1.. 5] :: [Dynamic]\n[1,2,3,4,5]\n```\n\n## Append things together\n\nLike in JavaScript, we try to do our best to make something out of appending...\n\n``` haskell\n\u003e \"Wat\" \u003c\u003e 1 \u003c\u003e \"!\" \u003c\u003e Null\n\"Wat1!\"\n```\n\n## Suspicious?\n\nIt's real! This code runs just fine:\n\n``` haskell\nsilly a =\n  if a \u003e 0\n     then toJsonFile \"out.txt\" \"Hi\"\n     else toJsonFile \"out.txt\" (5 + \"a\")\n```\n\nThat passes [the dynamic typing test](https://stackoverflow.com/a/27791387).\n\n## Mix and match your regular Haskell functions\n\nHere's an exporation of my Monzo (bank account) data.\n\nLoad up the JSON output:\n\n```haskell\n\u003e monzo \u003c- fromJsonFile \"monzo.json\"\n```\n\nPreview what's in it:\n\n```haskell\n\u003e take 100 $ show monzo\n\"{\\n    \\\"transactions\\\": [\\n        {\\n            \\\"amount\\\": 10000,\\n            \\\"dedupe_id\\\": \\\"com.monzo.f\"\n\u003e toKeys monzo\n[\"transactions\"]\n```\n\nOK, just transactions. How many?\n\n```haskell\n\u003e length $ toList $ monzo!\"transactions\"\n119\n```\n\nWhat keys do I get in each transaction?\n\n```haskell\n\u003e toKeys $ head $ toList $ monzo!\"transactions\"\n[\"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\"]\n```\n\nWhat's in `amount`?\n\n```haskell\n\u003e (!\"amount\") $ head $ toList $ monzo!\"transactions\"\n10000\n```\n\nLooks like pennies, let's divide that by 100. What's the total +/- sum\nof my last 5 transactions?\n\n```haskell\n\u003e sum $ map ((/100) . (!\"amount\")) $ take 5 $ toList $ monzo!\"transactions\"\n468.65\n```\n\nWhat categories are there?\n\n```haskell\n\u003e nub $ map (!\"category\") $ toList $ monzo!\"transactions\"\n[\"general\",\"entertainment\",\"groceries\",\"eating_out\",\"shopping\",\"expenses\",\"bills\",\"personal_care\",\"cash\"]\n```\n\nHow many transactions did I do in each category? Let's use Data.Map to\nhistogram that.\n\n```haskell\n\u003e fromDict $ M.toList $ foldl (\\cats cat -\u003e M.insertWith (+) cat 1 cats) mempty $ map (!\"category\") $ toList $ monzo!\"transactions\"\n{\n    \"personal_care\": 2,\n    \"entertainment\": 8,\n    \"bills\": 3,\n    \"general\": 58,\n    \"groceries\": 16,\n    \"shopping\": 8,\n    \"expenses\": 19,\n    \"eating_out\": 4,\n    \"cash\": 1\n}\n\u003e\n```\n\nCool!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisdone-archive%2Fdynamic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrisdone-archive%2Fdynamic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisdone-archive%2Fdynamic/lists"}