├── .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",
326 | " - \n",
327 | " 1\n",
328 | "
\n",
329 | " - \n",
330 | " 2\n",
331 | "
\n",
332 | " - \n",
333 | " 3\n",
334 | "
\n",
335 | "
\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",
353 | " - \n",
354 | " 1\n",
355 | "
\n",
356 | " - \n",
357 | " 2\n",
358 | "
\n",
359 | " - \n",
360 | " 3\n",
361 | "
\n",
362 | "
\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 numbersA 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 | 
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 | 
42 |
43 | This example was from @katychuang
44 |
45 | ---
46 |
47 | ### 4. rendering SVG
48 |
49 | 
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 |
--------------------------------------------------------------------------------