-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from turion/more_koans
More koans
- Loading branch information
Showing
13 changed files
with
406 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.