Skip to content

Commit

Permalink
Add Koan basic-2-7
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel Bärenz committed Jan 18, 2024
1 parent 1485c3b commit 58e04db
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
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 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
71 changes: 71 additions & 0 deletions koans/basic/2/7-count-everything-nicer/solution/Koan.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{-# LANGUAGE Arrows #-}

{- | 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 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.
-- v v It is a value that can depend on the current tick of the clock.
-- v v
-- v v /--- Signal functions can be used between <- and -<.
-- v v v
-- v v v /--- This is the input to the signal function. (tagS needs none.)
-- v v v v
-- 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 -< totalCharCount

-- 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
30 changes: 30 additions & 0 deletions koans/basic/2/7-count-everything-nicer/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
16 changes: 16 additions & 0 deletions rhine-koans.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,19 @@ test-suite basic-2-6-count-everything-test
type: exitcode-stdio-1.0
main-is: Test.hs
hs-source-dirs: koans/basic/2/6-count-everything/test

common basic-2-7-count-everything-nicer
if flag(solution)
hs-source-dirs: koans/basic/2/7-count-everything-nicer/solution
else
hs-source-dirs: koans/basic/2/7-count-everything-nicer

executable basic-2-7-count-everything-nicer
import: exec, basic-2-7-count-everything-nicer
main-is: Main.hs

test-suite basic-2-7-count-everything-nicer-test
import: test, basic-2-7-count-everything-nicer
type: exitcode-stdio-1.0
main-is: Test.hs
hs-source-dirs: koans/basic/2/7-count-everything-nicer/test

0 comments on commit 58e04db

Please sign in to comment.