1
1
mirror of https://github.com/thma/LtuPatternFactory.git synced 2024-12-12 10:42:08 +03:00

wip on readme

This commit is contained in:
thma 2018-11-02 16:07:39 +01:00
parent 52e2300246
commit 962458f26f
2 changed files with 143 additions and 3 deletions

140
README.md
View File

@ -662,10 +662,43 @@ http://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/
### Iterating over a Tree ### Iterating over a Tree
The most generic type class enabling iteration over algebraic data types is `Traversable` as it allows combinations of `map` and `fold` operations.
We are re-using the `Exp` type from earlier examples to show what's needed for enabling iteration in functional languages.
```haskell
instance Functor Exp where
fmap f (Var x) = Var x
fmap f (Val a) = Val $ f a
fmap f (Add x y) = Add (fmap f x) (fmap f y)
fmap f (Mul x y) = Mul (fmap f x) (fmap f y)
instance Traversable Exp where
traverse g (Var x) = pure $ Var x
traverse g (Val x) = Val <$> g x
traverse g (Add x y) = Add <$> traverse g x <*> traverse g y
traverse g (Mul x y) = Mul <$> traverse g x <*> traverse g y
```
With this declaration we can traverse an `Exp` tree:
```haskell
iteratorDemo = do
putStrLn "Iterator -> Traversable"
let exp = Mul (Add (Val 3) (Val 1))
(Mul (Val 2) (Var "pi"))
env = [("pi", pi)]
print $ traverse (\x c -> if even x then [x] else [2*x]) exp 0
```
In this example we are touching all (nested) `Val` elements and multiply all odd values by 2.
### Combining traversal operations ### Combining traversal operations
Compared with `Foldable` or `Functor` the declaration of a `Traversable` instance looks a bit intimidating. In particular the type declaration for `traverse`:
```haskell
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
```
looks like quite a bit of over-engineering for simple traversals as in the above example.
In oder to explain real power of the `Traversable` type class we will look at a more sophisticated example in this section.
The Unix utility `wc` is a good example for a traversal operation that performs several different tasks while traversing its input: The Unix utility `wc` is a good example for a traversal operation that performs several different tasks while traversing its input:
```bash ```bash
@ -712,8 +745,115 @@ For efficiency reasons this solution may be okay, but from a designers perspecti
So we would like to be able to isolate the different counting algorithms (*separation of concerns*) and be able to combine them in a way that provides efficient one-time traversal. So we would like to be able to isolate the different counting algorithms (*separation of concerns*) and be able to combine them in a way that provides efficient one-time traversal.
We start with the simple task of character counting:
```haskell
type Count = Const (Sum Integer)
count :: a -> Count b
count _ = Const 1
cciBody :: Char -> Count a
cciBody = count
cci :: String -> Count [a]
cci = traverse cciBody
-- and then in ghci:
> cci "hello world"
Const (Sum {getSum = 11})
```
For each character we just emit a `Const 1` which are elements of the `Sum Integer` monoid.
This allows automatic summation over all collected elements.
The next step of counting newlines looks similar:
```haskell
-- return (Sum 1) if true, else (Sum 0)
test :: Bool -> Sum Integer
test b = Sum $ if b then 1 else 0
-- use the test function to emit (Sum 1) only when a newline char is detected
lciBody :: Char -> Count a
lciBody c = Const $ test (c == '\n')
-- define the linecount using traverse
lci :: String -> Count [a]
lci = traverse lciBody
-- and the in ghci:
> lci "hello \n world"
Const (Sum {getSum = 1})
```
Now let's try to combine character counting and line counting.
In order to match the type declaration for `traverse`:
```haskell
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
```
we had to define `cciBody` and `lciBody` so that their return types are `Applicative Functors`.
The good news is that the product of two `Applicatives` is again an `Applicative` (the same holds true for Composition of `Applicatives`).
With this knowledge we can now use traverse to use the product of `cciBody` and `lciBody`:
```haskell
import Data.Functor.Product -- Product of Functors
-- define infix operator for building a Functor Product
(<#>) :: (Functor m, Functor n) => (a -> m b) -> (a -> n b) -> (a -> Product m n b)
(f <#> g) y = Pair (f y) (g y)
-- use a single traverse to apply the Product of cciBody and lciBody
clci :: String -> Product Count Count [a]
clci = traverse (cciBody <#> lciBody)
-- and then in ghci:
> clci "hello \n world"
Pair (Const (Sum {getSum = 13})) (Const (Sum {getSum = 1}))
```
So we have achieved our aim of separating line counting and character counting in separate functions while still being able to apply them in only one traversal.
The only piece missing is the word counting. This is a bit tricky as it involves dealing with a state monad and wrapping it as an Applicative Functor:
```haskell
import Data.Functor.Compose -- Composition of Functors
import Data.Functor.Const -- Const Functor
import Data.Functor.Identity -- Identity Functor (needed for coercion)
import Data.Monoid (Sum (..), getSum) -- Sum Monoid for Integers
import Control.Monad.State.Lazy -- State Monad
import Control.Applicative -- WrappedMonad (wrapping a Monad as Applicative Functor)
import Data.Coerce (coerce) -- Coercion (forcing types to match, when
-- their underlying representations are equal)
-- we use a (State Bool) monad to carry the 'isInWord' state through all invocations
-- WrappedMonad is used to use the monad as an Applicative Functor
-- This Applicative is then Composed with the actual Count a
wciBody :: Char -> Compose (WrappedMonad (State Bool)) Count a
wciBody c = coerce (updateState c) where
updateState :: Char -> Bool -> (Sum Integer, Bool)
updateState c w = let s = not(isSpace c) in (test (not w && s), s)
isSpace :: Char -> Bool
isSpace c = c == ' ' || c == '\n' || c == '\t'
-- using traverse to count words in a String
wci :: String -> Compose (WrappedMonad (State Bool)) Count [a]
wci = traverse wciBody
-- Forming the Product of character counting, line counting and word counting
-- and performing a one go traversal unsing this Functor product
clwci :: String -> (Product (Product Count Count) (Compose (WrappedMonad (State Bool)) Count)) [a]
clwci = traverse (cciBody <#> lciBody <#> wciBody)
-- the actual wordcount implementation.
-- for any String a triple of line count, word count, character count is returned
wc :: String -> (Integer, Integer, Integer)
wc str =
let raw = clwci str
cc = coerce $ pfst (pfst raw)
lc = coerce $ psnd (pfst raw)
wc = coerce $ evalState (unwrapMonad (getCompose (psnd raw))) False
in (lc,wc,cc)
-- and then in ghci:
> wc "hello \n world"
(1,2,13)
```
The wordcount example has been implemented according to ideas presented in the execellent paper The wordcount example has been implemented according to ideas presented in the execellent paper
[The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf). [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf).

View File

@ -42,12 +42,12 @@ cciBody = count
cci :: String -> Count [a] cci :: String -> Count [a]
cci = traverse cciBody cci = traverse cciBody
lciBody :: Char -> Count a
lciBody c = Const $ test (c == '\n')
test :: Bool -> Sum Integer test :: Bool -> Sum Integer
test b = Sum $ if b then 1 else 0 test b = Sum $ if b then 1 else 0
lciBody :: Char -> Count a
lciBody c = Const $ test (c == '\n')
lci :: String -> Count [a] lci :: String -> Count [a]
lci = traverse lciBody lci = traverse lciBody