packages feed

lima 0.1.0.5 → 0.1.0.6

raw patch · 13 files changed

+1840/−35 lines, 13 filesdep +string-interpolate

Dependencies added: string-interpolate

Files

README.md view
@@ -2,57 +2,83 @@  Convert between -- `Haskell` (`.hs`) files with `GitHub`-flavoured `Markdown` comments and `Markdown` (`.md`)-- `Literate Haskell` (`.lhs`) files and `GitHub`-flavoured `Markdown` (`.md`).+- `Haskell` (`.hs`) and `GitHub Flavored Markdown` (`.md`)+- `Literate Haskell` (`.lhs`) and `GitHub Flavored Markdown` (`.md`).  It is usually possible to make conversion abide the [roundtrip property](https://jesper.sikanda.be/posts/quickcheck-intro.html). In other words, make conversions `file.lhs` -> `file.lhs.md` -> `file.lhs.md.lhs` or `file.hs` -> `file.hs.md` -> `file.hs.md.hs` and get `file.lhs = file.lhs.md.lhs` and `file.hs = file.hs.md.hs` in terms of their contents. +## Demo++![demo](https://github.com/deemp/flakes/blob/main/lima/README/demo.png?raw=true)+ ## Alternatives  - [LiterateMarkdown](https://github.com/haskie-lambda/LiterateMarkdown). `lima` is a fork of this (abandoned?) project. Initially, I just wanted to fix some bugs, but then realized that I can't conveniently use `Haskell Language Server` with `.lhs` files so I added the `.hs` -> `.md` conversion. -- [IHaskell](https://github.com/IHaskell/IHaskell) - create `Jupyter` notebooks with `Haskell` code cells and `GitHub`-flavoured `Markdown` text cells and do much more!+- [IHaskell](https://github.com/IHaskell/IHaskell) - create `Jupyter` notebooks with `Haskell` code cells and `GitHub Flavored Markdown` text cells and do much more!  ## Conversion -### hs <-> md+### .hs -> .md -[Examples](./testdata/hs)+#### Examples -Rules:+1. [hs](./testdata/hs/input0.hs) -> [hs.md](./testdata/hs/input0.hs.md) -> [hs.md.hs](./testdata/hs/input0.hs.md.hs) -- To produce text blocks, write multiline comments in `GitHub`-flavoured `Markdown`-- Such comments should start with `{- ` or `{-\n`-- Multiline comments (even `{- -}`) split `Haskell` code into snippets-- Special comments like `{- FOURMOLU_ENABLE -}` won't appear in a `.md`. You can supply other comments in a config (`hs-md.special-comments`). See the sample [config](./testdata/config/).-- You can ignore parts of a `.hs` file by enclosing them into `{- LIMA_DISABLE -}` and `{- LIMA_ENABLE -}`. The lines between such comments will be commented out in the resulting `.md`.-- You can indent a Haskell snippet by writing before it a magic comment `{- LIMA_INDENT N -}`, where `N` is an integer denoting the new indentation level. Reset the indentation by writing `{- LIMA_DEDENT -}`.+#### Rules -### lhs <-> md+- **Multiline comments**:+  - Become text blocks.+  - Should be written:+    - in `GitHub Flavored Markdown`;+    - on one line: `{- <COMMENTS> -}`;+    - on many lines: `{-\n<COMMENTS>\n-}`.+  - Split `Haskell` code in `.md`.+- **Special comments**:+  - Become comments in `.md`.+  - Can be supplied in a config via `lima --config <your config path>` ([sample config](./testdata/config/))+  - Should be written on a single line: `{- <COMMENTS> -}`+  - `{- LIMA_INDENT N -}` increases the indentation of snippets by `N`+  - `{- LIMA_DEDENT -}` sets the indentation of snippets to `0`+  - `{- LIMA_DISABLE -}` starts copying the following lines verbatim+  - `{- LIMA_ENABLE -}` stops copying the following lines verbatim+- **Code and single-line comments**:+  - Become `hs` snippets in `.md` -Examples:+### .lhs -> .md -- [input0.lhs](./testdata/input0.lhs) -> [input0.lhs.md](./testdata/input0.lhs.md)-- [input1.lhs](./testdata/input1.lhs) -> [input1.lhs.md](./testdata/input1.lhs.md)+#### Examples -Rules:+1. [hs](./testdata/lhs/input0.lhs) -> [hs.md](./testdata/lhs/input0.lhs.md) -> [hs.md.hs](./testdata/lhs/input0.lhs.md.lhs)+1. [hs](./testdata/lhs/input1.lhs) -> [hs.md](./testdata/lhs/input1.lhs.md) -> [hs.md.hs](./testdata/lhs/input1.lhs.md.lhs) -- As `.lhs` doesn't support `#` (heading) or `>` (quotation start) at a line beginning, one should write ` #` and ` >` instead.+#### Rules -  - ` #` -> `#` -> ` #`-  - ` >` -> `>` -> ` >`+- **Text**:+  - As `.lhs` doesn't support `#` (heading) or `>` (quotation start) at a line beginning, write ` #` and ` >` instead. `lhs` -> `lhs.md` -> `lhs.md.lhs`: -- If you'd like to provide some code snippet in a `.lhs`, follow these rules:+    - <code>&nbsp;#</code> -> `#` -> <code>&nbsp;#</code>+    - <code>&nbsp;></code> -> `>` -> <code>&nbsp;></code>+- **Snippets**: -  - `>` is for `Haskell` code, there should be an empty line before and after the block with `Haskell` code+  - `>` is for `Haskell` code. There should be an empty line before and after the block with `Haskell` code   - `<` is for any other code. Such code will be converted into code blocks of type `console` in `.md`-  - The round-trip property is not guarranteed if you insert code snippets into `.lhs` using three backticks-    - Nevertheless, feel free to insert them into `.md`. In `.lhs`, they will just lose the language info+  - Snippets in <code>\`\`\`</code> become <code>\`\`\`console</code> and then `<`.+    - The round-trip property is not guarranteed  ## Command-line tool -### cabal+### From Hackage +1. Install via cabal++    ```console+    cabal update+    cabal install lima+    ```++### From sources+ 1. Clone this repo and install `lima`.      ```console@@ -62,22 +88,59 @@     cabal install .     ``` -### nix+### Nix  1. [Install Nix](https://github.com/deemp/flakes/blob/main/README/InstallNix.md)  1. Get `lima` on `PATH`.      ```console-    nix flake lock github:deemp/flakes+    nix flake lock github:deemp/flakes?dir=lima     nix shell github:deemp/flakes?dir=lima     lima --help     ```  ### Windows -To install the executable on `Windows`, if you can't convince cabal to use [`--bindir-method=copy`](https://github.com/haskell/cabal/issues/5748) you can build the project locally and copy the built executeable to `C:/Users/username/AppData/Roaming/cabal/bin` and ensure that this directory is in your `PATH`.+Warning: took it from [LiterateMarkdown](https://github.com/haskie-lambda/LiterateMarkdown). +To install the executable on `Windows`, if you can't convince cabal to use [`--bindir-method=copy`](https://github.com/haskell/cabal/issues/5748) you can build the project locally and copy the built executable to `C:/Users/username/AppData/Roaming/cabal/bin` and ensure that this directory is in your `PATH`.++## build-tool-depends++You can use `lima` to generate your docs, e.g., via `cabal test docs`. Just provide such a test with a script that converts (and, possibly, combines) files. As you'll use `lima` in a script, you should add it to that test's [build-tool-depends](https://cabal.readthedocs.io/en/3.8/cabal-package.html#pkg-field-build-tool-depends):++  ```cabal+  build-tool-depends:+      lima:lima ==0.1.*+  ```++### Nix flake++1. Add `lima` to inputs:+  +  ```nix+  inputs.lima.url = "github:deemp/flakes?dir=lima";+  ```++1. Add `lima` to the override of your package++  ```nix+  override = {+    overrides = self: super: {+      myPackage = overrideCabal+        (super.callCabal2nix myPackageName ./. { })+        (x: {+          testHaskellDepends = [+            (super.callCabal2nix "lima" "${lima.outPath}/lima" { })+          ] ++ (x.testHaskellDepends or [ ]);+        });+    };+  };+  ```++1. Use `cabal v1-test` so that `cabal` uses the supplied `lima`.+ ## Contribute  Clone this repo and enter `lima`@@ -98,7 +161,7 @@  ### nix -1. [Install](https://github.com/deemp/flakes/blob/main/README/InstallNix.md) `Nix`. Optionally, [learn](https://github.com/deemp/flakes#prerequisites) more about `Nix`.+1. [Install](https://github.com/deemp/flakes/blob/main/README/InstallNix.md) `Nix`.  1. Run a devshell and build `lima`: @@ -114,6 +177,6 @@     nix run nix-dev/#codium .     ``` -1. Open a `Haskell` file there, hover over a term and wait until `HLS` shows the hints.+1. Open a `Haskell` file there, hover over a term and wait until `HLS` shows hints.  1. [Troubleshoot](https://github.com/deemp/flakes/blob/main/README/Troubleshooting.md) if necessary.
lima.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           lima-version:        0.1.0.5+version:        0.1.0.6 synopsis:       (Haskell or Literate Haskell) <-> Markdown converter description:    See the [README.md](https://github.com/deemp/flakes/tree/main/lima#readme) category:       Productivity@@ -18,6 +18,16 @@ extra-source-files:     CHANGELOG.md     README.md+    testdata/config/lima.yaml+    testdata/hs/input0.hs+    testdata/hs/input0.hs.md+    testdata/hs/input0.hs.md.hs+    testdata/lhs/input0.lhs+    testdata/lhs/input0.lhs.md+    testdata/lhs/input0.lhs.md.lhs+    testdata/lhs/input1.lhs+    testdata/lhs/input1.lhs.md+    testdata/lhs/input1.lhs.md.lhs  source-repository head   type: git@@ -64,5 +74,6 @@     , data-default     , lima     , optparse-applicative+    , string-interpolate     , yaml   default-language: Haskell2010
test/Main.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -10,21 +11,22 @@ import Data.Char (toLower) import Data.Default (Default (..)) import Data.Maybe (fromMaybe)+import Data.String.Interpolate (i) import Data.Yaml (decodeFileEither, decodeFileThrow) import System.Environment (getArgs) import System.Exit (ExitCode (..), exitWith)  testDir :: String-testDir = "testdata/"+testDir = "testdata"  main :: IO () main = do   -- test round-trip btw hs and md-  let pathsHs1 = ((testDir ++ "/hs/") ++) <$> ["input0.hs"]+  let pathsHs1 = (\x -> [i|#{testDir}/hs/input#{x}.hs|]) <$> [0 :: Int]       pathsMd1 = (++ ".md") <$> pathsHs1       pathsHs2 = (++ ".hs") <$> pathsMd1   Config{..} <--    decodeFileThrow (testDir ++ "/config/lima.yaml")+    decodeFileThrow [i|#{testDir}/config/lima.yaml|]       `catch` (\(x :: SomeException) -> exitWith $ ExitFailure 1)   contentsHs1 <- mapM readFile pathsHs1   let contentsMd1 = hsToMd (fromMaybe def configHsMd) <$> contentsHs1@@ -33,7 +35,7 @@   zipWithM_ writeFile pathsHs2 contentsHs2    -- test round-trip btw lhs and md-  let pathsLhs1 = ((testDir ++ "/lhs/") ++) <$> ["input0.lhs", "input1.lhs"]+  let pathsLhs1 = (\x -> [i|#{testDir}/lhs/input#{x}.lhs|]) <$> [0 :: Int, 1]       pathsMd1 = (++ ".md") <$> pathsLhs1       pathsLhs2 = (++ ".lhs") <$> pathsMd1   contentsLhs1 <- mapM readFile pathsLhs1
+ testdata/config/lima.yaml view
@@ -0,0 +1,4 @@+hs-md:+  special-comments:+    - "first special comment"+    - "second special comment"
+ testdata/hs/input0.hs view
@@ -0,0 +1,151 @@+{-+# Top heading++- Only multi-line comments are supported for Markdown++- Haskell code snippet (use `hs`)++    ```hs+    "1+4+2+3"+    ```++- Console code snippet++```console+"1*2*3*4"+```++1. what's in comments is written as is+2. Haskell code becomes haskell code snippets++- There should be a line between a multi-line comment and Haskell code++- A Haskell snippet starts with a non-empty line that is not a part of a multi-line comment++- Magic comments will be skipped++- Haskell function docs won't++- If there's a single line btw Haskell code and a comment, this line is removed++the following two comments will be commented out due to a config+-}++{- first special comment -}++{- second special comment -}++-- before a magic comment++{- FOURMOLU_DISABLE -}++-- after a magic comment+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -Wno-missing-kind-signatures #-}++{- FOURMOLU_ENABLE -}++-- this comment will go into the Haskell snippet+module Main where -- a comment after++a1 :: Integer+a1 = 4++{-+This comment will divide Haskell snippets+-}++-- Imports++a2 :: Integer+a2 = 2++{-+## Second-level heading++some text++### Third-level heading+-}++-- Text one+-- this multiline comment should separate this line and the line after the comment+{--}+-- Text two should be ignored++{- LIMA_DISABLE -}+-- Text two to ignore+some :: String+some = "code to ignore"++{- LIMA_ENABLE -}++{-+- Indented block+-}++{- LIMA_INDENT 2 -}++-- Text three++-- This comment will go into a Haskell snippet++-- | This doc will go into a Haskell snippet+class (Monoid (m a)) => Reducible m a where+  op :: m a -> Char++  -- | this too+  constr :: m a -> a -> m a++  -- | and this one+  get :: m a -> m a -> a++{-+don't put anything outside and after this comment like in `{- -} -- hey`+-}++-- a comment that will go into a snippet++{- LIMA_DEDENT -}++{- FOURMOLU_DISABLE -}++{-| some +doc that will be in a snippet -}+main :: IO ()+main = print "hi!"++{- FOURMOLU_ENABLE -}++{-+<b name="fn_laws">1</b> <- Some html++{- LIMA_DISABLE -}++in comments should read like {- LIMA_DISABLE -}+-}++{- LIMA_DISABLE -}++-- Some text++{- LIMA_ENABLE -}++-- shouldn't be ignored++{-+this LIMA_ENABLE reads like a comment because there's no corresponding+preceding LIMA_DISABLE++1. listing+-}++{- LIMA_INDENT 4 -}++-- where's lorem ipsum blah?++{- LIMA_DEDENT -}++-- final comment
+ testdata/hs/input0.hs.md view
@@ -0,0 +1,154 @@+# Top heading++- Only multi-line comments are supported for Markdown++- Haskell code snippet (use `hs`)++    ```hs+    "1+4+2+3"+    ```++- Console code snippet++```console+"1*2*3*4"+```++1. what's in comments is written as is+2. Haskell code becomes haskell code snippets++- There should be a line between a multi-line comment and Haskell code++- A Haskell snippet starts with a non-empty line that is not a part of a multi-line comment++- Magic comments will be skipped++- Haskell function docs won't++- If there's a single line btw Haskell code and a comment, this line is removed++the following two comments will be commented out due to a config+<!-- first special comment -->+<!-- second special comment -->++```haskell+-- before a magic comment+```++<!-- FOURMOLU_DISABLE -->++```haskell+-- after a magic comment+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -Wno-missing-kind-signatures #-}+```++<!-- FOURMOLU_ENABLE -->++```haskell+-- this comment will go into the Haskell snippet+module Main where -- a comment after++a1 :: Integer+a1 = 4+```++This comment will divide Haskell snippets++```haskell+-- Imports++a2 :: Integer+a2 = 2+```++## Second-level heading++some text++### Third-level heading++```haskell+-- Text one+-- this multiline comment should separate this line and the line after the comment+{--}+-- Text two should be ignored+```+<!-- LIMA_DISABLE+-- Text two to ignore+some :: String+some = "code to ignore"++LIMA_ENABLE -->++- Indented block++  <!-- LIMA_INDENT 2 -->++  ```haskell+  -- Text three+  +  -- This comment will go into a Haskell snippet+  +  -- | This doc will go into a Haskell snippet+  class (Monoid (m a)) => Reducible m a where+    op :: m a -> Char+  +    -- | this too+    constr :: m a -> a -> m a+  +    -- | and this one+    get :: m a -> m a -> a+  ```++don't put anything outside and after this comment like in `{- -} -- hey`++  ```haskell+  -- a comment that will go into a snippet+  ```++  <!-- LIMA_DEDENT -->+<!-- FOURMOLU_DISABLE -->++```haskell+{-| some +doc that will be in a snippet -}+main :: IO ()+main = print "hi!"+```++<!-- FOURMOLU_ENABLE -->++<b name="fn_laws">1</b> <- Some html++{- LIMA_DISABLE -}++in comments should read like {- LIMA_DISABLE -}+<!-- LIMA_DISABLE++-- Some text++LIMA_ENABLE -->++```haskell+-- shouldn't be ignored+```++this LIMA_ENABLE reads like a comment because there's no corresponding+preceding LIMA_DISABLE++1. listing++    <!-- LIMA_INDENT 4 -->++    ```haskell+    -- where's lorem ipsum blah?+    ```++    <!-- LIMA_DEDENT -->++```haskell+-- final comment+```
+ testdata/hs/input0.hs.md.hs view
@@ -0,0 +1,151 @@+{-+# Top heading++- Only multi-line comments are supported for Markdown++- Haskell code snippet (use `hs`)++    ```hs+    "1+4+2+3"+    ```++- Console code snippet++```console+"1*2*3*4"+```++1. what's in comments is written as is+2. Haskell code becomes haskell code snippets++- There should be a line between a multi-line comment and Haskell code++- A Haskell snippet starts with a non-empty line that is not a part of a multi-line comment++- Magic comments will be skipped++- Haskell function docs won't++- If there's a single line btw Haskell code and a comment, this line is removed++the following two comments will be commented out due to a config+-}++{- first special comment -}++{- second special comment -}++-- before a magic comment++{- FOURMOLU_DISABLE -}++-- after a magic comment+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -Wno-missing-kind-signatures #-}++{- FOURMOLU_ENABLE -}++-- this comment will go into the Haskell snippet+module Main where -- a comment after++a1 :: Integer+a1 = 4++{-+This comment will divide Haskell snippets+-}++-- Imports++a2 :: Integer+a2 = 2++{-+## Second-level heading++some text++### Third-level heading+-}++-- Text one+-- this multiline comment should separate this line and the line after the comment+{--}+-- Text two should be ignored++{- LIMA_DISABLE -}+-- Text two to ignore+some :: String+some = "code to ignore"++{- LIMA_ENABLE -}++{-+- Indented block+-}++{- LIMA_INDENT 2 -}++-- Text three++-- This comment will go into a Haskell snippet++-- | This doc will go into a Haskell snippet+class (Monoid (m a)) => Reducible m a where+  op :: m a -> Char++  -- | this too+  constr :: m a -> a -> m a++  -- | and this one+  get :: m a -> m a -> a++{-+don't put anything outside and after this comment like in `{- -} -- hey`+-}++-- a comment that will go into a snippet++{- LIMA_DEDENT -}++{- FOURMOLU_DISABLE -}++{-| some +doc that will be in a snippet -}+main :: IO ()+main = print "hi!"++{- FOURMOLU_ENABLE -}++{-+<b name="fn_laws">1</b> <- Some html++{- LIMA_DISABLE -}++in comments should read like {- LIMA_DISABLE -}+-}++{- LIMA_DISABLE -}++-- Some text++{- LIMA_ENABLE -}++-- shouldn't be ignored++{-+this LIMA_ENABLE reads like a comment because there's no corresponding+preceding LIMA_DISABLE++1. listing+-}++{- LIMA_INDENT 4 -}++-- where's lorem ipsum blah?++{- LIMA_DEDENT -}++-- final comment
+ testdata/lhs/input0.lhs view
@@ -0,0 +1,170 @@+ # Dillinger++[![N|Solid](https://cldup.com/dTxpPi9lDf.thumb.png)](https://nodesource.com/products/nsolid)++[![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger)++Dillinger is a cloud-enabled, mobile-ready, offline-storage, AngularJS powered HTML5 Markdown editor.++- Type some Markdown on the left+- See HTML in the right+- Magic++ ## New Features++- Import a HTML file and watch it magically convert to Markdown+- Drag and drop images (requires your Dropbox account be linked)++You can also:++- Import and save files from GitHub, Dropbox, Google Drive and One Drive+- Drag and drop markdown and HTML files into Dillinger+- Export documents as Markdown, HTML and PDF++Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email.  As [John Gruber] writes on the [Markdown site][df1]++ > The overriding design goal for Markdown's+ > formatting syntax is to make it as readable+ > as possible. The idea is that a+ > Markdown-formatted document should be+ > publishable as-is, as plain text, without+ > looking like it's been marked up with tags+ > or formatting instructions.++This text you see here is *actually* written in Markdown! To get a feel for Markdown's syntax, type some text into the left window and watch the results in the right.++ ### Tech++Dillinger uses a number of open source projects to work properly:++- [AngularJS] - HTML enhanced for web apps!+- [Ace Editor] - awesome web-based text editor+- [markdown-it] - Markdown parser done right. Fast and easy to extend.+- [Twitter Bootstrap] - great UI boilerplate for modern web apps+- [node.js] - evented I/O for the backend+- [Express] - fast node.js network app framework [@tjholowaychuk]+- [Gulp] - the streaming build system+- [Breakdance](https://breakdance.github.io/breakdance/) - HTML to Markdown converter+- [jQuery] - duh++And of course Dillinger itself is open source with a [public repository][dill]+ on GitHub.++ ### Installation++Dillinger requires [Node.js](https://nodejs.org/) v4+ to run.++Install the dependencies and devDependencies and start the server.++< cd dillinger+< npm install -d+< node app++For production environments...++< npm install --production+< NODE_ENV=production node app++ ### Plugins++Dillinger is currently extended with the following plugins. Instructions on how to use them in your own application are linked below.++| Plugin | README |+| ------ | ------ |+| Dropbox | [plugins/dropbox/README.md][PlDb] |+| GitHub | [plugins/github/README.md][PlGh] |+| Google Drive | [plugins/googledrive/README.md][PlGd] |+| OneDrive | [plugins/onedrive/README.md][PlOd] |+| Medium | [plugins/medium/README.md][PlMe] |+| Google Analytics | [plugins/googleanalytics/README.md][PlGa] |++ ### Development++Want to contribute? Great!++Dillinger uses Gulp + Webpack for fast developing.+Make a change in your file and instantaneously see your updates!++Open your favorite Terminal and run these commands.++First Tab:++< node app++Second Tab:++< gulp watch++(optional) Third:++< karma test++ #### Building for source++For production release:++< gulp build --prod++Generating pre-built zip archives for distribution:++< gulp build dist --prod++ #### Docker++Dillinger is very easy to install and deploy in a Docker container.++By default, the Docker will expose port 8080, so change this within the Dockerfile if necessary. When ready, simply use the Dockerfile to build the image.++< cd dillinger+< docker build -t joemccann/dillinger:${package.json.version} .++This will create the dillinger image and pull in the necessary dependencies. Be sure to swap out `${package.json.version}` with the actual version of Dillinger.++Once done, run the Docker image and map the port to whatever you wish on your host. In this example, we simply map port 8000 of the host to port 8080 of the Docker (or whatever port was exposed in the Dockerfile):++< docker run -d -p 8000:8080 --restart="always" <youruser>/dillinger:${package.json.version}++Verify the deployment by navigating to your server address in your preferred browser.++< 127.0.0.1:8000++ #### Kubernetes + Google Cloud++See [KUBERNETES.md](https://github.com/joemccann/dillinger/blob/master/KUBERNETES.md)++> module Main where+>  +> main = putStrLn "HelloWorld"++ ### Todos++- Write MORE Tests+- Add Night Mode++ ### License++MIT++**Free Software, Hell Yeah!**++[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)++   [dill]: <https://github.com/joemccann/dillinger>+   [john gruber]: <http://daringfireball.net>+   [df1]: <http://daringfireball.net/projects/markdown/>+   [markdown-it]: <https://github.com/markdown-it/markdown-it>+   [Ace Editor]: <http://ace.ajax.org>+   [node.js]: <http://nodejs.org>+   [Twitter Bootstrap]: <http://twitter.github.com/bootstrap/>+   [jQuery]: <http://jquery.com>+   [@tjholowaychuk]: <http://twitter.com/tjholowaychuk>+   [express]: <http://expressjs.com>+   [AngularJS]: <http://angularjs.org>+   [Gulp]: <http://gulpjs.com>++   [PlDb]: <https://github.com/joemccann/dillinger/tree/master/plugins/dropbox/README.md>+   [PlGh]: <https://github.com/joemccann/dillinger/tree/master/plugins/github/README.md>+   [PlGd]: <https://github.com/joemccann/dillinger/tree/master/plugins/googledrive/README.md>+   [PlOd]: <https://github.com/joemccann/dillinger/tree/master/plugins/onedrive/README.md>+   [PlMe]: <https://github.com/joemccann/dillinger/tree/master/plugins/medium/README.md>+   [PlGa]: <https://github.com/RahulHP/dillinger/blob/master/plugins/googleanalytics/README.md>
+ testdata/lhs/input0.lhs.md view
@@ -0,0 +1,192 @@+# Dillinger++[![N|Solid](https://cldup.com/dTxpPi9lDf.thumb.png)](https://nodesource.com/products/nsolid)++[![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger)++Dillinger is a cloud-enabled, mobile-ready, offline-storage, AngularJS powered HTML5 Markdown editor.++- Type some Markdown on the left+- See HTML in the right+- Magic++## New Features++- Import a HTML file and watch it magically convert to Markdown+- Drag and drop images (requires your Dropbox account be linked)++You can also:++- Import and save files from GitHub, Dropbox, Google Drive and One Drive+- Drag and drop markdown and HTML files into Dillinger+- Export documents as Markdown, HTML and PDF++Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email.  As [John Gruber] writes on the [Markdown site][df1]++> The overriding design goal for Markdown's+> formatting syntax is to make it as readable+> as possible. The idea is that a+> Markdown-formatted document should be+> publishable as-is, as plain text, without+> looking like it's been marked up with tags+> or formatting instructions.++This text you see here is *actually* written in Markdown! To get a feel for Markdown's syntax, type some text into the left window and watch the results in the right.++### Tech++Dillinger uses a number of open source projects to work properly:++- [AngularJS] - HTML enhanced for web apps!+- [Ace Editor] - awesome web-based text editor+- [markdown-it] - Markdown parser done right. Fast and easy to extend.+- [Twitter Bootstrap] - great UI boilerplate for modern web apps+- [node.js] - evented I/O for the backend+- [Express] - fast node.js network app framework [@tjholowaychuk]+- [Gulp] - the streaming build system+- [Breakdance](https://breakdance.github.io/breakdance/) - HTML to Markdown converter+- [jQuery] - duh++And of course Dillinger itself is open source with a [public repository][dill]+ on GitHub.++### Installation++Dillinger requires [Node.js](https://nodejs.org/) v4+ to run.++Install the dependencies and devDependencies and start the server.++```console+cd dillinger+npm install -d+node app+```++For production environments...++```console+npm install --production+NODE_ENV=production node app+```++### Plugins++Dillinger is currently extended with the following plugins. Instructions on how to use them in your own application are linked below.++| Plugin | README |+| ------ | ------ |+| Dropbox | [plugins/dropbox/README.md][PlDb] |+| GitHub | [plugins/github/README.md][PlGh] |+| Google Drive | [plugins/googledrive/README.md][PlGd] |+| OneDrive | [plugins/onedrive/README.md][PlOd] |+| Medium | [plugins/medium/README.md][PlMe] |+| Google Analytics | [plugins/googleanalytics/README.md][PlGa] |++### Development++Want to contribute? Great!++Dillinger uses Gulp + Webpack for fast developing.+Make a change in your file and instantaneously see your updates!++Open your favorite Terminal and run these commands.++First Tab:++```console+node app+```++Second Tab:++```console+gulp watch+```++(optional) Third:++```console+karma test+```++#### Building for source++For production release:++```console+gulp build --prod+```++Generating pre-built zip archives for distribution:++```console+gulp build dist --prod+```++#### Docker++Dillinger is very easy to install and deploy in a Docker container.++By default, the Docker will expose port 8080, so change this within the Dockerfile if necessary. When ready, simply use the Dockerfile to build the image.++```console+cd dillinger+docker build -t joemccann/dillinger:${package.json.version} .+```++This will create the dillinger image and pull in the necessary dependencies. Be sure to swap out `${package.json.version}` with the actual version of Dillinger.++Once done, run the Docker image and map the port to whatever you wish on your host. In this example, we simply map port 8000 of the host to port 8080 of the Docker (or whatever port was exposed in the Dockerfile):++```console+docker run -d -p 8000:8080 --restart="always" <youruser>/dillinger:${package.json.version}+```++Verify the deployment by navigating to your server address in your preferred browser.++```console+127.0.0.1:8000+```++#### Kubernetes + Google Cloud++See [KUBERNETES.md](https://github.com/joemccann/dillinger/blob/master/KUBERNETES.md)++```haskell+module Main where+ +main = putStrLn "HelloWorld"+```++### Todos++- Write MORE Tests+- Add Night Mode++### License++MIT++**Free Software, Hell Yeah!**++[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)++   [dill]: <https://github.com/joemccann/dillinger>+   [john gruber]: <http://daringfireball.net>+   [df1]: <http://daringfireball.net/projects/markdown/>+   [markdown-it]: <https://github.com/markdown-it/markdown-it>+   [Ace Editor]: <http://ace.ajax.org>+   [node.js]: <http://nodejs.org>+   [Twitter Bootstrap]: <http://twitter.github.com/bootstrap/>+   [jQuery]: <http://jquery.com>+   [@tjholowaychuk]: <http://twitter.com/tjholowaychuk>+   [express]: <http://expressjs.com>+   [AngularJS]: <http://angularjs.org>+   [Gulp]: <http://gulpjs.com>++   [PlDb]: <https://github.com/joemccann/dillinger/tree/master/plugins/dropbox/README.md>+   [PlGh]: <https://github.com/joemccann/dillinger/tree/master/plugins/github/README.md>+   [PlGd]: <https://github.com/joemccann/dillinger/tree/master/plugins/googledrive/README.md>+   [PlOd]: <https://github.com/joemccann/dillinger/tree/master/plugins/onedrive/README.md>+   [PlMe]: <https://github.com/joemccann/dillinger/tree/master/plugins/medium/README.md>+   [PlGa]: <https://github.com/RahulHP/dillinger/blob/master/plugins/googleanalytics/README.md>
+ testdata/lhs/input0.lhs.md.lhs view
@@ -0,0 +1,170 @@+ # Dillinger++[![N|Solid](https://cldup.com/dTxpPi9lDf.thumb.png)](https://nodesource.com/products/nsolid)++[![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger)++Dillinger is a cloud-enabled, mobile-ready, offline-storage, AngularJS powered HTML5 Markdown editor.++- Type some Markdown on the left+- See HTML in the right+- Magic++ ## New Features++- Import a HTML file and watch it magically convert to Markdown+- Drag and drop images (requires your Dropbox account be linked)++You can also:++- Import and save files from GitHub, Dropbox, Google Drive and One Drive+- Drag and drop markdown and HTML files into Dillinger+- Export documents as Markdown, HTML and PDF++Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email.  As [John Gruber] writes on the [Markdown site][df1]++ > The overriding design goal for Markdown's+ > formatting syntax is to make it as readable+ > as possible. The idea is that a+ > Markdown-formatted document should be+ > publishable as-is, as plain text, without+ > looking like it's been marked up with tags+ > or formatting instructions.++This text you see here is *actually* written in Markdown! To get a feel for Markdown's syntax, type some text into the left window and watch the results in the right.++ ### Tech++Dillinger uses a number of open source projects to work properly:++- [AngularJS] - HTML enhanced for web apps!+- [Ace Editor] - awesome web-based text editor+- [markdown-it] - Markdown parser done right. Fast and easy to extend.+- [Twitter Bootstrap] - great UI boilerplate for modern web apps+- [node.js] - evented I/O for the backend+- [Express] - fast node.js network app framework [@tjholowaychuk]+- [Gulp] - the streaming build system+- [Breakdance](https://breakdance.github.io/breakdance/) - HTML to Markdown converter+- [jQuery] - duh++And of course Dillinger itself is open source with a [public repository][dill]+ on GitHub.++ ### Installation++Dillinger requires [Node.js](https://nodejs.org/) v4+ to run.++Install the dependencies and devDependencies and start the server.++< cd dillinger+< npm install -d+< node app++For production environments...++< npm install --production+< NODE_ENV=production node app++ ### Plugins++Dillinger is currently extended with the following plugins. Instructions on how to use them in your own application are linked below.++| Plugin | README |+| ------ | ------ |+| Dropbox | [plugins/dropbox/README.md][PlDb] |+| GitHub | [plugins/github/README.md][PlGh] |+| Google Drive | [plugins/googledrive/README.md][PlGd] |+| OneDrive | [plugins/onedrive/README.md][PlOd] |+| Medium | [plugins/medium/README.md][PlMe] |+| Google Analytics | [plugins/googleanalytics/README.md][PlGa] |++ ### Development++Want to contribute? Great!++Dillinger uses Gulp + Webpack for fast developing.+Make a change in your file and instantaneously see your updates!++Open your favorite Terminal and run these commands.++First Tab:++< node app++Second Tab:++< gulp watch++(optional) Third:++< karma test++ #### Building for source++For production release:++< gulp build --prod++Generating pre-built zip archives for distribution:++< gulp build dist --prod++ #### Docker++Dillinger is very easy to install and deploy in a Docker container.++By default, the Docker will expose port 8080, so change this within the Dockerfile if necessary. When ready, simply use the Dockerfile to build the image.++< cd dillinger+< docker build -t joemccann/dillinger:${package.json.version} .++This will create the dillinger image and pull in the necessary dependencies. Be sure to swap out `${package.json.version}` with the actual version of Dillinger.++Once done, run the Docker image and map the port to whatever you wish on your host. In this example, we simply map port 8000 of the host to port 8080 of the Docker (or whatever port was exposed in the Dockerfile):++< docker run -d -p 8000:8080 --restart="always" <youruser>/dillinger:${package.json.version}++Verify the deployment by navigating to your server address in your preferred browser.++< 127.0.0.1:8000++ #### Kubernetes + Google Cloud++See [KUBERNETES.md](https://github.com/joemccann/dillinger/blob/master/KUBERNETES.md)++> module Main where+>  +> main = putStrLn "HelloWorld"++ ### Todos++- Write MORE Tests+- Add Night Mode++ ### License++MIT++**Free Software, Hell Yeah!**++[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)++   [dill]: <https://github.com/joemccann/dillinger>+   [john gruber]: <http://daringfireball.net>+   [df1]: <http://daringfireball.net/projects/markdown/>+   [markdown-it]: <https://github.com/markdown-it/markdown-it>+   [Ace Editor]: <http://ace.ajax.org>+   [node.js]: <http://nodejs.org>+   [Twitter Bootstrap]: <http://twitter.github.com/bootstrap/>+   [jQuery]: <http://jquery.com>+   [@tjholowaychuk]: <http://twitter.com/tjholowaychuk>+   [express]: <http://expressjs.com>+   [AngularJS]: <http://angularjs.org>+   [Gulp]: <http://gulpjs.com>++   [PlDb]: <https://github.com/joemccann/dillinger/tree/master/plugins/dropbox/README.md>+   [PlGh]: <https://github.com/joemccann/dillinger/tree/master/plugins/github/README.md>+   [PlGd]: <https://github.com/joemccann/dillinger/tree/master/plugins/googledrive/README.md>+   [PlOd]: <https://github.com/joemccann/dillinger/tree/master/plugins/onedrive/README.md>+   [PlMe]: <https://github.com/joemccann/dillinger/tree/master/plugins/medium/README.md>+   [PlGa]: <https://github.com/RahulHP/dillinger/blob/master/plugins/googleanalytics/README.md>
+ testdata/lhs/input1.lhs view
@@ -0,0 +1,233 @@+ # Monoid reduction parsing/execution++I gave our apprentice the excersise to write a program+that takes strings in the form++< "1+4+2+3"++with variable length and write it in way that it is easily+extendible to strings of the form++< "1*2*3*4"++Not a hugely interesting task normally.+However, after giving the task, it occurred to me, that+the examples involved evaluating Monoids, specifically+the product and sum monoid on natural numbers.++This got me thinking:+What would be an elegant, extendible way to write such a+program?++This is what I came up with:++I am using these language extensions, I will explain their usage+when it comes up.++> {-# LANGUAGE MultiParamTypeClasses #-}+> {-# LANGUAGE FlexibleContexts #-}+> {-# LANGUAGE FlexibleInstances #-}+> {-# LANGUAGE InstanceSigs #-}++Next, let's import the needed modules:++> module Main where+> import Data.Monoid ( Product(..), Sum(..))          -- obviously when we are talking monoids +> import Data.List ( intercalate)            -- an interesting monoid, and some helper functions++ ## What we want to have++What is is exactly what we want to have? We want something+that expresses an idea for reducing a monoid string (a string+that expresses a monoid computation) that generalizes over+any monoid we throw at it.++In Haskell we use a typeclass for that:++> class (Monoid (m a)) => Reducible m a where+>         op     :: m a -> Char+>         constr :: m a -> a -> m a+>         get    :: m a -> m a -> a++This states that a `Reducible` has to be a `Monoid`+and we would like to konw what `Char` `op`erand it is associated with.+Additioally, we would like to know how to construct the `Monoid` form+a value and a way to extract a value from the `Monoid`.+Here `MultiParamTypeClasses` enables us to writd `Reducible m a`+so that monoids with different base types can behave differently.++Now that we have written down what we want, we can try to implement+a `Reducible`. To begin, we will start with the `Sum` and `Product`+monoids mentioned in the beginning:++> instance (Num a) => Reducible Sum a where+>         op :: Num a => Sum a -> Char+>         op     _ = '+'+>         constr :: Num a => Sum a -> a -> Sum a+>         constr _ = Sum+>         get :: Num a => Sum a -> Sum a -> a+>         get    _ = getSum+> +> instance (Num a) => Reducible Product a where+>         op :: Num a => Product a -> Char+>         op     _ = '*'+>         constr :: Num a => Product a -> a -> Product a+>         constr _ = Product+>         get :: Num a => Product a -> Product a -> a+>         get    _ = getProduct++As you might have noticed, we completely ignore the given argument in all+three functions. The argument is merely used as a phantom type, for+the compiler to disambiguate what instance (and with it, which dictionary)+will be used at runtime.+The `FlexibleInstances` extension allows us to specify an instance with a+`Num` constraint, to let us use the two instances with all `Num` instances.++ ## How do we work with this?++Let's define how we actually reduce a "monoid string" to the value it+represents.++The function `reduce'` takes a string and the information what monoid+the string represents and calculates the value from it:++> reduce' :: (Read a, Reducible m a) => String -> m a -> a ++We need the Read a to make sure, that we can actually parse the values+the monoid holds. It would be nicer and safer to use a proper parser here+but for the sake of simplicity we keep it like that.+(We can always wrap a `pureTry` around the call to `reduce'`, which, again,+is not a nice complete way to do it, but easier. ^^)+Now let's drop reduce's curtain of uncertainty:++> reduce' list red = get red  +>                 $ foldr (<>) mempty+>                 $ map (\x -> constr red $ read x) +>                 $ splitOn (op red) list ++What is going on here?+It's actually quite simple:+First, we split the string (`list`) by the reducibles operator.+Then we convert each value in the resulting list to the monoid itself.+This leaves us with a list of monoid values, that we can reduce with a+fold and the monoid operation `<>` and finally we `get` the value+out of the monoid, using the reducibles info on how to do that.++The `splitOn` function itself is rather boilerplate:++> splitOn :: (Eq a) => a -> [a] -> [[a]]+> splitOn _ [] = [[]]+> splitOn c (h:t) +>   | h == c    = []:rest+>   | otherwise = (h:head rest):tail rest+>    where rest = splitOn c t ++no funny business here.++ ## However++... we would like an interface that is a bit simpler.+Wouldn't it be nice, to write++< reduce "1+2+3+4"++and get back the `Int` it computes to?++Let's write such a function:++> reduce :: String -> Int++The type now restricts us to returning `Int`s, but we could easily write+another function that returns strings, etc.+(We could even use TemplateHaskell or something along the `Typeable` and+`Data` typeclasses to automatically write such functions or return polymorphic+results and work with them, but that is something for another post.)++> reduce s+>     | '+' `elem` s = reduce' s (mempty :: Sum Int)+>     | '*' `elem` s = reduce' s (mempty :: Product Int)+>     | ':' `elem` s = reduce' s (mempty :: [Int])+>     | otherwise    = error "no calculatable operation detected"++The implementation is actually rather straight forward:+we just peek into the string what operand we find and then make a call to+`reduce'` with the proper monoid type.+Probably you have found the `'.'` by now and wonder if you missed something+up until now.+Don't fear, you haven't.+I have saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`:++> instance Reducible [] Int where+>   op :: [Int] -> Char+>   op     _ = ':'+>   constr :: [Int] -> Int -> [Int]+>   constr _ = (:[])+>   get :: [Int] -> [Int] -> Int+>   get    _ = sum -- length -- basically all [a] -> Int Functions++which even generalizes the behavior from above.+Note that the reduction part has now moved to the `get` function instead of+the binary monoid operation `<>`.+What is this good for you ask?+Monoids are bound by certain laws<sup id="laws">[1](#fn_laws)</sup>.++< -- Identity laws+< x <> mempty = x+< mempty <> x = x+< +< -- Associativity+< (x <> y) <> z = x <> (y <> z)++These state that the binary operation needs to be associative (so we couldn't have+a monoid operation for division for example, as this would violate associativity.+However, moving the reduction operation to the reducible instead, we can specify+a function that is not associative by just implementing a function from++< [Int] -> Int++For example:++< foldl (/) 1 list++or even have a non binary function such as++< f l = func l True+<   where+<     func []       _     = []+<     func (h:[])   _     = h+<     func (x:y:ls) True  = x*y + (func ls False)+<     func (x:y:ls) False = x/y - (func ls True)++which is sensitive to where in the list the operands are.++As an additional example this is how one could implement string concattenation+using a reducible:++> instance Reducible [] String where+>   op :: [String] -> Char+>   op     _ = '.'+>   constr :: [String] -> String -> [String]+>   constr _ = (:[])+>   get :: [String] -> [String] -> String+>   get    _ = intercalate [] ++ ## Final Words++The final example along with the ability to shift the reduction step to the+reducible opens up insersting applications for implementing simple DSL computations+or just fast, easily understandable parsers for complex data structures.+(Like implementing a `Reducible [] Foo` where Foo is some complex data type.)++> main :: IO ()+> main = do+>         print $ reduce "1+2+3+4"+>         print $ reduce "2*3*4"+>         print $ reduce "2:3:4" +>         print $ reduce' "\"hello \".\"world\"" (mempty :: [String])++---------------------------------------------------------------------------------------+<b name="fn_laws">1</b>: These are lazily taken from the [Haskell Website](https://wiki.haskell.org/Monoid)[<-](#laws)
+ testdata/lhs/input1.lhs.md view
@@ -0,0 +1,271 @@+# Monoid reduction parsing/execution++I gave our apprentice the excersise to write a program+that takes strings in the form++```console+"1+4+2+3"+```++with variable length and write it in way that it is easily+extendible to strings of the form++```console+"1*2*3*4"+```++Not a hugely interesting task normally.+However, after giving the task, it occurred to me, that+the examples involved evaluating Monoids, specifically+the product and sum monoid on natural numbers.++This got me thinking:+What would be an elegant, extendible way to write such a+program?++This is what I came up with:++I am using these language extensions, I will explain their usage+when it comes up.++```haskell+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+```++Next, let's import the needed modules:++```haskell+module Main where+import Data.Monoid ( Product(..), Sum(..))          -- obviously when we are talking monoids +import Data.List ( intercalate)            -- an interesting monoid, and some helper functions+```++## What we want to have++What is is exactly what we want to have? We want something+that expresses an idea for reducing a monoid string (a string+that expresses a monoid computation) that generalizes over+any monoid we throw at it.++In Haskell we use a typeclass for that:++```haskell+class (Monoid (m a)) => Reducible m a where+        op     :: m a -> Char+        constr :: m a -> a -> m a+        get    :: m a -> m a -> a+```++This states that a `Reducible` has to be a `Monoid`+and we would like to konw what `Char` `op`erand it is associated with.+Additioally, we would like to know how to construct the `Monoid` form+a value and a way to extract a value from the `Monoid`.+Here `MultiParamTypeClasses` enables us to writd `Reducible m a`+so that monoids with different base types can behave differently.++Now that we have written down what we want, we can try to implement+a `Reducible`. To begin, we will start with the `Sum` and `Product`+monoids mentioned in the beginning:++```haskell+instance (Num a) => Reducible Sum a where+        op :: Num a => Sum a -> Char+        op     _ = '+'+        constr :: Num a => Sum a -> a -> Sum a+        constr _ = Sum+        get :: Num a => Sum a -> Sum a -> a+        get    _ = getSum++instance (Num a) => Reducible Product a where+        op :: Num a => Product a -> Char+        op     _ = '*'+        constr :: Num a => Product a -> a -> Product a+        constr _ = Product+        get :: Num a => Product a -> Product a -> a+        get    _ = getProduct+```++As you might have noticed, we completely ignore the given argument in all+three functions. The argument is merely used as a phantom type, for+the compiler to disambiguate what instance (and with it, which dictionary)+will be used at runtime.+The `FlexibleInstances` extension allows us to specify an instance with a+`Num` constraint, to let us use the two instances with all `Num` instances.++## How do we work with this?++Let's define how we actually reduce a "monoid string" to the value it+represents.++The function `reduce'` takes a string and the information what monoid+the string represents and calculates the value from it:++```haskell+reduce' :: (Read a, Reducible m a) => String -> m a -> a +```++We need the Read a to make sure, that we can actually parse the values+the monoid holds. It would be nicer and safer to use a proper parser here+but for the sake of simplicity we keep it like that.+(We can always wrap a `pureTry` around the call to `reduce'`, which, again,+is not a nice complete way to do it, but easier. ^^)+Now let's drop reduce's curtain of uncertainty:++```haskell+reduce' list red = get red  +                $ foldr (<>) mempty+                $ map (\x -> constr red $ read x) +                $ splitOn (op red) list +```++What is going on here?+It's actually quite simple:+First, we split the string (`list`) by the reducibles operator.+Then we convert each value in the resulting list to the monoid itself.+This leaves us with a list of monoid values, that we can reduce with a+fold and the monoid operation `<>` and finally we `get` the value+out of the monoid, using the reducibles info on how to do that.++The `splitOn` function itself is rather boilerplate:++```haskell+splitOn :: (Eq a) => a -> [a] -> [[a]]+splitOn _ [] = [[]]+splitOn c (h:t) +  | h == c    = []:rest+  | otherwise = (h:head rest):tail rest+   where rest = splitOn c t +```++no funny business here.++## However++... we would like an interface that is a bit simpler.+Wouldn't it be nice, to write++```console+reduce "1+2+3+4"+```++and get back the `Int` it computes to?++Let's write such a function:++```haskell+reduce :: String -> Int+```++The type now restricts us to returning `Int`s, but we could easily write+another function that returns strings, etc.+(We could even use TemplateHaskell or something along the `Typeable` and+`Data` typeclasses to automatically write such functions or return polymorphic+results and work with them, but that is something for another post.)++```haskell+reduce s+    | '+' `elem` s = reduce' s (mempty :: Sum Int)+    | '*' `elem` s = reduce' s (mempty :: Product Int)+    | ':' `elem` s = reduce' s (mempty :: [Int])+    | otherwise    = error "no calculatable operation detected"+```++The implementation is actually rather straight forward:+we just peek into the string what operand we find and then make a call to+`reduce'` with the proper monoid type.+Probably you have found the `'.'` by now and wonder if you missed something+up until now.+Don't fear, you haven't.+I have saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`:++```haskell+instance Reducible [] Int where+  op :: [Int] -> Char+  op     _ = ':'+  constr :: [Int] -> Int -> [Int]+  constr _ = (:[])+  get :: [Int] -> [Int] -> Int+  get    _ = sum -- length -- basically all [a] -> Int Functions+```++which even generalizes the behavior from above.+Note that the reduction part has now moved to the `get` function instead of+the binary monoid operation `<>`.+What is this good for you ask?+Monoids are bound by certain laws<sup id="laws">[1](#fn_laws)</sup>.++```console+-- Identity laws+x <> mempty = x+mempty <> x = x++-- Associativity+(x <> y) <> z = x <> (y <> z)+```++These state that the binary operation needs to be associative (so we couldn't have+a monoid operation for division for example, as this would violate associativity.+However, moving the reduction operation to the reducible instead, we can specify+a function that is not associative by just implementing a function from++```console+[Int] -> Int+```++For example:++```console+foldl (/) 1 list+```++or even have a non binary function such as++```console+f l = func l True+  where+    func []       _     = []+    func (h:[])   _     = h+    func (x:y:ls) True  = x*y + (func ls False)+    func (x:y:ls) False = x/y - (func ls True)+```++which is sensitive to where in the list the operands are.++As an additional example this is how one could implement string concattenation+using a reducible:++```haskell+instance Reducible [] String where+  op :: [String] -> Char+  op     _ = '.'+  constr :: [String] -> String -> [String]+  constr _ = (:[])+  get :: [String] -> [String] -> String+  get    _ = intercalate [] +```++## Final Words++The final example along with the ability to shift the reduction step to the+reducible opens up insersting applications for implementing simple DSL computations+or just fast, easily understandable parsers for complex data structures.+(Like implementing a `Reducible [] Foo` where Foo is some complex data type.)++```haskell+main :: IO ()+main = do+        print $ reduce "1+2+3+4"+        print $ reduce "2*3*4"+        print $ reduce "2:3:4" +        print $ reduce' "\"hello \".\"world\"" (mempty :: [String])+```++---------------------------------------------------------------------------------------+<b name="fn_laws">1</b>: These are lazily taken from the [Haskell Website](https://wiki.haskell.org/Monoid)[<-](#laws)
+ testdata/lhs/input1.lhs.md.lhs view
@@ -0,0 +1,233 @@+ # Monoid reduction parsing/execution++I gave our apprentice the excersise to write a program+that takes strings in the form++< "1+4+2+3"++with variable length and write it in way that it is easily+extendible to strings of the form++< "1*2*3*4"++Not a hugely interesting task normally.+However, after giving the task, it occurred to me, that+the examples involved evaluating Monoids, specifically+the product and sum monoid on natural numbers.++This got me thinking:+What would be an elegant, extendible way to write such a+program?++This is what I came up with:++I am using these language extensions, I will explain their usage+when it comes up.++> {-# LANGUAGE MultiParamTypeClasses #-}+> {-# LANGUAGE FlexibleContexts #-}+> {-# LANGUAGE FlexibleInstances #-}+> {-# LANGUAGE InstanceSigs #-}++Next, let's import the needed modules:++> module Main where+> import Data.Monoid ( Product(..), Sum(..))          -- obviously when we are talking monoids +> import Data.List ( intercalate)            -- an interesting monoid, and some helper functions++ ## What we want to have++What is is exactly what we want to have? We want something+that expresses an idea for reducing a monoid string (a string+that expresses a monoid computation) that generalizes over+any monoid we throw at it.++In Haskell we use a typeclass for that:++> class (Monoid (m a)) => Reducible m a where+>         op     :: m a -> Char+>         constr :: m a -> a -> m a+>         get    :: m a -> m a -> a++This states that a `Reducible` has to be a `Monoid`+and we would like to konw what `Char` `op`erand it is associated with.+Additioally, we would like to know how to construct the `Monoid` form+a value and a way to extract a value from the `Monoid`.+Here `MultiParamTypeClasses` enables us to writd `Reducible m a`+so that monoids with different base types can behave differently.++Now that we have written down what we want, we can try to implement+a `Reducible`. To begin, we will start with the `Sum` and `Product`+monoids mentioned in the beginning:++> instance (Num a) => Reducible Sum a where+>         op :: Num a => Sum a -> Char+>         op     _ = '+'+>         constr :: Num a => Sum a -> a -> Sum a+>         constr _ = Sum+>         get :: Num a => Sum a -> Sum a -> a+>         get    _ = getSum+> +> instance (Num a) => Reducible Product a where+>         op :: Num a => Product a -> Char+>         op     _ = '*'+>         constr :: Num a => Product a -> a -> Product a+>         constr _ = Product+>         get :: Num a => Product a -> Product a -> a+>         get    _ = getProduct++As you might have noticed, we completely ignore the given argument in all+three functions. The argument is merely used as a phantom type, for+the compiler to disambiguate what instance (and with it, which dictionary)+will be used at runtime.+The `FlexibleInstances` extension allows us to specify an instance with a+`Num` constraint, to let us use the two instances with all `Num` instances.++ ## How do we work with this?++Let's define how we actually reduce a "monoid string" to the value it+represents.++The function `reduce'` takes a string and the information what monoid+the string represents and calculates the value from it:++> reduce' :: (Read a, Reducible m a) => String -> m a -> a ++We need the Read a to make sure, that we can actually parse the values+the monoid holds. It would be nicer and safer to use a proper parser here+but for the sake of simplicity we keep it like that.+(We can always wrap a `pureTry` around the call to `reduce'`, which, again,+is not a nice complete way to do it, but easier. ^^)+Now let's drop reduce's curtain of uncertainty:++> reduce' list red = get red  +>                 $ foldr (<>) mempty+>                 $ map (\x -> constr red $ read x) +>                 $ splitOn (op red) list ++What is going on here?+It's actually quite simple:+First, we split the string (`list`) by the reducibles operator.+Then we convert each value in the resulting list to the monoid itself.+This leaves us with a list of monoid values, that we can reduce with a+fold and the monoid operation `<>` and finally we `get` the value+out of the monoid, using the reducibles info on how to do that.++The `splitOn` function itself is rather boilerplate:++> splitOn :: (Eq a) => a -> [a] -> [[a]]+> splitOn _ [] = [[]]+> splitOn c (h:t) +>   | h == c    = []:rest+>   | otherwise = (h:head rest):tail rest+>    where rest = splitOn c t ++no funny business here.++ ## However++... we would like an interface that is a bit simpler.+Wouldn't it be nice, to write++< reduce "1+2+3+4"++and get back the `Int` it computes to?++Let's write such a function:++> reduce :: String -> Int++The type now restricts us to returning `Int`s, but we could easily write+another function that returns strings, etc.+(We could even use TemplateHaskell or something along the `Typeable` and+`Data` typeclasses to automatically write such functions or return polymorphic+results and work with them, but that is something for another post.)++> reduce s+>     | '+' `elem` s = reduce' s (mempty :: Sum Int)+>     | '*' `elem` s = reduce' s (mempty :: Product Int)+>     | ':' `elem` s = reduce' s (mempty :: [Int])+>     | otherwise    = error "no calculatable operation detected"++The implementation is actually rather straight forward:+we just peek into the string what operand we find and then make a call to+`reduce'` with the proper monoid type.+Probably you have found the `'.'` by now and wonder if you missed something+up until now.+Don't fear, you haven't.+I have saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`saved somthing interesting for the end.+You probably konw that Lists also have a monoid instance in Haskell.+Therefore, we can reasonably write an instance for `Reducible`:++> instance Reducible [] Int where+>   op :: [Int] -> Char+>   op     _ = ':'+>   constr :: [Int] -> Int -> [Int]+>   constr _ = (:[])+>   get :: [Int] -> [Int] -> Int+>   get    _ = sum -- length -- basically all [a] -> Int Functions++which even generalizes the behavior from above.+Note that the reduction part has now moved to the `get` function instead of+the binary monoid operation `<>`.+What is this good for you ask?+Monoids are bound by certain laws<sup id="laws">[1](#fn_laws)</sup>.++< -- Identity laws+< x <> mempty = x+< mempty <> x = x+< +< -- Associativity+< (x <> y) <> z = x <> (y <> z)++These state that the binary operation needs to be associative (so we couldn't have+a monoid operation for division for example, as this would violate associativity.+However, moving the reduction operation to the reducible instead, we can specify+a function that is not associative by just implementing a function from++< [Int] -> Int++For example:++< foldl (/) 1 list++or even have a non binary function such as++< f l = func l True+<   where+<     func []       _     = []+<     func (h:[])   _     = h+<     func (x:y:ls) True  = x*y + (func ls False)+<     func (x:y:ls) False = x/y - (func ls True)++which is sensitive to where in the list the operands are.++As an additional example this is how one could implement string concattenation+using a reducible:++> instance Reducible [] String where+>   op :: [String] -> Char+>   op     _ = '.'+>   constr :: [String] -> String -> [String]+>   constr _ = (:[])+>   get :: [String] -> [String] -> String+>   get    _ = intercalate [] ++ ## Final Words++The final example along with the ability to shift the reduction step to the+reducible opens up insersting applications for implementing simple DSL computations+or just fast, easily understandable parsers for complex data structures.+(Like implementing a `Reducible [] Foo` where Foo is some complex data type.)++> main :: IO ()+> main = do+>         print $ reduce "1+2+3+4"+>         print $ reduce "2*3*4"+>         print $ reduce "2:3:4" +>         print $ reduce' "\"hello \".\"world\"" (mempty :: [String])++---------------------------------------------------------------------------------------+<b name="fn_laws">1</b>: These are lazily taken from the [Haskell Website](https://wiki.haskell.org/Monoid)[<-](#laws)