Skip to content

Commit

Permalink
Merge pull request #7 from turion/more_koans
Browse files Browse the repository at this point in the history
More koans
  • Loading branch information
turion authored Jan 16, 2024
2 parents 06bd0ae + 9186406 commit 9b3f6b9
Show file tree
Hide file tree
Showing 41 changed files with 637 additions and 80 deletions.
15 changes: 7 additions & 8 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
# Structure

* Should be numbered, but also have a short title
* Subfolders have numbers and names
* Every koan has an executable, and a test
* How to do koans for different backends? Separate packages? Or rather separate sublibraries, like basic, advanced etc.?
* The test should maybe show a green check mark
* Each Koan should have a Haddocked substructure
* If bug in HLS can't be solved, provide my own hie.yaml

## CI

* Separate branch where all koans are solved
* CI check to make sure the diff is restricted `Koan.hs` files in the `koans` subdirectory
* Formatter
* cabal outdated wöchentlich checken
* Check cabal outdated

# Content
## Basic

* stdin clock
* char count per line
* line count & total char count
* but summing char counts doesn't count newlines, so need all 3
* exception for eof & final summary
* cat a file in the tool
* secondly progress report
* using the current time
* behaviours: reusability across clocks
* E.g. revisit some of the earlier koans and refactor them
* infer clock interval from component, or vice versa? if not possible, add type signature?

* Arrow notation
Expand All @@ -36,6 +32,9 @@
* feedback, building up state, delay, sum
* exception handling for control flow

* common mistakes?
* arrM vs arrMCl

## Theory track

* LiftClock
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
29 changes: 29 additions & 0 deletions koans/basic/2/2-count-the-words/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{- | Count the words.
With any clock, you can treat the 'Tag' like any other data.
For example, you can apply any function the console input.
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text as Text (words)

-- rhine
import FRP.Rhine

-- | 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
-- Do you remember how to convert a pure function into a ClSF?
wordCount = userInput >-> _ (Text.words >>> length)

-- | Print the number of words of the line that was just entered.
printWordCount :: ClSF IO StdinClock () ()
printWordCount = wordCount >-> arrMCl print

main :: IO ()
main = flow $ printWordCount @@ StdinClock
29 changes: 29 additions & 0 deletions koans/basic/2/2-count-the-words/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{- | Count the words.
With any clock, you can treat the 'Tag' like any other data.
For example, you can apply any function the console input.
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text as Text (words)

-- rhine
import FRP.Rhine

-- | 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
-- Do you remember how to convert a pure function into a ClSF?
wordCount = userInput >-> arr (Text.words >>> length)

-- | Print the number of words of the line that was just entered.
printWordCount :: ClSF IO StdinClock () ()
printWordCount = wordCount >-> arrMCl print

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

-- text
import Data.Text (Text)
import Data.Text as Text (words)

-- 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 . length . Text.words <$> testLines) -> []
_ -> ["The program produced output, but the lines had the wrong lengths."]
26 changes: 26 additions & 0 deletions koans/basic/2/3-count-the-lines/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{- | Count the lines.
Signal functions can have information about the past by storing internal state.
For example, we can count the number of lines that have been entered so far.
-}
module Koan where

-- rhine
import FRP.Rhine

{- | The number of lines of input so far.
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 = count -- This is part of the library!

-- | Print the number of the line that was just entered.
printLineCount :: ClSF IO StdinClock () ()
printLineCount = lineCount >-> arrMCl print

main :: IO ()
-- Recap: Do you remember how to make a 'Rhine' from a 'ClSF'?
main = flow _
26 changes: 26 additions & 0 deletions koans/basic/2/3-count-the-lines/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{- | Count the lines.
Signal functions can have information about the past by storing internal state.
For example, we can count the number of lines that have been entered so far.
-}
module Koan where

-- rhine
import FRP.Rhine

{- | The number of lines of input so far.
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 = count -- This is part of the library!

-- | Print the number of the line that was just entered.
printLineCount :: ClSF IO StdinClock () ()
printLineCount = lineCount >-> arrMCl print

main :: IO ()
-- Recap: Do you remember how to make a 'Rhine' from a 'ClSF'?
main = flow $ printLineCount @@ StdinClock
24 changes: 24 additions & 0 deletions koans/basic/2/3-count-the-lines/test/Test.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Main where

-- text
import Data.Text (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 == take (length testLines) (tshow @Int <$> [1 ..]) -> []
_ -> ["The program produced output, but it wasn't quite right."]
67 changes: 67 additions & 0 deletions koans/basic/2/4-count-all-the-words/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{- | Count the all the words!
There are a number of ways how you can create internal state yourself.
One of them is 'feedback'. Let's look at its type signature:
@
feedback :: s -> ClSF m (a, s) (b, s) -> ClSF m a b
@
'feedback' takes an internal state @s@,
and a signal function with two inputs and two outputs.
The two inputs are @a@ and the current internal state @s@.
The two outputs are @b@ and the modified internal state.
'feedback' then hides this state, which is why at the end,
the return type is @ClSF m a b@.
If you call 'feedback', you can pass an arbitrary signal function as second argument.
It allows you to use and modify the internal state.
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text as Text (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)

{- | Compute the sum of all input numbers so far, including the current one.
/----------/--/-- Let's solve this problem in general, for any number type, any monad and any clock.
| | |
v v v
-}
sumClSF :: (Monad m, Num a) => ClSF m cl a a
sumClSF =
feedback -- We're using internal state
0 -- As long as no input has arrived, this is the internal state we start with
$ arr aggregator -- No side effects or further state needed: We manipulate the state with a pure function.
where
aggregator :: (Num a) => (a, a) -> (a, a)
aggregator (currentInput, currentSum) =
let
nextSum = _ -- What should be the state after a further line of input has arrived?
in
-- The missing part is the final output of the signal function.
-- If we have summed up to a certain number, what should it be?
(_, nextSum)

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

-- | Print the number of total words so far.
printTotalWordCount :: ClSF IO StdinClock () ()
printTotalWordCount = totalWordCount >-> arrMCl print

main :: IO ()
main = flow $ printTotalWordCount @@ StdinClock
67 changes: 67 additions & 0 deletions koans/basic/2/4-count-all-the-words/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{- | Count the all the words!
There are a number of ways how you can create internal state yourself.
One of them is 'feedback'. Let's look at its type signature:
@
feedback :: s -> ClSF m (a, s) (b, s) -> ClSF m a b
@
'feedback' takes an internal state @s@,
and a signal function with two inputs and two outputs.
The two inputs are @a@ and the current internal state @s@.
The two outputs are @b@ and the modified internal state.
'feedback' then hides this state, which is why at the end,
the return type is @ClSF m a b@.
If you call 'feedback', you can pass an arbitrary signal function as second argument.
It allows you to use and modify the internal state.
-}
module Koan where

-- text
import Data.Text (Text)
import Data.Text as Text (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)

{- | Compute the sum of all input numbers so far, including the current one.
/----------/--/-- Let's solve this problem in general, for any number type, any monad and any clock.
| | |
v v v
-}
sumClSF :: (Monad m, Num a) => ClSF m cl a a
sumClSF =
feedback -- We're using internal state
0 -- As long as no input has arrived, this is the internal state we start with
$ arr aggregator -- No side effects or further state needed: We manipulate the state with a pure function.
where
aggregator :: (Num a) => (a, a) -> (a, a)
aggregator (currentInput, currentSum) =
let
nextSum = currentInput + currentSum -- What should be the state after a further line of input has arrived?
in
-- The missing part is the final output of the signal function.
-- If we have summed up to a certain number, what should it be?
(nextSum, nextSum)

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

-- | Print the number of total words so far.
printTotalWordCount :: ClSF IO StdinClock () ()
printTotalWordCount = totalWordCount >-> arrMCl print

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

-- text
import Data.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 <$> [2, 5, 6]) -> []
_
| output == (tshow @Int <$> [0, 2, 5]) ->
[ "Your program seems to be counting the words, but only the past ones!"
, "Can you make sure it includes the current line as well?"
]
_ | output == (tshow @Int <$> [2, 3, 1]) -> ["Your program seems to be counting the words, but it doesn't return their sum!"]
_ -> ["The program produced output, but it wasn't quite right."]
Loading

0 comments on commit 9b3f6b9

Please sign in to comment.