├── .gitignore ├── LICENSE ├── README.md ├── resources ├── README.md └── reviews.md └── tutorials ├── README.md ├── blah.hs ├── blaze-ihaskell.ipynb ├── clay ├── clay-custom.hs ├── clay-pre.hs └── clay-simple.hs ├── foursquaretrends.hs ├── img-ratio.hs ├── reflex-frp ├── README.md ├── css.hs ├── helloworld.css ├── nasa-pod.hs ├── static │ ├── css.jpg │ ├── nasa-pod.jpg │ └── svg.jpg ├── style.css ├── svg.hs └── xmas.hs └── twitter-fpcomplete.hs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.js_dyn_hi 3 | *.js_dyn_o 4 | *.js_hi 5 | *.js_o 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dr. Kat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | getting-started-with-haskell 2 | ============================ 3 | 4 | There are many resources for learning haskell on the web, but few of them focus on final end products (emphasis on the word *products*), so I've started this project to collect resources that are more beginner friendly yet also serves practical purpose(s). 5 | 6 | There are several sections in this repository: 7 | 8 | - [Tutorials](https://github.com/katychuang/getting-started-with-haskell/tree/master/tutorials) - A directory of cookbook-like tutorials, targeted for those with an active, hands-on learning style. 9 | - [Resources](https://github.com/katychuang/getting-started-with-haskell/tree/master/resources) - An unsorted, loosely grouped list of resources, targeted for individuals with a more passive, by the book learning approach. 10 | 11 | *Disclaimer:* I am still fairly new to the Haskell language, so any advice or questions are welcomed as this repository grows. Feel free to send an issue ticket or DM on twitter [@katychuang](http://twitter.com/katychuang), or find me on IRC @katychuang. 12 | 13 | ## Where to find haskellers (mostly online) 14 | 15 | * [Haskell Community on Reddit](http://www.reddit.com/r/haskell/) 16 | * [IRC Channels](http://www.haskell.org/haskellwiki/IRC_channel) 17 | * [Local User Groups](http://www.haskell.org/haskellwiki/User_groups) 18 | * [Twitter, #haskell](https://twitter.com/search?q=%23haskell) 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/README.md: -------------------------------------------------------------------------------- 1 | This is an unsorted and unrated list of resources; peruse these resources at your own risk. 2 | 3 | *Note: I'm accepting submissions for reviews on resources, submit an issue and I'll link them [here](https://github.com/katychuang/getting-started-with-haskell/blob/master/resources/reviews.md).* 4 | 5 | ## Haskell Syntax 6 | 7 | * [Tour of Haskell Syntax](http://www.cs.utep.edu/cheon/cs3360/pages/haskell-syntax.html) 8 | * [10 things you should know about haskell syntax](https://www.fpcomplete.com/blog/2012/09/ten-things-you-should-know-about-haskell-syntax) 9 | * [A Quick Tour of Haskell Syntax](http://prajitr.github.io/quick-haskell-syntax/) 10 | * [The Haskell Cheatsheet](http://cheatsheet.codeslower.com/) 11 | * [Haskell Reference Card](http://www.haskell.org/haskellwiki/Reference_card) 12 | * [Haskell Wiki: Cookbook](http://www.haskell.org/haskellwiki/Cookbook) 13 | 14 | ## Tutorials 15 | 16 | * [wreq: a Haskell web client library HTTP made easy for Haskell.](http://www.serpentine.com/wreq/) 17 | * [Haskell Wiki (tagged:Tutorials)](http://www.haskell.org/haskellwiki/Category:Tutorials) 18 | * [Haskell Wiki Tutorials](http://www.haskell.org/haskellwiki/Tutorials) 19 | * [Haskell Wiki: Learning_Haskell](http://www.haskell.org/haskellwiki/Learning_Haskell) 20 | * [Haskell Wiki: Books & Tutorials](http://www.haskell.org/haskellwiki/Books_and_tutorials) 21 | * [School of Haskell (FP Complete)](https://www.fpcomplete.com/school) 22 | * http://lisperati.com/haskell/ 23 | * http://www.umiacs.umd.edu/~hal/docs/daume02yaht.pdf 24 | * [Roll your own IRC Bot](http://www.haskell.org/haskellwiki/Roll_your_own_IRC_bot) 25 | * [Category Theory for Programmers](http://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/) 26 | * [Monads in 15 minutes](http://www.randomhacks.net/2007/03/12/monads-in-15-minutes/) 27 | * [Functional Programming in Haskell](https://github.com/caiorss/Functional-Programming/blob/master/haskell/README.org) 28 | * [Functors, Applicatives, And Monads In Pictures](http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html#functors) 29 | * [Hitchhikers guide to Haskell](https://wiki.haskell.org/Hitchhikers_guide_to_Haskell) 30 | 31 | **Note:** there is a more detailed list of tried tutorials in the in the [*tutorials* directory](https://github.com/katychuang/getting-started-with-haskell/tree/master/tutorials) 32 | 33 | ## Books & Book Clubs 34 | 35 | * [Anatomy of Programming Languages](http://www.cs.utexas.edu/~wcook/anatomy/) 36 | * [Big list of books](http://reinh.com/notes/posts/2014-07-25-recommended-reading-material.html) 37 | * [Haskell Data Analysis Cookbook](http://haskelldata.com) 38 | * [Real World Haskell](http://book.realworldhaskell.org) 39 | * [Real World Haskell Book Club](https://groups.google.com/forum/#!forum/real-world-haskell-book-club) 40 | * [Learn You a Haskell for Great Good!](http://learnyouahaskell.com/) 41 | 42 | ## Tips 43 | 44 | * [What I Wish I Knew When Learning Haskell 2.0](http://dev.stephendiehl.com/hask/) 45 | * [N00b FAQ](http://echo.rsmw.net/n00bfaq.html) 46 | * [Haskell Programming Tips](http://www.haskell.org/haskellwiki/Haskell_programming_tips) 47 | * [Twitter-Haskell Tips](https://twitter.com/HaskellTips) 48 | * [Simple Cabal Guide](http://katychuang.com/cabal-guide) 49 | 50 | ## Misc 51 | 52 | * [Scalable Program Architectures](https://news.ycombinator.com/item?id=7586812) 53 | * [iHaskell](http://gibiansky.github.io/IHaskell/) 54 | 55 | -------------------------------------------------------------------------------- /resources/reviews.md: -------------------------------------------------------------------------------- 1 | Here is a list of detailed reviews of learning resources. 2 | 3 | At this time I am too green with Haskell to personally provide specific recommendations personally, so here is a list of links to words from more experienced haskellers. 4 | 5 | 6 | ## Reviews 7 | 8 | * [How to Learn Haskell](https://github.com/bitemyapp/learnhaskell) 9 | * [Bitemyapp's view on books & courses](http://bitemyapp.com/posts/2014-12-31-functional-education.html) -------------------------------------------------------------------------------- /tutorials/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Project Index 3 | 4 | **Topic: Web development, JSON parsing** 5 | 6 | | Description | File | URLs | 7 | | ------------------------------------- | --------------------- | ------------------------ | 8 | | Aeson Library Basics | blah.hs | [Tutorial][1] | 9 | | Parsing Twitter API | twitter-fpcomplete.hs | [Tutorial][2] | 10 | | List trending venues (Foursquare API) | foursquaretrends.hs | [Tutorial][3], [Code][4] | 11 | | Hakyll set up | - | [Tutorial][5] | 12 | | Snap framework w/ PostgreSQL Setup | - | [Tutorial][6] | 13 | | A REST API with Haskell and Snap | - | [Tutorial][7] | 14 | | Effectful Haskell: IO, Monads, Functors| - | [Tutorial][8] | 15 | 16 | **Topic: General Functional Programming Concepts** 17 | 18 | | Description | File | URLs | 19 | | ------------------------------------- | --------------------- | ------------------------ | 20 | | Effectful Haskell: IO, Monads, Functors| - | [Tutorial][8] | 21 | 22 | 23 | 24 | [1]: http://blog.raynes.me/blog/2012/11/27/easy-json-parsing-in-haskell-with-aeson/ 25 | [2]: https://www.fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/json 26 | [3]: https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/foursquare-api-example 27 | [4]: https://github.com/wcauchois/haskell-foursquare-api-example 28 | [5]: http://yannesposito.com/Scratch/en/blog/Hakyll-setup/ 29 | [6]: http://janrain.com/blog/tutorial-building-a-sample-application-with-haskell-snap-postgresql-and-the-postgresql-simple-snaplet/ 30 | [7]: http://robots-staging.thoughtbot.com/a-rest-api-with-haskell-and-snap 31 | [8]: http://slpopejoy.github.io/posts/Effectful01.html 32 | 33 | --- 34 | 35 | **Topic: Numeric Functions & Data types** 36 | 37 | | Description | File | URLs | 38 | | ------------------------------------- | --------------------- | ------------------------ | 39 | | Image Ratio calculation | img-ratio.hs | | 40 | 41 | --- 42 | 43 | **Topic: Environments** 44 | 45 | | Description | File | URLs | 46 | | ------------------------------------- | --------------------- | ------------------------ | 47 | | iHaskell with [Blaze][301] html | blaze-ihaskell.ipynb | [Demo][301] | 48 | 49 | [301]: http://nbviewer.ipython.org/github/katychuang/getting-started-with-haskell/blob/master/tutorials/blaze-ihaskell.ipynb 50 | 51 | 52 | -------------------------------------------------------------------------------- /tutorials/blah.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Blah where 3 | 4 | import Data.Aeson ((.:), (.:?), decode, FromJSON(..), Value(..)) 5 | import Control.Applicative ((<$>), (<*>)) 6 | import Data.Scientific as Scientific 7 | import Data.Time.Format (parseTime) 8 | import Data.Time.Clock (UTCTime) 9 | import System.Locale (defaultTimeLocale) 10 | import Control.Monad (liftM) 11 | import Data.Attoparsec.Number (Number(..)) 12 | import qualified Data.HashMap.Strict as HM 13 | import qualified Data.ByteString.Lazy.Char8 as BS 14 | 15 | ------------------------------------------------------------------------ 16 | 17 | parseRHTime :: String -> Maybe UTCTime 18 | parseRHTime = parseTime defaultTimeLocale "%FT%X%QZ" 19 | 20 | -- match objects 21 | data Paste = Paste { getLines :: Scientific -- note difference from example 22 | , getDate :: Maybe UTCTime 23 | , getID :: String 24 | , getLanguage :: String 25 | , getPrivate :: Bool 26 | , getURL :: String 27 | , getUser :: Maybe String 28 | , getBody :: String 29 | } deriving (Show) 30 | 31 | -- define instance to make it possible to decode 32 | instance FromJSON Paste where 33 | parseJSON (Object v) = 34 | Paste <$> 35 | (v .: "lines") <*> 36 | liftM parseRHTime (v .: "date") <*> 37 | (v .: "paste-id") <*> 38 | (v .: "language") <*> 39 | (v .: "private") <*> 40 | (v .: "url") <*> 41 | (v .:? "user") <*> 42 | (v .: "contents") 43 | 44 | -- .:? allows keys to be null or not exist 45 | -- nested structures `((v .: "stuff") >>= (.: "language"))` 46 | -- { stuff: { language: en} } 47 | 48 | -- --function to extract the number of lines from the json 49 | getRHLines :: String -> Scientific -- note difference from example 50 | getRHLines json = 51 | case HM.lookup "lines" hm of 52 | Just (Number n) -> n 53 | Nothing -> error "Y U NO HAZ NUMBER?" 54 | where (Just (Object hm)) = decode (BS.pack json) :: Maybe Value 55 | 56 | ------------------------------------------------------------------------ 57 | 58 | {- 59 | # REFERENCE & LINKS 60 | 61 | This file comes from a tutorial published in Nov 2012. Some types have 62 | been changed to make it work with the latest version of Aeson 0.7.0.3 63 | The original version is published at: 64 | http://blog.raynes.me/blog/2012/11/27/easy-json-parsing-in-haskell-with-aeson/ 65 | 66 | 67 | The main change from original version published in 2012 was the input 68 | type change due to the Aeson library updates since then. The current as of Spring 2015 69 | aeson version 0.7.0.3 takes Scientific instead of Integer type. You can find 70 | types by typing `:t Number` into your ghci console. 71 | 72 | Reference for aeson 0.7.0.3 types can be found on hackage: 73 | http://hackage.haskell.org/package/aeson-0.7.0.3/docs/Data-Aeson-Types.html 74 | 75 | 76 | # Usage 77 | inside ghci, load the file with :load blah.hs 78 | then you can call the function and pass in json data 79 | getRHLines "{\"lines\":1,\"date\":\"2012-01-04T01:44:22.964Z\",\"paste-id\":\"1\",\"fork\":null,\"random-id\":\"f1fc1181fb294950ca4df7008\",\"language\":\"Clojure\",\"private\":false,\"url\":\"https://www.refheap.com/paste/1\",\"user\":\"raynes\",\"contents\":\"(begin)\"}" 80 | 81 | -} 82 | 83 | 84 | -------------------------------------------------------------------------------- /tutorials/blaze-ihaskell.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "text/plain": [ 13 | "5" 14 | ] 15 | }, 16 | "metadata": {}, 17 | "output_type": "display_data" 18 | } 19 | ], 20 | "source": [ 21 | "2+3" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "# HTML \n", 29 | "\n", 30 | "* http://jaspervdj.be/blaze/tutorial.html" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": { 37 | "collapsed": true 38 | }, 39 | "outputs": [], 40 | "source": [ 41 | "{-# LANGUAGE OverloadedStrings #-}" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 3, 47 | "metadata": { 48 | "collapsed": true 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "import Control.Monad (forM_)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 4, 58 | "metadata": { 59 | "collapsed": true 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "import Text.Blaze.Html5 as H" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 5, 69 | "metadata": { 70 | "collapsed": true 71 | }, 72 | "outputs": [], 73 | "source": [ 74 | "import Text.Blaze.Html5.Attributes as A" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 7, 80 | "metadata": { 81 | "collapsed": false 82 | }, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/html": [ 87 | "
Redundant do
Found:
do H.title \"Natural numbers\"
Why Not:
H.title \"Natural numbers\"
" 184 | ], 185 | "text/plain": [ 186 | "Line 3: Redundant do\n", 187 | "Found:\n", 188 | "do H.title \"Natural numbers\"\n", 189 | "Why not:\n", 190 | "H.title \"Natural numbers\"" 191 | ] 192 | }, 193 | "metadata": {}, 194 | "output_type": "display_data" 195 | } 196 | ], 197 | "source": [ 198 | "numbers :: Int -> Html\n", 199 | "numbers n = docTypeHtml $ do\n", 200 | " H.head $ do\n", 201 | " H.title \"Natural numbers\"\n", 202 | " body $ do\n", 203 | " p \"A list of natural numbers:\"\n", 204 | " ul $ forM_ [1 .. n] (li . toHtml)" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 8, 210 | "metadata": { 211 | "collapsed": false 212 | }, 213 | "outputs": [ 214 | { 215 | "data": { 216 | "text/html": [ 217 | "\n", 314 | "\n", 315 | "\n", 316 | " \n", 317 | " \n", 318 | " Natural numbers\n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | "

\n", 323 | " A list of natural numbers:\n", 324 | "

\n", 325 | " \n", 336 | " \n", 337 | "\n" 338 | ], 339 | "text/plain": [ 340 | "\n", 341 | "\n", 342 | "\n", 343 | " \n", 344 | " \n", 345 | " Natural numbers\n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | "

\n", 350 | " A list of natural numbers:\n", 351 | "

\n", 352 | " \n", 363 | " \n", 364 | "" 365 | ] 366 | }, 367 | "metadata": {}, 368 | "output_type": "display_data" 369 | } 370 | ], 371 | "source": [ 372 | "numbers 3" 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 13, 378 | "metadata": { 379 | "collapsed": true 380 | }, 381 | "outputs": [], 382 | "source": [ 383 | "import Text.Blaze.Renderer.Pretty\n", 384 | "import Text.Blaze.Renderer.String" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": 14, 390 | "metadata": { 391 | "collapsed": false 392 | }, 393 | "outputs": [ 394 | { 395 | "data": { 396 | "text/plain": [ 397 | "\"\\nNatural numbers

A list of natural numbers:

\"" 398 | ] 399 | }, 400 | "metadata": {}, 401 | "output_type": "display_data" 402 | } 403 | ], 404 | "source": [ 405 | "Text.Blaze.Renderer.String.renderHtml $ numbers 3" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "metadata": { 412 | "collapsed": true 413 | }, 414 | "outputs": [], 415 | "source": [] 416 | } 417 | ], 418 | "metadata": { 419 | "kernelspec": { 420 | "display_name": "Haskell", 421 | "language": "haskell", 422 | "name": "haskell" 423 | } 424 | }, 425 | "nbformat": 4, 426 | "nbformat_minor": 0 427 | } 428 | -------------------------------------------------------------------------------- /tutorials/clay/clay-custom.hs: -------------------------------------------------------------------------------- 1 | -- clay-custom.hs 2 | -- This is an example of adding custom values using the other type class or 3 | -- fallback operator `-:` to explicitly specify values 4 | -- http://www.shakthimaan.com/posts/2016/01/27/haskell-web-programming/news.html 5 | 6 | {-# LANGUAGE OverloadedStrings #-} 7 | 8 | import Clay 9 | 10 | main :: IO () 11 | main = putCss $ 12 | body ? 13 | do fontSize (other "11pt !important") 14 | "border" -: "0" 15 | -------------------------------------------------------------------------------- /tutorials/clay/clay-pre.hs: -------------------------------------------------------------------------------- 1 | -- clay-pre.hs 2 | -- This is a more comprehensive eample from: 3 | -- http://www.shakthimaan.com/posts/2016/01/27/haskell-web-programming/news.html 4 | 5 | {-# LANGUAGE OverloadedStrings #-} 6 | 7 | import Clay 8 | 9 | main :: IO () 10 | main = putCss $ 11 | pre ? 12 | do border dotted (pt 1) black 13 | whiteSpace (other "pre") 14 | fontSize (other "8pt") 15 | overflow (other "auto") 16 | padding (em 20) (em 0) (em 20) (em 0) 17 | 18 | {- 19 | - Compile with `ghc --make clay-pre.hs` 20 | - Execute ./clay-pre to produce the css 21 | - -} 22 | -------------------------------------------------------------------------------- /tutorials/clay/clay-simple.hs: -------------------------------------------------------------------------------- 1 | -- clay-simple.hs 2 | -- http://www.shakthimaan.com/posts/2016/01/27/haskell-web-programming/news.html 3 | 4 | {-# LANGUAGE OverloadedStrings #-} 5 | 6 | import Clay 7 | 8 | main :: IO () 9 | main = putCss exampleStylesheet 10 | 11 | exampleStylesheet :: Css 12 | exampleStylesheet = body ? fontFamily ["Baskerville", "Georgia", "Garamond", "Times"] [serif] 13 | 14 | {- 15 | - Compile with the command `ghc --make clay-simple.hs` 16 | - Then execute `./clay-simple` to generate the css output 17 | - -} 18 | -------------------------------------------------------------------------------- /tutorials/foursquaretrends.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | import Network.HTTP.Conduit 4 | import Data.Aeson 5 | import Data.Aeson.Types 6 | import Network.HTTP.Conduit 7 | import Data.Char(toLower) 8 | import qualified Data.Text as T 9 | import qualified Data.Vector as V 10 | import Data.Maybe 11 | import Data.List(find, intercalate) 12 | import Network.HTTP(urlEncode) 13 | 14 | 15 | ------------------------------------------------------------------------ 16 | -- Endpoint 17 | ------------------------------------------------------------------------ 18 | 19 | class Endpoint a where 20 | buildURI :: a -> String 21 | 22 | callJsonEndpoint :: (FromJSON j, Endpoint e) => e -> IO j 23 | callJsonEndpoint e = 24 | do responseBody <- simpleHttp (buildURI e) 25 | case eitherDecode responseBody of 26 | Left err -> fail err 27 | Right res -> return res 28 | 29 | ------------------------------------------------------------------------ 30 | -- GeocoderEndpoint 31 | ------------------------------------------------------------------------ 32 | 33 | 34 | -- https://developers.google.com/maps/documentation/geocoding/ 35 | data GeocoderEndpoint = 36 | GeocodeEndpoint { address :: String, sensor :: Bool } 37 | 38 | -- This instance converts a data structure into a URI, by passing in 39 | -- paramater values as variables 40 | instance Endpoint GeocoderEndpoint where 41 | buildURI GeocodeEndpoint { address = address, sensor = sensor } = 42 | let params = [("address", Just address), ("sensor", Just $ map toLower $ show sensor)] 43 | in "http://maps.googleapis.com/maps/api/geocode/json" ++ renderQuery True params 44 | 45 | 46 | ------------------------------------------------------------------------ 47 | -- GeocoderModel 48 | ------------------------------------------------------------------------ 49 | 50 | 51 | data GeocodeResponse = GeocodeResponse LatLng deriving Show 52 | 53 | instance FromJSON GeocodeResponse where 54 | parseJSON val = 55 | do let Object obj = val 56 | (Array results) <- obj .: "results" 57 | (Object location) <- navigateJson (results V.! 0) ["geometry", "location"] 58 | (Number lat) <- location .: "lat" 59 | (Number lng) <- location .: "lng" 60 | return $ GeocodeResponse (realToFrac lat, realToFrac lng) 61 | 62 | 63 | ------------------------------------------------------------------------ 64 | -- FoursquareEndpoint 65 | ------------------------------------------------------------------------ 66 | 67 | 68 | foursquareApiVersion = "20130721" 69 | 70 | data FoursquareCredentials = FoursquareCredentials { clientId :: String, clientSecret :: String } 71 | 72 | data FoursquareEndpoint = 73 | VenuesTrendingEndpoint { ll :: LatLng, limit :: Maybe Int, radius :: Maybe Double } 74 | 75 | instance Endpoint FoursquareEndpoint where 76 | buildURI VenuesTrendingEndpoint {ll = ll, limit = limit, radius = radius} = 77 | let params = [("ll", Just $ renderLatLng ll), ("limit", fmap show limit), ("radius", fmap show radius)] 78 | in "https://api.foursquare.com/v2/venues/trending" ++ renderQuery True params 79 | 80 | data AuthorizedFoursquareEndpoint = AuthorizedFoursquareEndpoint FoursquareCredentials FoursquareEndpoint 81 | 82 | instance Endpoint AuthorizedFoursquareEndpoint where 83 | buildURI (AuthorizedFoursquareEndpoint creds e) = appendParams originalUri authorizationParams 84 | where originalUri = buildURI e 85 | authorizationParams = [("client_id", Just $ clientId creds), 86 | ("client_secret", Just $ clientSecret creds), 87 | ("v", Just foursquareApiVersion)] 88 | 89 | authorizeWith :: FoursquareEndpoint -> FoursquareCredentials -> AuthorizedFoursquareEndpoint 90 | authorizeWith = flip AuthorizedFoursquareEndpoint 91 | 92 | 93 | ------------------------------------------------------------------------ 94 | -- FoursquareModel 95 | ------------------------------------------------------------------------ 96 | 97 | 98 | withFoursquareResponse :: (Object -> Parser a) -> Value -> Parser a 99 | withFoursquareResponse func val = do let Object obj = val 100 | response <- obj .: "response" 101 | func response 102 | 103 | data Venue = Venue { venueId :: String, name :: String } deriving Show 104 | 105 | data VenuesTrendingResponse = VenuesTrendingResponse { venues :: [Venue] } deriving Show 106 | 107 | instance FromJSON VenuesTrendingResponse where 108 | parseJSON = withFoursquareResponse parseResponse 109 | where parseResponse :: Object -> Parser VenuesTrendingResponse 110 | parseResponse obj = do (Array venues) <- obj .: "venues" 111 | parsedVenues <- V.mapM (\(Object o) -> parseVenue o) venues 112 | return $ VenuesTrendingResponse $ V.toList parsedVenues 113 | parseVenue :: Object -> Parser Venue 114 | parseVenue obj = do (String idText) <- obj .: "id" 115 | (String nameText) <- obj .: "name" 116 | return $ Venue { venueId = T.unpack idText, name = T.unpack nameText } 117 | 118 | ------------------------------------------------------------------------ 119 | -- Core 120 | ------------------------------------------------------------------------ 121 | 122 | type LatLng = (Double, Double) 123 | 124 | renderLatLng :: LatLng -> String 125 | renderLatLng (lat, lng) = show lat ++ "," ++ show lng 126 | 127 | navigateJson :: Value -> [T.Text] -> Parser Value 128 | navigateJson (Object obj) (first : second : rest) = 129 | do next <- obj .: first 130 | navigateJson next (second : rest) 131 | navigateJson (Object obj) [last] = obj .: last 132 | 133 | renderQuery :: Bool -> [(String, Maybe String)] -> String 134 | renderQuery b params = (if b then "?" else "") ++ intercalate "&" serializedParams 135 | where serializedParams = catMaybes $ map renderParam params 136 | renderParam (key, Just val) = Just $ key ++ "=" ++ (urlEncode val) 137 | renderParam (_, Nothing) = Nothing 138 | 139 | appendParams :: String -> [(String, Maybe String)] -> String 140 | appendParams uri params 141 | | isJust (find (=='?') uri) = uri ++ "&" ++ renderQuery False params 142 | | otherwise = uri ++ renderQuery True params 143 | 144 | 145 | ------------------------------------------------------------------------ 146 | -- Main 147 | ------------------------------------------------------------------------ 148 | 149 | -- | Main entry point to the application. 150 | 151 | targetAddress = "568 Broadway, New York, NY" 152 | 153 | -- | The main entry point. 154 | main :: IO () 155 | main = 156 | do putStrLn "API key?" 157 | apiKey <- getLine 158 | putStrLn "API secret?" 159 | apiSecret <- getLine 160 | let creds = FoursquareCredentials apiKey apiSecret 161 | 162 | (GeocodeResponse latLng) <- callJsonEndpoint $ GeocodeEndpoint targetAddress False 163 | let venuesTrendingEndpoint = VenuesTrendingEndpoint latLng Nothing Nothing `authorizeWith` creds 164 | (VenuesTrendingResponse venues) <- callJsonEndpoint venuesTrendingEndpoint 165 | let printVenue v = putStrLn $ "- " ++ name v 166 | mapM_ printVenue venues 167 | 168 | 169 | ------------------------------------------------------------------------ 170 | 171 | {- 172 | 173 | # REFERENCE & LINKS 174 | 175 | This file comes from a tutorial published in Aug 2013. 176 | The original version is published at: https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/foursquare-api-example 177 | 178 | In order to use this code, you'll need to install http-conduit and aeson. 179 | They can be installed through cabal with the following command: 180 | 181 | `prompt$ cabal install http-conduit aeson` 182 | 183 | -} 184 | -------------------------------------------------------------------------------- /tutorials/img-ratio.hs: -------------------------------------------------------------------------------- 1 | -- Author: @katychuang 2 | -- These are functions I wrote to practice thinking in Haskell to help with 3 | -- photo processing work. See instructions below on how to use ghci 4 | 5 | ------------------------------------------------------------------------ 6 | -- Files begin with some settings to make sure ghc runs smoothly. 7 | 8 | {-# LANGUAGE OverloadedStrings #-} 9 | {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} 10 | ------------------------------------------------------------------------ 11 | -- Modules start with imports. 12 | 13 | -- We don't need to import anything for this file. 14 | 15 | ------------------------------------------------------------------------ 16 | -- Simple exercises 17 | 18 | -- reduce input number in half 19 | shrinkWidth :: Double -> Double 20 | shrinkWidth x = x / 2 21 | 22 | shrinkHeight :: Double -> Double 23 | shrinkHeight x = x / 2 24 | 25 | -- Passing in width and height as a tuple 26 | shrink :: (Double, Double) -> (Double, Double) 27 | shrink (x, y) = (x / 2, y / 2) 28 | 29 | -- Passing in in width and height as a list 30 | shrink' :: [Double] -> [Double] 31 | shrink' theList = map (/2) theList 32 | 33 | -- Reduce a list of numbers by percentage (i.e. 0.5 for half size) indicated as the 2nd item in a tuple 34 | redux :: ([Double], Double) -> [Double] 35 | redux (theList, percentShrink) = map (*percentShrink) theList 36 | 37 | -- Reduce a pair of width/height pair in a tuple, by a given factor, i.e. 0.5 38 | redux' :: Double -> (Double, Double) -> (Double, Double) 39 | redux' factor (x, y) = (x * factor, y * factor) 40 | 41 | ------------------------------------------------------------------------ 42 | -- Return calculations at 0.75, 0.5, 0.25 increments 43 | quarters :: (Double, Double) -> [(Double, Double)] 44 | quarters (x, y) = [(x * 0.75, y * 0.75), (x * 0.5, y * 0.5), (x * 0.25, y * 0.25)] 45 | 46 | -- Return calculations at 0.10 increments, i.e. 0.9, 0.8, 0.7, etc 47 | tenths :: (Double, Double) -> [(Double, Double)] 48 | tenths (x, y) = [(x * 0.9, y * 0.9), (x * 0.8, y * 0.8), (x * 0.7, y * 0.7), 49 | (x * 0.6, y * 0.6), (x * 0.5, y * 0.5), (x * 0.4, y * 0.4), 50 | (x * 0.3, y * 0.3), (x * 0.2, y * 0.2), (x * 0.1, y * 0.1)] 51 | 52 | -- Return calculations at 0.10 increments, i.e. 0.9, 0.8, 0.7, etc (with recursion to keep it a oneliner) 53 | tenths' :: (Double, Double) -> [(Double, Double)] 54 | tenths' (x, y) = reverse $ [(x * factor / 10, y * factor / 10) | factor <- [1..9]] 55 | 56 | 57 | ------------------------------------------------------------------------ 58 | {- 59 | 60 | How to run this script: 61 | 62 | 1. Open up ghci in your console and load this file `:l img-ratio.hs` or 63 | `:load img-ratio.hs`. :l is short for :load. 64 | 65 | 2. Call one of the defined functions, i.e. `shrinkWidth 1024` will return 66 | the value of 1024 divided by 2; the output will be 512.0. 67 | 68 | Note the data types in the function to understand correct input for 69 | each method. For instance, shrinkWidth takes a Double type, so passing 70 | in a string such as `blue` will produce an error "Not in scope: `blue'". 71 | Similarly, for shrink' (pronounced shrink prime), this function takes a list of 72 | Doubles. Passing in a different type, such as a tuple 73 | `shrink' (1024, 768)`, will give you an error: 74 | Couldn't match expected type `[Double]' with actual type `(t0, t1)' 75 | - In the first argument of shrink', namely `(1024, 768)' 76 | - In the expression: shrink' (1024, 768) 77 | - In an equation for `it': it = shrink' (1024, 768) 78 | 79 | Another example of wrong input - passing in a list of strings: 80 | > `shrink' ["red", "white", "blue"]` 81 | Couldn't match expected type `Double' with actual type `[Char]' 82 | In the expression: "red" 83 | In the first argument of shrink', namely `["red", "white", "blue"]' 84 | In the expression: shrink' ["red", "white", "blue"] 85 | -} 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/README.md: -------------------------------------------------------------------------------- 1 | # Reflex examples 2 | 3 | For these examples you will need to install reflex-platform. 4 | 5 | ``` 6 | $ git clone git@github.com:reflex-frp/reflex-platform.git 7 | $ ./try-reflex 8 | ``` 9 | 10 | Once you're in the nix shell, you can run the command `ghcjs file.hs` as it'll compile the haskell code into a single page application. 11 | 12 | --- 13 | ## Examples 14 | 15 | 1. NASA Picture of the Day api client 16 | 2. Type Ahead autocomplete 17 | 3. using CSS 18 | 4. rendering SVG 19 | 20 | 21 | ### 1. nasa-pod.hs 22 | 23 | You'll need to get an API key first, from [https://api.nasa.gov](https://api.nasa.gov). Then with this key you can enter it into the textbox in the compiled gui. This example is also available at [reflex-examples](https://github.com/reflex-frp/reflex-examples/blob/master/nasa-pod/workshop.hs). 24 | 25 | ![](static/nasa-pod.jpg) 26 | 27 | This example was from a live-coding workshop at the NY Haskell Users Group by Ali Abrar. 28 | 29 | --- 30 | 31 | ### 2. Type Ahead autocomplete 32 | 33 | Code is available from [@ali-abrar](https://gist.github.com/ali-abrar/2a52593f3a391d82c40f439d4894f017) 34 | 35 | This example was from a live-coding workshop at the NY Haskell Users Group by Ali Abrar. 36 | 37 | --- 38 | 39 | ### 3. using CSS 40 | 41 | ![](static/css.jpg) 42 | 43 | This example was from @katychuang 44 | 45 | --- 46 | 47 | ### 4. rendering SVG 48 | 49 | ![](static/svg.jpg) 50 | 51 | This example was from @katychuang 52 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/css.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | import Reflex.Dom 3 | import Data.FileEmbed 4 | 5 | -- entry point for any haskell application is main function 6 | main = mainWidgetWithCss $(embedFile "helloworld.css") $ do 7 | el "h1" $ 8 | text "hello world" 9 | elAttr "div" ("class" =: "box") $ 10 | text "hello world" 11 | elAttr "div" ("style" =: "border:1px solid blue; border-radius:100px; text-align:center; height:200px; width:200px; display:table-cell; vertical-align:middle") $ 12 | text "hello world" 13 | return () 14 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/helloworld.css: -------------------------------------------------------------------------------- 1 | .box { 2 | border: 1px dashed black; 3 | height: 100px; 4 | width: 100px; 5 | vertical-align: middle; 6 | display: table-cell; 7 | text-align: center; 8 | } -------------------------------------------------------------------------------- /tutorials/reflex-frp/nasa-pod.hs: -------------------------------------------------------------------------------- 1 | -- Haskell workshop example of using Reflex-FRP 2 | -- Created: April 23, 206 3 | 4 | {- 5 | Go to https://api.nasa.gov to get an API Key 6 | -} 7 | 8 | {-# LANGUAGE ScopedTypeVariables, DeriveGeneric #-} 9 | import Reflex.Dom 10 | import qualified Data.Text as T 11 | import Data.Maybe 12 | import GHC.Generics 13 | import Data.Aeson 14 | import qualified Data.Map as Map 15 | 16 | data NasaPicture = NasaPicture { copyright :: Maybe String 17 | , date :: String 18 | , explanation :: String 19 | , hdurl :: String 20 | , media_type :: String 21 | , service_version :: String 22 | , title :: String 23 | , url :: String 24 | } 25 | deriving (Show, Generic) 26 | 27 | -- basic way to interact with the json api 28 | instance FromJSON NasaPicture 29 | 30 | -- entry point for any haskell application is main function 31 | main :: IO () 32 | main = mainWidget workshop 33 | 34 | workshop :: forall t m. MonadWidget t m => m () 35 | workshop = do 36 | elClass "h1" "title" $ text "NASA Picture of the Day" 37 | t <- textInput def --def provides default input 38 | let apiKey = _textInput_value t 39 | 40 | b :: Event t () <- button "Send Request" 41 | 42 | let apiKeyButtonEvent :: Event t String = tagDyn apiKey b 43 | apiKeyEnterEvent :: Event t String = tagDyn apiKey (textInputGetEnter t) 44 | 45 | -- if both events fire at the same time the first on the list is picked first. 46 | apiKeyEvent :: Event t String = leftmost [ apiKeyButtonEvent 47 | , apiKeyEnterEvent 48 | ] 49 | 50 | submittedApiKey :: Dynamic t String <- holdDyn "NO STRING SUBMITTED" apiKeyEvent 51 | --dynText submittedApiKey 52 | 53 | let req :: Event t XhrRequest = fmap apiKeyToXhrRequest apiKeyEvent 54 | rsp :: Event t XhrResponse <- performRequestAsync req 55 | let rspText :: Event t (Maybe T.Text) = fmap _xhrResponse_responseText rsp 56 | rspString :: Event t String = fmapMaybe (\mt -> fmap T.unpack mt) rspText 57 | 58 | respDyn <- holdDyn "No Response" rspString 59 | --dynText respDyn 60 | 61 | divClass "break" $ return () 62 | 63 | -- have to tell it what the blob of text should represent 64 | let decoded :: Event t (Maybe NasaPicture) = fmap decodeXhrResponse rsp 65 | 66 | dynPic :: Dynamic t (Maybe NasaPicture) <- holdDyn Nothing decoded 67 | 68 | dynPicString <- mapDyn show dynPic 69 | --dynText dynPicString 70 | 71 | imgAttrs :: Dynamic t (Map.Map String String) <- forDyn dynPic $ \np -> 72 | case np of 73 | Nothing -> Map.empty 74 | Just pic -> Map.singleton "src" (url pic) 75 | -- reflex gives you syntactic sugar which can be Just pic -> "src" =: url pic 76 | 77 | elDynAttr "img" imgAttrs $ return () 78 | 79 | return () --return unit because mainWidget requires the type return 80 | 81 | -- function builds the request 82 | apiKeyToXhrRequest :: String -> XhrRequest 83 | apiKeyToXhrRequest k = XhrRequest { _xhrRequest_method = "GET" 84 | , _xhrRequest_url = "https://api.nasa.gov/planetary/apod?api_key=" ++ k 85 | , _xhrRequest_config = def 86 | } 87 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/static/css.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katychuang/getting-started-with-haskell/98cbead7a720a210043d027b1acb7bb73a8ef46b/tutorials/reflex-frp/static/css.jpg -------------------------------------------------------------------------------- /tutorials/reflex-frp/static/nasa-pod.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katychuang/getting-started-with-haskell/98cbead7a720a210043d027b1acb7bb73a8ef46b/tutorials/reflex-frp/static/nasa-pod.jpg -------------------------------------------------------------------------------- /tutorials/reflex-frp/static/svg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katychuang/getting-started-with-haskell/98cbead7a720a210043d027b1acb7bb73a8ef46b/tutorials/reflex-frp/static/svg.jpg -------------------------------------------------------------------------------- /tutorials/reflex-frp/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: arial; 3 | color: #222; 4 | } 5 | 6 | h1, h2, p { 7 | font-size: 12pt; 8 | } 9 | 10 | h1 { 11 | visibility: hidden; 12 | } 13 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/svg.hs: -------------------------------------------------------------------------------- 1 | import Reflex.Dom 2 | import Data.Map (fromList) 3 | import qualified Data.Map as Map 4 | import Data.Monoid 5 | 6 | main = mainWidget $ myDiv 7 | 8 | myDiv = do 9 | let attrs = constDyn $ fromList 10 | [ ("width" , "100") 11 | , ("height" , "100") 12 | ] 13 | let cAttrs = constDyn $ fromList 14 | [ ("cx", "50") 15 | , ("cy", "50") 16 | , ("r", "40") 17 | , ("stroke", "green") 18 | , ("stroke-width", "3") 19 | , ("fill", "yellow" ) 20 | ] 21 | 22 | s <- elSvg "svg" attrs (elSvg "circle" cAttrs (return())) 23 | 24 | return () 25 | 26 | elSvg tag a1 a2 = do 27 | elDynAttrNS' ns tag a1 a2 28 | return () 29 | 30 | ns :: Maybe String 31 | ns = Just "http://www.w3.org/2000/svg" 32 | 33 | 34 | -------------------------------------------------------------------------------- /tutorials/reflex-frp/xmas.hs: -------------------------------------------------------------------------------- 1 | {- Example written by Dr. Kat Chuang 2 | @katychuang on GitHub 3 | 4 | To run this file, install Reflex-Dom, and then convert to html/js 5 | with the command `ghcjs xmas.hs` 6 | -} 7 | 8 | {-# LANGUAGE OverloadedStrings, TemplateHaskell #-} 9 | 10 | import Reflex.Dom 11 | import Data.FileEmbed 12 | import Data.Time (Day, diffDays, fromGregorian, getCurrentTime, utctDay) 13 | import System.Locale (defaultTimeLocale) 14 | import Control.Monad.IO.Class (liftIO) 15 | import Data.Text (pack) 16 | 17 | 18 | 19 | main :: IO () 20 | main = mainWidgetWithCss $(embedFile "style.css") $ do 21 | el "h1" $ text "" 22 | el "div" $ do 23 | el "h2" $ text "Days since last Christmas" 24 | el "p" $ bodyElement 25 | 26 | 27 | -- This function returns the text value 28 | bodyElement :: MonadWidget t m => m() 29 | bodyElement = do 30 | now <- liftIO getCurrentTime 31 | let delta = showText $ daysSinceXmas now 32 | text delta 33 | where 34 | lastXmas = (fromGregorian 2017 12 25) 35 | daysSinceXmas a = diffDays (utctDay a) lastXmas 36 | showText s = pack $ show s 37 | -------------------------------------------------------------------------------- /tutorials/twitter-fpcomplete.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, DeriveGeneric #-} 2 | 3 | import Data.ByteString (ByteString) 4 | import Network.HTTP.Conduit 5 | import Web.Authenticate.OAuth 6 | import Data.Aeson 7 | import Data.Time.Clock (UTCTime) 8 | import Data.Text (Text) 9 | import GHC.Generics 10 | 11 | -- Insert here your own credentials 12 | 13 | myoauth :: OAuth 14 | myoauth = 15 | newOAuth { oauthServerName = "api.twitter.com" 16 | , oauthConsumerKey = "your consumer key here" 17 | , oauthConsumerSecret = "your consumer secret here" 18 | } 19 | 20 | mycred :: Credential 21 | mycred = newCredential "your access token here" 22 | "your access token secret here" 23 | 24 | -- | Type for tweets. Use only the fields you are interested in. 25 | -- The parser will filter them. To see a list of available fields 26 | -- see . 27 | data Tweet = 28 | Tweet { text :: !Text 29 | } deriving (Show, Generic) 30 | 31 | instance FromJSON Tweet 32 | instance ToJSON Tweet 33 | 34 | -- | This function reads a timeline JSON and parse it using the 'Tweet' type. 35 | timeline :: String -- ^ Screen name of the user 36 | -> IO (Either String [Tweet]) -- ^ If there is any error parsing the JSON data, it 37 | -- will return 'Left String', where the 'String' 38 | -- contains the error information. 39 | timeline name = do 40 | -- Firstly, we create a HTTP request with method GET (it is the default so we don't have to change that). 41 | req <- parseUrl $ "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" ++ name 42 | -- Using a HTTP manager, we authenticate the request and send it to get a response. 43 | res <- withManager $ \m -> do 44 | -- OAuth Authentication. 'signOAuth' modifies the HTTP header adding the 45 | -- appropriate authentication. 46 | signedreq <- signOAuth myoauth mycred req 47 | -- Send request. 48 | httpLbs signedreq m 49 | -- Decode the response body. 50 | return $ eitherDecode $ responseBody res 51 | 52 | -- | The main function, as an example of how to use the 'timeline' 53 | -- function. 54 | main :: IO () 55 | main = do 56 | -- Read the timeline from Hackage user. Feel free to change the screen 57 | -- name to any other. 58 | ets <- timeline "Hackage" 59 | case ets of 60 | -- When the parsing of the JSON data fails, we report it. 61 | Left err -> putStrLn err 62 | -- When successful, print in the screen the first 5 tweets. 63 | Right ts -> mapM_ print $ take 5 ts 64 | 65 | 66 | ------------------------------------------------------------------------ 67 | 68 | {- 69 | # REFERENCE & LINKS 70 | 71 | Originally published at: 72 | https://www.fpcomplete.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/json 73 | 74 | # Usage 75 | 76 | 1. Run this `cabal install authenticate-oauth` 77 | 78 | 2. load this file into ghci with `:l twitter-fpcomplete.hs` 79 | ghci to open ghci (or you can use cabal repl) 80 | :l or :load to load a haskell file 81 | 82 | 3. run Main: 83 | In your ghci console, it'll show `Prelude>` 84 | From there, type in `main` 85 | 86 | -} 87 | --------------------------------------------------------------------------------