Skip to content

Commit

Permalink
Merge pull request #10 from turion/more_koans
Browse files Browse the repository at this point in the history
More koans
  • Loading branch information
turion authored Jan 18, 2024
2 parents a825567 + ca8b11f commit 5b30b09
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 8 deletions.
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## CI

* Check cabal outdated
* Check in diffs between solution and problem and make sure it is stable (such that every fix in a solution gets propagated to the problem and vice versa)

# Content
## Basic
Expand Down Expand Up @@ -46,6 +47,9 @@
* Complicated clock errors
* Order of components (including a RB) is not the same as order of clocks in Rhine, this gives a weird error that `HasClock cl '[]` cannot be satisfied
* Write your own backend to some library
* Complicated Arrow syntax
* scoping of live and static values
* Nested if then else & do blocks

## Data analysis track

Expand Down
2 changes: 1 addition & 1 deletion koans/basic/2/3-count-the-lines/Koan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The 'count' signal function has internal state, the current count.
Every time it is called (because 'StdinClock' has ticked),
the count is incremented and returned.
-}
lineCount :: ClSF IO StdinClock () Integer
lineCount :: ClSF IO StdinClock () Int
lineCount = count -- This is part of the library!

-- | Print the number of the line that was just entered.
Expand Down
2 changes: 1 addition & 1 deletion koans/basic/2/3-count-the-lines/solution/Koan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The 'count' signal function has internal state, the current count.
Every time it is called (because 'StdinClock' has ticked),
the count is incremented and returned.
-}
lineCount :: ClSF IO StdinClock () Integer
lineCount :: ClSF IO StdinClock () Int
lineCount = count -- This is part of the library!

-- | Print the number of the line that was just entered.
Expand Down
9 changes: 7 additions & 2 deletions koans/basic/2/5-count-all-the-chars/Koan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ userInput = tagS
wordCount :: ClSF IO StdinClock () Int
wordCount = userInput >-> arr (Text.words >>> length)

-- | Output the number of characters of the line that was just entered.
{- | Output the number of characters of the line that was just entered.
The newline character is not part of 'userInput',
therefore +1 is added for it.
-}
charCount :: ClSF IO StdinClock () Int
charCount = userInput >-> arr Text.length
-- Yes, you can use >>> to compose ordinary functions as well!
charCount = userInput >-> arr (Text.length >>> (+ 1))

-- | Compute the sum of all input numbers so far, including the current one.
sumClSF :: (Monad m, Num a) => ClSF m cl a a
Expand Down
9 changes: 7 additions & 2 deletions koans/basic/2/5-count-all-the-chars/solution/Koan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ userInput = tagS
wordCount :: ClSF IO StdinClock () Int
wordCount = userInput >-> arr (Text.words >>> length)

-- | Output the number of characters of the line that was just entered.
{- | Output the number of characters of the line that was just entered.
The newline character is not part of 'userInput',
therefore +1 is added for it.
-}
charCount :: ClSF IO StdinClock () Int
charCount = userInput >-> arr Text.length
-- Yes, you can use >>> to compose ordinary functions as well!
charCount = userInput >-> arr (Text.length >>> (+ 1))

-- | Compute the sum of all input numbers so far, including the current one.
sumClSF :: (Monad m, Num a) => ClSF m cl a a
Expand Down
4 changes: 2 additions & 2 deletions koans/basic/2/5-count-all-the-chars/test/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ main :: IO ()
main = testForSecondsInput 1 testLines Koan.main $ \output ->
case output of
[] -> ["Weird, your program didn't produce any output!"]
_ | output == (tshow @Int <$> [2, 11, 5, 20, 6, 24]) -> []
_ | output == (tshow @Int <$> [2, 12, 5, 22, 6, 27]) -> []
_
| output == (tshow @Int <$> [11, 2, 20, 5, 24, 6]) ->
| output == (tshow @Int <$> [12, 2, 22, 5, 27, 6]) ->
["Nearly there, it seems you've swapped characters and words around."]
_ ->
[ "The program produced output, but it wasn't quite right."
Expand Down
70 changes: 70 additions & 0 deletions koans/basic/2/6-count-everything/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{- | Count everything!
If we can count words, characters, and lines, let's just put it all together.
This is a bit finicky, but give it a try nevertheless!
Remember the combinator for parallel composition:
@
(&&&) :: Monad m => ClSF m cl a b -> ClSF m cl a c -> ClSF m cl a (b, c)
@
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text qualified as Text (length, words)

-- rhine
import FRP.Rhine hiding (currentInput)

-- | A line of user input.
userInput :: ClSF IO StdinClock () Text
userInput = tagS

-- | Output the number of words of the line that was just entered.
wordCount :: ClSF IO StdinClock () Int
wordCount = userInput >-> arr (Text.words >>> length)

{- | Output the number of characters of the line that was just entered.
The newline character is not part of 'userInput',
therefore +1 is added for it.
-}
charCount :: ClSF IO StdinClock () Int
-- Yes, you can use >>> to compose ordinary functions as well!
charCount = userInput >-> arr (Text.length >>> (+ 1))

-- | Compute the sum of all input numbers so far, including the current one.
sumClSF :: (Monad m, Num a) => ClSF m cl a a
sumClSF = feedback 0 $ arr aggregator
where
aggregator :: (Num a) => (a, a) -> (a, a)
aggregator (currentInput, currentSum) =
let
nextSum = currentInput + currentSum
in
(nextSum, nextSum)

-- | The number of lines of input so far.
lineCount :: ClSF IO StdinClock () Integer
lineCount = count

-- | The number of words of input so far.
totalWordCount :: ClSF IO StdinClock () Int
totalWordCount = wordCount >-> sumClSF

-- | The number of characters of input so far.
totalCharCount :: ClSF IO StdinClock () Int
totalCharCount = charCount >-> sumClSF

-- | The number of total lines, words and characters so far.
totalCount :: ClSF IO StdinClock () _ -- What will the type of this be?
totalCount = _ &&& _ &&& _

-- | Print the number of total words and characters so far.
printAllCounts :: ClSF IO StdinClock () ()
-- On what do you need to pattern match here to bring lines_, words_ and chars into scope?
printAllCounts = totalCount >-> arrMCl (\_ -> print lines_ >> print words_ >> print chars)

main :: IO ()
main = flow $ printAllCounts @@ StdinClock
70 changes: 70 additions & 0 deletions koans/basic/2/6-count-everything/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{- | Count everything!
If we can count words, characters, and lines, let's just put it all together.
This is a bit finicky, but give it a try nevertheless!
Remember the combinator for parallel composition:
@
(&&&) :: Monad m => ClSF m cl a b -> ClSF m cl a c -> ClSF m cl a (b, c)
@
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text qualified as Text (length, words)

-- rhine
import FRP.Rhine hiding (currentInput)

-- | A line of user input.
userInput :: ClSF IO StdinClock () Text
userInput = tagS

-- | Output the number of words of the line that was just entered.
wordCount :: ClSF IO StdinClock () Int
wordCount = userInput >-> arr (Text.words >>> length)

{- | Output the number of characters of the line that was just entered.
The newline character is not part of 'userInput',
therefore +1 is added for it.
-}
charCount :: ClSF IO StdinClock () Int
-- Yes, you can use >>> to compose ordinary functions as well!
charCount = userInput >-> arr (Text.length >>> (+ 1))

-- | Compute the sum of all input numbers so far, including the current one.
sumClSF :: (Monad m, Num a) => ClSF m cl a a
sumClSF = feedback 0 $ arr aggregator
where
aggregator :: (Num a) => (a, a) -> (a, a)
aggregator (currentInput, currentSum) =
let
nextSum = currentInput + currentSum
in
(nextSum, nextSum)

-- | The number of lines of input so far.
lineCount :: ClSF IO StdinClock () Integer
lineCount = count

-- | The number of words of input so far.
totalWordCount :: ClSF IO StdinClock () Int
totalWordCount = wordCount >-> sumClSF

-- | The number of characters of input so far.
totalCharCount :: ClSF IO StdinClock () Int
totalCharCount = charCount >-> sumClSF

-- | The number of total lines, words and characters so far.
totalCount :: ClSF IO StdinClock () (Integer, (Int, Int)) -- What will the type of this be?
totalCount = lineCount &&& totalWordCount &&& totalCharCount

-- | Print the number of total words and characters so far.
printAllCounts :: ClSF IO StdinClock () ()
-- On what do you need to pattern match here to bring lines_, words_ and chars into scope?
printAllCounts = totalCount >-> arrMCl (\(lines_, (words_, chars)) -> print lines_ >> print words_ >> print chars)

main :: IO ()
main = flow $ printAllCounts @@ StdinClock
30 changes: 30 additions & 0 deletions koans/basic/2/6-count-everything/test/Test.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Main where

-- text
import Data.Text as Text

-- koan
import Koan qualified (main)

-- test-io
import TestIO

testLines :: [Text]
testLines =
[ "Hello Rhine"
, "this is a"
, "test"
]

main :: IO ()
main = testForSecondsInput 1 testLines Koan.main $ \output ->
case output of
[] -> ["Weird, your program didn't produce any output!"]
_ | output == (tshow @Int <$> [1, 2, 12, 2, 5, 22, 3, 6, 27]) -> []
_ ->
[ "The program produced output, but it wasn't quite right."
, "It received the following input:"
]
++ testLines
++ ["And it returned:"]
++ output
81 changes: 81 additions & 0 deletions koans/basic/2/7-count-everything-nicer/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{-# LANGUAGE CPP #-}

-- Disabling formatter and linter because it would fail on the syntax error otherwise.
#ifndef __HLINT__
{- FOURMOLU_DISABLE -}

-- Start reading here
-- vvvvvvvvvvvvvvvvvv

{- | Count everything nicer.
The last problem got quite verbose, and fiddling around with nested tuples isn't fun.
Fortunately, Haskell has a language extension that provides very useful syntax
for data flow constructs like signal functions!
It is called "arrow notation", and you can read a bit more about it here: https://www.haskell.org/arrows/.
Have a look how the code of the previous koan can be cleaned up with it.
-}
module Koan where

-- text
import Data.Text qualified as Text (length, words)

-- rhine
import FRP.Rhine hiding (currentInput)

-- | Compute the sum of all input numbers so far, including the current one.
sumClSF :: (Monad m, Num a) => ClSF m cl a a
sumClSF = feedback 0 $ arr aggregator
where
aggregator :: (Num a) => (a, a) -> (a, a)
aggregator (currentInput, currentSum) =
let
nextSum = currentInput + currentSum
in
(nextSum, nextSum)

-- | Print the number of total words and characters so far.
printAllCounts :: ClSF IO StdinClock () ()
-- proc is a keyword. Think of it like a lambda expression!
-- But why does GHC spit out a nasty parse error here?
-- Read through the following to find out!
printAllCounts = proc () -> do
-- This is nearly like do notation, except it also has syntax for input, the -<.

-- /------/--- Everything left from a <- is the output _signal_ of a signal function.
-- | | It is a value that can depend on the current tick of the clock.
-- | |
-- | | /--- Signal functions can be used between <- and -<.
-- | | |
-- | | | /--- This is the input to the signal function. (tagS needs none.)
-- | | | |
-- v v v v
userInput <- tagS -< ()

-- We can apply ordinary functions to signals.
let wordCount = length $ Text.words userInput
charCount = Text.length userInput + 1

lineCount <- count @Int -< ()

-- Signals can be inputs to signal functions.
-- This way we can aggregate signals.
totalWordCount <- sumClSF -< wordCount
totalCharCount <- sumClSF -< charCount

-- If a signal function has trivial output (), the <- is not needed.
arrMCl print -< lineCount
arrMCl print -< totalWordCount
arrMCl print -< _ -- Which one is missing here?

-- As you've seen, arrow notation introduces two new syntactic constructions,
-- the proc keyword an the -< operator.
-- You need to turn on a GHC language extension so that they can be parsed!
-- Can you uncomment the following line, and move to the top of the file?
-- {-# LANGUAGE Arrows #-}

main :: IO ()
main = flow $ printAllCounts @@ StdinClock

-- Ignore the next line ;)
#endif
Loading

0 comments on commit 5b30b09

Please sign in to comment.