parent
f435c644c2
commit
94d3695e64
27 changed files with 100200 additions and 0 deletions
@ -0,0 +1,2 @@ |
||||
# Intermission: Short Exercise |
||||
see addition/Addition.hs |
@ -0,0 +1,22 @@ |
||||
# Chapter Excercises |
||||
## Validating numbers into words |
||||
see src/wordnumbertest.hs |
||||
|
||||
## Using QuickCheck |
||||
see src/proptest.hs |
||||
|
||||
## Failure |
||||
This will fill due to precision. `sqrt 2` cannot be accurately represented, so it is rounded to a ration number. This rounding error causes a different when squaring it. |
||||
|
||||
## Idempotence |
||||
see src/idempotence.hs |
||||
|
||||
## Hangman testing |
||||
see hangman/test/tests.hs |
||||
|
||||
## Validating ciphers |
||||
No code available, but the principle is easy: |
||||
1. Create generator for the input (limited to a-z and A-Z) |
||||
2. Create generator for the caesar key (limited to 0-25) |
||||
3. Create generator for vigenere key (limited to a-z) |
||||
4. quickCheck the properties that decode after encode is the identity |
@ -0,0 +1,108 @@ |
||||
-- Addition.hs |
||||
module Addition where |
||||
|
||||
import Test.Hspec |
||||
import Test.QuickCheck |
||||
|
||||
sayHello :: IO () |
||||
sayHello = putStrLn "hello" |
||||
|
||||
main :: IO () |
||||
main = hspec $ do |
||||
describe "Addition" $ do |
||||
it "1 + 1 is greater than 1" $ do |
||||
((1 :: Integer) + 1) > 1 `shouldBe` True |
||||
it "2 + 2 is equal to 4" $ do |
||||
((2 :: Integer) + 2) `shouldBe` 4 |
||||
it "15 divided by 3 is 5" $ do |
||||
dividedBy (15 :: Integer) 3 `shouldBe` (5,0) |
||||
it "22 divided by 5 is 4 remainder 2" $ do |
||||
dividedBy (22 :: Integer) 5 `shouldBe` (4,2) |
||||
-- Intermission: Short Exercise |
||||
it "0 times 1 is 0" $ do |
||||
myMult (0 :: Integer) 1 `shouldBe` 0 |
||||
it "1 times 0 is 0" $ do |
||||
myMult (1 :: Integer) 0 `shouldBe` 0 |
||||
it "2 times 2 is 4" $ do |
||||
myMult (2 :: Integer) 2 `shouldBe` 4 |
||||
it "2 times (-2) is (-4)" $ do |
||||
myMult (2 :: Integer) (-2) `shouldBe` (-4) |
||||
it "(-2) times 2 is (-4)" $ do |
||||
myMult (-2 :: Integer) 2 `shouldBe` (-4) |
||||
it "(-2) times (-2) is 4" $ do |
||||
myMult (-2 :: Integer) (-2) `shouldBe` 4 |
||||
-- QuickCheck |
||||
it "x + 1 is always greater than x" $ do |
||||
property $ \x -> x + 1 > (x :: Int) |
||||
|
||||
|
||||
dividedBy :: Integral a => a -> a -> (a,a) |
||||
dividedBy num denom = go num denom 0 |
||||
where go n d count |
||||
| n < d = (count, n) |
||||
| otherwise = go (n - d) d (count + 1) |
||||
|
||||
-- Intermission: Short Exercise |
||||
myMult :: (Eq a, Num a) => a -> a -> a |
||||
myMult 0 _ = 0 |
||||
myMult _ 0 = 0 |
||||
myMult a b = |
||||
if abs b == b |
||||
then a + myMult a (b - 1) |
||||
else negate $ myMult a (negate b) |
||||
|
||||
trivialInt :: Gen Int |
||||
trivialInt = return 1 |
||||
|
||||
oneThroughThree :: Gen Int |
||||
oneThroughThree = elements [1, 2, 3] |
||||
|
||||
oneThroughThree' :: Gen Int |
||||
oneThroughThree' = elements [1, 2, 2, 2, 2, 2, 3] |
||||
|
||||
genBool :: Gen Bool |
||||
genBool = choose (False, True) |
||||
|
||||
genBool' :: Gen Bool |
||||
genBool' = elements [False, True] |
||||
|
||||
genOrdering :: Gen Ordering |
||||
genOrdering = elements [LT, EQ, GT] |
||||
|
||||
genChar :: Gen Char |
||||
genChar = elements ['a'..'z'] |
||||
|
||||
genTuple :: (Arbitrary a, Arbitrary b) => Gen (a,b) |
||||
genTuple = do |
||||
a <- arbitrary |
||||
b <- arbitrary |
||||
return (a,b) |
||||
|
||||
genThreeple :: (Arbitrary a, Arbitrary b, Arbitrary c) => Gen (a,b,c) |
||||
genThreeple = do |
||||
a <- arbitrary |
||||
b <- arbitrary |
||||
c <- arbitrary |
||||
return (a, b, c) |
||||
|
||||
genEither :: (Arbitrary a, Arbitrary b) => Gen (Either a b) |
||||
genEither = do |
||||
a <- arbitrary |
||||
b <- arbitrary |
||||
elements [Left a, Right b] |
||||
|
||||
genMaybe :: (Arbitrary a) => Gen (Maybe a) |
||||
genMaybe = do |
||||
a <- arbitrary |
||||
elements [Nothing, Just a] |
||||
|
||||
genMaybe' :: (Arbitrary a) => Gen (Maybe a) |
||||
genMaybe' = do |
||||
a <- arbitrary |
||||
frequency [ (1, return Nothing), (3, return (Just a)) ] |
||||
|
||||
prop_additionGreater :: Int -> Bool |
||||
prop_additionGreater x = x + 1 > x |
||||
|
||||
runQc :: IO () |
||||
runQc = quickCheck prop_additionGreater |
@ -0,0 +1,18 @@ |
||||
-- addition.cabal |
||||
name: addition |
||||
version: 0.1.0.0 |
||||
license-file: LICENSE |
||||
author: Gaël Depreeuw |
||||
maintainer: sky@isfalling.org |
||||
category: Text |
||||
build-type: Simple |
||||
cabal-version: >=1.10 |
||||
|
||||
library |
||||
exposed-modules: Addition |
||||
ghc-options: -Wall -fwarn-tabs |
||||
build-depends: base >=4.7 && <5, |
||||
hspec, |
||||
QuickCheck |
||||
hs-source-dirs: . |
||||
default-language: Haskell2010 |
@ -0,0 +1,66 @@ |
||||
# This file was automatically generated by 'stack init' |
||||
# |
||||
# Some commonly used options have been documented as comments in this file. |
||||
# For advanced use and comprehensive documentation of the format, please see: |
||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/ |
||||
|
||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
||||
# A snapshot resolver dictates the compiler version and the set of packages |
||||
# to be used for project dependencies. For example: |
||||
# |
||||
# resolver: lts-3.5 |
||||
# resolver: nightly-2015-09-21 |
||||
# resolver: ghc-7.10.2 |
||||
# resolver: ghcjs-0.1.0_ghc-7.10.2 |
||||
# resolver: |
||||
# name: custom-snapshot |
||||
# location: "./custom-snapshot.yaml" |
||||
resolver: lts-9.10 |
||||
|
||||
# User packages to be built. |
||||
# Various formats can be used as shown in the example below. |
||||
# |
||||
# packages: |
||||
# - some-directory |
||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
||||
# - location: |
||||
# git: https://github.com/commercialhaskell/stack.git |
||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# extra-dep: true |
||||
# subdirs: |
||||
# - auto-update |
||||
# - wai |
||||
# |
||||
# A package marked 'extra-dep: true' will only be built if demanded by a |
||||
# non-dependency (i.e. a user package), and its test suites and benchmarks |
||||
# will not be run. This is useful for tweaking upstream packages. |
||||
packages: |
||||
- . |
||||
# Dependency packages to be pulled from upstream that are not in the resolver |
||||
# (e.g., acme-missiles-0.3) |
||||
extra-deps: [] |
||||
|
||||
# Override default flag values for local packages and extra-deps |
||||
flags: {} |
||||
|
||||
# Extra package databases containing global packages |
||||
extra-package-dbs: [] |
||||
|
||||
# Control whether we use the GHC we find on the path |
||||
# system-ghc: true |
||||
# |
||||
# Require a specific version of stack, using version ranges |
||||
# require-stack-version: -any # Default |
||||
# require-stack-version: ">=1.5" |
||||
# |
||||
# Override the architecture used by stack, especially useful on Windows |
||||
# arch: i386 |
||||
# arch: x86_64 |
||||
# |
||||
# Extra directories used by stack for building |
||||
# extra-include-dirs: [/path/to/dir] |
||||
# extra-lib-dirs: [/path/to/dir] |
||||
# |
||||
# Allow a newer minor version of GHC than the snapshot specifies |
||||
# compiler-check: newer-minor |
@ -0,0 +1,30 @@ |
||||
Copyright Author name here (c) 2017 |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following |
||||
disclaimer in the documentation and/or other materials provided |
||||
with the distribution. |
||||
|
||||
* Neither the name of Author name here nor the names of other |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1 @@ |
||||
# hangman |
@ -0,0 +1,2 @@ |
||||
import Distribution.Simple |
||||
main = defaultMain |
@ -0,0 +1,10 @@ |
||||
module Main where |
||||
|
||||
import Hangman |
||||
import Data.Char (toLower) |
||||
|
||||
main :: IO () |
||||
main = do |
||||
word <- randomWord' |
||||
let puzzle = freshPuzzle (fmap toLower word) |
||||
runGame puzzle |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@ |
||||
name: hangman |
||||
version: 0.1.0.0 |
||||
-- synopsis: |
||||
-- description: |
||||
homepage: https://github.com/githubuser/hangman#readme |
||||
license: BSD3 |
||||
license-file: LICENSE |
||||
author: Gaël Depreeuw |
||||
maintainer: example@example.com |
||||
copyright: 2017 Gaël Depreeuw |
||||
category: Game |
||||
build-type: Simple |
||||
cabal-version: >=1.10 |
||||
extra-source-files: README.md, |
||||
data/dict.txt |
||||
|
||||
library |
||||
exposed-modules: Hangman |
||||
hs-source-dirs: src |
||||
ghc-options: -Wall -fwarn-tabs |
||||
build-depends: base >= 4.7 && < 5 |
||||
, random |
||||
, split |
||||
default-language: Haskell2010 |
||||
|
||||
executable hangman |
||||
hs-source-dirs: app |
||||
main-is: Main.hs |
||||
default-language: Haskell2010 |
||||
build-depends: base >= 4.7 && < 5 |
||||
, hangman |
||||
test-suite tests |
||||
type: exitcode-stdio-1.0 |
||||
hs-source-dirs: test |
||||
main-is: tests.hs |
||||
build-depends: base |
||||
, hangman |
||||
, hspec |
||||
ghc-options: -Wall -fno-warn-orphans |
||||
default-language: Haskell2010 |
@ -0,0 +1,118 @@ |
||||
module Hangman where |
||||
|
||||
import Control.Monad (forever) |
||||
import Data.Char (isAlpha) |
||||
import Data.Maybe (isJust) |
||||
import Data.List (intersperse) |
||||
import System.Exit (exitSuccess) |
||||
import System.Random (randomRIO) |
||||
|
||||
newtype WordList = WordList [String] deriving (Eq, Show) |
||||
|
||||
allWords :: IO WordList |
||||
allWords = do |
||||
dict <- readFile "data/dict.txt" |
||||
return (WordList $ lines dict) |
||||
|
||||
minWordLength :: Int |
||||
minWordLength = 5 |
||||
|
||||
maxWordLength :: Int |
||||
maxWordLength = 9 |
||||
|
||||
maxWrongGuesses :: Int |
||||
maxWrongGuesses = 10 |
||||
|
||||
gameWords :: IO WordList |
||||
gameWords = do |
||||
WordList aw <- allWords |
||||
return $ WordList (filter gameLength aw) |
||||
where gameLength w = |
||||
let l = length (w :: String) |
||||
in l >= minWordLength && l < maxWordLength |
||||
&& all isAlpha w |
||||
|
||||
randomWord :: WordList -> IO String |
||||
randomWord (WordList wl) = do |
||||
randomIndex <- randomRIO (0, length wl - 1) |
||||
return $ wl !! randomIndex |
||||
|
||||
randomWord' :: IO String |
||||
randomWord' = gameWords >>= randomWord |
||||
|
||||
data Puzzle = Puzzle String [Maybe Char] [Char] Int deriving (Eq) |
||||
instance Show Puzzle where |
||||
show (Puzzle _ discovered guessed i) = |
||||
(intersperse ' ' $ fmap renderPuzzleChar discovered) |
||||
++ " Guessed so far: " ++ guessed ++ "\n" |
||||
++ "Wrong guesses so far: " ++ show i |
||||
|
||||
freshPuzzle :: String -> Puzzle |
||||
freshPuzzle s = Puzzle s l [] 0 |
||||
where l = fmap (const Nothing) s |
||||
|
||||
charInWord :: Puzzle -> Char -> Bool |
||||
charInWord (Puzzle s _ _ _) c = elem c s |
||||
|
||||
alreadyGuessed :: Puzzle -> Char -> Bool |
||||
alreadyGuessed (Puzzle _ _ s _) c = elem c s |
||||
|
||||
renderPuzzleChar :: Maybe Char -> Char |
||||
renderPuzzleChar Nothing = '_' |
||||
renderPuzzleChar (Just c) = c |
||||
|
||||
fillInCharacter :: Puzzle -> Char -> Puzzle |
||||
fillInCharacter (Puzzle word filledInSoFar s i) c = |
||||
Puzzle word newFilledInSoFar (c:s) i |
||||
where newFilledInSoFar = zipWith (zipper c) word filledInSoFar |
||||
zipper guessed wordChar guessChar = |
||||
if wordChar == guessed |
||||
then Just wordChar |
||||
else guessChar |
||||
|
||||
handleWrongGuess :: Puzzle -> Puzzle |
||||
handleWrongGuess (Puzzle w f g i) = |
||||
Puzzle w f g (i+1) |
||||
|
||||
handleGuess :: Puzzle -> Char -> IO Puzzle |
||||
handleGuess puzzle guess = do |
||||
putStrLn $ "Your guess was: " ++ [guess] |
||||
case (charInWord puzzle guess, alreadyGuessed puzzle guess) of |
||||
(_, True) -> do |
||||
putStrLn "You already guessed that character, pick something else!" |
||||
return puzzle |
||||
(True, _) -> do |
||||
putStrLn "This character was in the word, filling in the word\ |
||||
\ accordingly" |
||||
return (fillInCharacter puzzle guess) |
||||
(False, _) -> do |
||||
putStrLn "This character wasn't in the word, try again." |
||||
return $ handleWrongGuess (fillInCharacter puzzle guess) |
||||
|
||||
gameOver :: Puzzle -> Int -> IO () |
||||
gameOver (Puzzle wordToGuess _ _ i) maxWrong = |
||||
if i >= maxWrong then |
||||
do putStrLn "You lose" |
||||
putStrLn $ "The word was: " ++ wordToGuess |
||||
exitSuccess |
||||
else return () |
||||
|
||||
gameWin :: Puzzle -> IO () |
||||
gameWin (Puzzle _ filledInSoFar _ _) = |
||||
if all isJust filledInSoFar then |
||||
do putStrLn "You win!" |
||||
exitSuccess |
||||
else return () |
||||
|
||||
runGame :: Puzzle -> IO () |
||||
runGame puzzle = forever $ do |
||||
gameOver puzzle maxWrongGuesses |
||||
gameWin puzzle |
||||
putStrLn $ "Current puzzle is: " ++ show puzzle |
||||
putStr "Guess a letter: " |
||||
guess <- getLine |
||||
case guess of |
||||
[c] -> handleGuess puzzle c >>= runGame |
||||
_ -> putStrLn "Your guess must be a single character" |
||||
|
||||
|
@ -0,0 +1,66 @@ |
||||
# This file was automatically generated by 'stack init' |
||||
# |
||||
# Some commonly used options have been documented as comments in this file. |
||||
# For advanced use and comprehensive documentation of the format, please see: |
||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/ |
||||
|
||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
||||
# A snapshot resolver dictates the compiler version and the set of packages |
||||
# to be used for project dependencies. For example: |
||||
# |
||||
# resolver: lts-3.5 |
||||
# resolver: nightly-2015-09-21 |
||||
# resolver: ghc-7.10.2 |
||||
# resolver: ghcjs-0.1.0_ghc-7.10.2 |
||||
# resolver: |
||||
# name: custom-snapshot |
||||
# location: "./custom-snapshot.yaml" |
||||
resolver: lts-9.10 |
||||
|
||||
# User packages to be built. |
||||
# Various formats can be used as shown in the example below. |
||||
# |
||||
# packages: |
||||
# - some-directory |
||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
||||
# - location: |
||||
# git: https://github.com/commercialhaskell/stack.git |
||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# extra-dep: true |
||||
# subdirs: |
||||
# - auto-update |
||||
# - wai |
||||
# |
||||
# A package marked 'extra-dep: true' will only be built if demanded by a |
||||
# non-dependency (i.e. a user package), and its test suites and benchmarks |
||||
# will not be run. This is useful for tweaking upstream packages. |
||||
packages: |
||||
- . |
||||
# Dependency packages to be pulled from upstream that are not in the resolver |
||||
# (e.g., acme-missiles-0.3) |
||||
extra-deps: [] |
||||
|
||||
# Override default flag values for local packages and extra-deps |
||||
flags: {} |
||||
|
||||
# Extra package databases containing global packages |
||||
extra-package-dbs: [] |
||||
|
||||
# Control whether we use the GHC we find on the path |
||||
# system-ghc: true |
||||
# |
||||
# Require a specific version of stack, using version ranges |
||||
# require-stack-version: -any # Default |
||||
# require-stack-version: ">=1.5" |
||||
# |
||||
# Override the architecture used by stack, especially useful on Windows |
||||
# arch: i386 |
||||
# arch: x86_64 |
||||
# |
||||
# Extra directories used by stack for building |
||||
# extra-include-dirs: [/path/to/dir] |
||||
# extra-lib-dirs: [/path/to/dir] |
||||
# |
||||
# Allow a newer minor version of GHC than the snapshot specifies |
||||
# compiler-check: newer-minor |
@ -0,0 +1,53 @@ |
||||
module Main where |
||||
|
||||
import Hangman |
||||
import Test.Hspec |
||||
|
||||
testPuzzle :: Puzzle |
||||
testPuzzle = Puzzle "test" (take 4 $ repeat Nothing) "" 0 |
||||
|
||||
correctChar :: Char |
||||
correctChar = 't' |
||||
|
||||
guessCorrect :: Puzzle |
||||
guessCorrect = |
||||
Puzzle "test" |
||||
[Just correctChar, Nothing, Nothing, Just correctChar] |
||||
[correctChar] |
||||
0 |
||||
|
||||
wrongChar :: Char |
||||
wrongChar = 'a' |
||||
|
||||
guessWrong :: Puzzle |
||||
guessWrong = |
||||
Puzzle "test" |
||||
[Nothing, Nothing, Nothing, Nothing] |
||||
[wrongChar] |
||||
0 |
||||
|
||||
test_fillInCharacter :: IO () |
||||
test_fillInCharacter = hspec $ do |
||||
describe "fillInCharacter" $ do |
||||
it "Wrong guess only adds to guesses" $ do |
||||
fillInCharacter testPuzzle wrongChar |
||||
== guessWrong |
||||
it "Right guess updates guesses and characters guessed so far" $ do |
||||
fillInCharacter testPuzzle correctChar |
||||
== guessCorrect |
||||
|
||||
-- I feel like I don't grasp enough of the language yet |
||||
-- to propery do this. I can't compare IO Puzzle with Puzzle |
||||
-- and using <- to bind the Puzzle doesn't work yet. |
||||
-- test_handleGuess :: IO () |
||||
-- test_handleGuess = hspec $ do |
||||
-- describe "handleGuess" $ do |
||||
-- it "Wrong guess only adds to guess" $ do |
||||
-- p <- handleGuess testPuzzle wrongChar |
||||
-- p == guessWrong |
||||
|
||||
main :: IO () |
||||
main = do |
||||
return () |
||||
test_fillInCharacter |
||||
-- test_handleGuess |
@ -0,0 +1,30 @@ |
||||
Copyright Author name here (c) 2017 |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following |
||||
disclaimer in the documentation and/or other materials provided |
||||
with the distribution. |
||||
|
||||
* Neither the name of Author name here nor the names of other |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1 @@ |
||||
# morse |
@ -0,0 +1,2 @@ |
||||
import Distribution.Simple |
||||
main = defaultMain |
@ -0,0 +1,48 @@ |
||||
name: morse |
||||
version: 0.1.0.0 |
||||
-- synopsis: |
||||
-- description: |
||||
homepage: https://github.com/githubuser/morse#readme |
||||
license: BSD3 |
||||
license-file: LICENSE |
||||
author: Gaël Depreeuw |
||||
maintainer: example@example.com |
||||
copyright: 2017 Gaël Depreeuw |
||||
category: Web |
||||
build-type: Simple |
||||
extra-source-files: README.md |
||||
cabal-version: >=1.10 |
||||
|
||||
library |
||||
exposed-modules: Morse |
||||
hs-source-dirs: src |
||||
ghc-options: -Wall -fwarn-tabs |
||||
build-depends: base >= 4.7 && < 5 |
||||
, containers |
||||
, QuickCheck |
||||
default-language: Haskell2010 |
||||
|
||||
executable morse |
||||
hs-source-dirs: src |
||||
main-is: Main.hs |
||||
ghc-options: -Wall -fwarn-tabs |
||||
build-depends: base >=4.7 && <5 |
||||
, morse |
||||
, containers |
||||
, QuickCheck |
||||
default-language: Haskell2010 |
||||
|
||||
test-suite tests |
||||
type: exitcode-stdio-1.0 |
||||
hs-source-dirs: test |
||||
main-is: tests.hs |
||||
build-depends: base |
||||
, containers |
||||
, morse |
||||
, QuickCheck |
||||
ghc-options: -Wall -fno-warn-orphans |
||||
default-language: Haskell2010 |
||||
|
||||
source-repository head |
||||
type: git |
||||
location: https://github.com/githubuser/morse |
@ -0,0 +1,55 @@ |
||||
module Main where |
||||
|
||||
import Control.Monad (forever, when) |
||||
import Data.List (intercalate) |
||||
import Data.Traversable (traverse) |
||||
import Morse (stringToMorse, morseToChar) |
||||
import System.Environment (getArgs) |
||||
import System.Exit (exitFailure, exitSuccess) |
||||
import System.IO (hGetLine, hIsEOF, stdin) |
||||
|
||||
convertToMorse :: IO () |
||||
convertToMorse = forever $ do |
||||
weAreDone <- hIsEOF stdin |
||||
when weAreDone exitSuccess |
||||
-- otherwise, proceed. |
||||
line <- hGetLine stdin |
||||
convertLine line |
||||
where convertLine line = do |
||||
let morse = stringToMorse line |
||||
case morse of |
||||
(Just str) -> putStrLn (intercalate " " str) |
||||
Nothing -> do |
||||
putStrLn $ "ERROR: " ++ line |
||||
exitFailure |
||||
|
||||
convertFromMorse :: IO () |
||||
convertFromMorse = forever $ do |
||||
weAreDone <- hIsEOF stdin |
||||
when weAreDone exitSuccess |
||||
-- otherwise, proceed. |
||||
line <- hGetLine stdin |
||||
convertLine line |
||||
where convertLine line = do |
||||
let decoded :: Maybe String |
||||
decoded = traverse morseToChar (words line) |
||||
case decoded of |
||||
(Just s) -> putStrLn s |
||||
Nothing -> do |
||||
putStrLn $ "Error: " ++ line |
||||
exitFailure |
||||
|
||||
main :: IO () |
||||
main = do |
||||
mode <- getArgs |
||||
case mode of |
||||
[arg] -> |
||||
case arg of |
||||
"from" -> convertFromMorse |
||||
"to" -> convertToMorse |
||||
_ -> argError |
||||
_ -> argError |
||||
where argError = do |
||||
putStrLn "Please specify the first argument as being\ |
||||
\'from' or 'to' morse, such as: morse to" |
||||
exitFailure |
@ -0,0 +1,65 @@ |
||||
module Morse |
||||
( Morse |
||||
, charToMorse |
||||
, morseToChar |
||||
, stringToMorse |
||||
, letterToMorse |
||||
, morseToLetter |
||||
) where |
||||
|
||||
import qualified Data.Map as M |
||||
|
||||
type Morse = String |
||||
|
||||
letterToMorse :: (M.Map Char Morse) |
||||
letterToMorse = M.fromList [ |
||||
('a', ".-") |
||||
, ('b', "-...") |
||||
, ('c', "-.-.") |
||||
, ('d', "-..") |
||||
, ('e', ".") |
||||
, ('f', "..-.") |
||||
, ('g', "--.") |
||||
, ('h', "....") |
||||
, ('i', "..") |
||||
, ('j', ".---") |
||||
, ('k', "-.-") |
||||
, ('l', ".-..") |
||||
, ('m', "--") |
||||
, ('n', "-.") |
||||
, ('o', "---") |
||||
, ('p', ".--.") |
||||
, ('q', "--.-") |
||||
, ('r', ".-.") |
||||
, ('s', "...") |
||||
, ('t', "-") |
||||
, ('u', "..-") |
||||
, ('v', "...-") |
||||
, ('w', ".--") |
||||
, ('x', "-..-") |
||||
, ('y', "-.--") |
||||
, ('z', "--..") |
||||
, ('1', ".----") |
||||
, ('2', "..---") |
||||
, ('3', "...--") |
||||
, ('4', "....-") |
||||
, ('5', ".....") |
||||
, ('6', "-....") |
||||
, ('7', "--...") |
||||
, ('8', "---..") |
||||
, ('9', "----.") |
||||
, ('0', "-----") |
||||
] |
||||
|
||||
morseToLetter :: M.Map Morse Char |
||||
morseToLetter = |
||||
M.foldWithKey (flip M.insert) M.empty letterToMorse |
||||
|
||||
charToMorse :: Char -> Maybe Morse |
||||
charToMorse c = M.lookup c letterToMorse |
||||
|
||||
stringToMorse :: String -> Maybe [Morse] |
||||
stringToMorse = sequence . (fmap charToMorse) |
||||
|
||||
morseToChar :: Morse -> Maybe Char |
||||
morseToChar m = M.lookup m morseToLetter |
@ -0,0 +1,66 @@ |
||||
# This file was automatically generated by 'stack init' |
||||
# |
||||
# Some commonly used options have been documented as comments in this file. |
||||
# For advanced use and comprehensive documentation of the format, please see: |
||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/ |
||||
|
||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
||||
# A snapshot resolver dictates the compiler version and the set of packages |
||||
# to be used for project dependencies. For example: |
||||
# |
||||
# resolver: lts-3.5 |
||||
# resolver: nightly-2015-09-21 |
||||
# resolver: ghc-7.10.2 |
||||
# resolver: ghcjs-0.1.0_ghc-7.10.2 |
||||
# resolver: |
||||
# name: custom-snapshot |
||||
# location: "./custom-snapshot.yaml" |
||||
resolver: lts-9.10 |
||||
|
||||
# User packages to be built. |
||||
# Various formats can be used as shown in the example below. |
||||
# |
||||
# packages: |
||||
# - some-directory |
||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
||||
# - location: |
||||
# git: https://github.com/commercialhaskell/stack.git |
||||
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||
# extra-dep: true |
||||
# subdirs: |
||||
# - auto-update |
||||
# - wai |
||||
# |
||||
# A package marked 'extra-dep: true' will only be built if demanded by a |
||||
# non-dependency (i.e. a user package), and its test suites and benchmarks |
||||
# will not be run. This is useful for tweaking upstream packages. |
||||
packages: |
||||
- . |
||||
# Dependency packages to be pulled from upstream that are not in the resolver |
||||
# (e.g., acme-missiles-0.3) |
||||
extra-deps: [] |
||||
|
||||
# Override default flag values for local packages and extra-deps |
||||
flags: {} |
||||
|
||||
# Extra package databases containing global packages |
||||
extra-package-dbs: [] |
||||
|
||||
# Control whether we use the GHC we find on the path |
||||
# system-ghc: true |
||||
# |
||||
# Require a specific version of stack, using version ranges |
||||
# require-stack-version: -any # Default |
||||
# require-stack-version: ">=1.5" |
||||
# |
||||
# Override the architecture used by stack, especially useful on Windows |
||||
# arch: i386 |
||||
# arch: x86_64 |
||||
# |
||||
# Extra directories used by stack for building |
||||
# extra-include-dirs: [/path/to/dir] |
||||
# extra-lib-dirs: [/path/to/dir] |
||||
# |
||||
# Allow a newer minor version of GHC than the snapshot specifies |
||||
# compiler-check: newer-minor |
@ -0,0 +1,26 @@ |
||||
module Main where |
||||
|
||||
import qualified Data.Map as M |
||||
import Morse |
||||
import Test.QuickCheck |
||||
|
||||
allowedChars :: [Char] |
||||
allowedChars = M.keys letterToMorse |
||||
|
||||
allowedMorse :: [Morse] |
||||
allowedMorse = M.elems letterToMorse |
||||
|
||||
charGen :: Gen Char |
||||
charGen = elements allowedChars |
||||
|
||||
morseGen :: Gen Morse |
||||
morseGen = elements allowedMorse |
||||
|
||||
prop_thereAndBackAgain :: Property |
||||
prop_thereAndBackAgain = |
||||
forAll charGen |
||||
(\c -> ((charToMorse c) |
||||
>>= morseToChar ) == Just c) |
||||
|
||||
main :: IO () |
||||
main = quickCheck prop_thereAndBackAgain |
@ -0,0 +1,31 @@ |
||||
module Idempotence where |
||||
|
||||
import Test.QuickCheck |
||||
import Data.List (sort) |
||||
import Data.Char (toUpper) |
||||
|
||||
twice :: (a -> a) -> a -> a |
||||
twice f = f . f |
||||
|
||||
fourTimes :: (a -> a) -> a -> a |
||||
fourTimes = twice . twice |
||||
|
||||
capitalizeWord :: String -> String |
||||
capitalizeWord "" = "" |
||||
capitalizeWord (x:xs) = (toUpper x) : xs |
||||
|
||||
-- 1 |
||||
prop_cap :: String -> Bool |
||||
prop_cap s = (capitalizeWord s == twice capitalizeWord s) |
||||
&& |
||||
(capitalizeWord s == fourTimes capitalizeWord s) |
||||
-- 2 |
||||
prop_sort :: (Ord a, Eq a) => [a] -> Bool |
||||
prop_sort xs = (sort xs == twice sort xs) |
||||
&& |
||||
(sort xs == fourTimes sort xs) |
||||
|
||||
main :: IO () |
||||
main = do |
||||
quickCheck prop_cap |
||||
quickCheck (prop_sort :: String -> Bool) |
@ -0,0 +1,14 @@ |
||||
module MakeGen where |
||||
|
||||
import Test.QuickCheck |
||||
|
||||
-- 1 |
||||
data Fool = Fulse | Frue deriving (Show, Eq) |
||||
|
||||
equal_foolGen :: Gen Fool |
||||
equal_foolGen = elements [Fulse, Frue] |
||||
|
||||
-- 2 |
||||
skewed_foolGen :: Gen Fool |
||||
skewed_foolGen = frequency [ (2, return Fulse), (1, return Frue) ] |
||||
|
@ -0,0 +1,109 @@ |
||||
module PropTest where |
||||
|
||||
import Test.QuickCheck |
||||
import Test.QuickCheck.Modifiers (NonZero) |
||||
import Test.QuickCheck.Function |
||||
import Data.List (sort) |
||||
|
||||
-- Note: |
||||
-- quickCheck takes a type of the Testable typeclass |
||||
-- Arbitrary a => a -> Bool is an instance of this, since |
||||
-- Bool is also Testable. |
||||
|
||||
-- 1 |
||||
half :: Fractional a => a -> a |
||||
half = (/2) |
||||
halfIdentity :: Fractional a => a -> a |
||||
halfIdentity = (*2) . half |
||||
|
||||
prop_half :: (Eq a, Fractional a) => a -> Bool |
||||
prop_half = \x -> halfIdentity x == x |
||||
|
||||
-- 2 |
||||
listOrdered :: (Ord a) => [a] -> Bool |
||||
listOrdered xs = |
||||
snd $ foldr go (Nothing, True) xs |
||||
where go _ status@(_,False) = status |
||||
go y (Nothing, t) = (Just y, t) |
||||
go y (Just x, _) = (Just y, x >= y) |
||||
prop_sort :: Ord a => [a] -> Bool |
||||
prop_sort = listOrdered . sort |
||||
|
||||
-- 3 |
||||
plusAssociative :: (Num a, Eq a) => a -> a -> a -> Bool |
||||
plusAssociative x y z = x + (y + z) == (x + y) + z |
||||
plusCommutative :: (Num a, Eq a) => a -> a -> Bool |
||||
plusCommutative x y = x + y == y + x |
||||
|
||||
-- 4 |
||||
multAssociative :: (Num a, Eq a) => a -> a -> a -> Bool |
||||
multAssociative x y z = x * (y * z) == (x * y) * z |
||||
multCommutative :: (Num a, Eq a) => a -> a -> Bool |
||||
multCommutative x y = x * y == y * x |
||||
|
||||
-- 5 |
||||
prop_quotRem :: (Eq a, Integral a) => a -> a -> Bool |
||||
prop_quotRem _ 0 = True -- exclude 0 from the test |
||||
prop_quotRem x y = (quot x y)*y + (rem x y) == x |
||||
prop_divMod :: (Eq a, Integral a) => a -> a -> Bool |
||||
prop_divMod _ 0 = True -- exclude 0 from the test |
||||
prop_divMod x y = (div x y)*y + (mod x y) == x |
||||
-- other solution would be the create a generator for Int or Integers or ... |
||||
-- which excludes 0 |
||||
-- OR use the modifiers from quickcheck |
||||
prop_quotRem' :: (Eq a, Integral a) => NonZero a -> NonZero a -> Bool |
||||
prop_quotRem' (NonZero x) (NonZero y) = (quot x y)*y + (rem x y) == x |
||||
prop_divMod' :: (Eq a, Integral a) => NonZero a -> NonZero a -> Bool |
||||
prop_divMod' (NonZero x) (NonZero y) = (div x y)*y + (mod x y) == x |
||||
|
||||
-- 6 These will fail |
||||
prop_eAss :: (Eq a, Num a, Integral a) => a -> a -> a -> Bool |
||||
prop_eAss x y z = x ^ (y ^ z) == (x ^ y) ^ z |
||||
prop_eCom :: (Eq a, Num a, Integral a) => a -> a -> Bool |
||||
prop_eCom x y = x ^ y == y ^ x |
||||
|
||||
-- 7 |
||||
prop_reverse :: Eq a => [a] -> Bool |
||||
prop_reverse xs = (reverse . reverse) xs == id xs |
||||
|
||||
-- 8 |
||||
prop_apply :: Eq b => (Fun a b) -> a -> Bool |
||||
prop_apply (Fun _ f) a = (f $ a) == f a |
||||
prop_compose :: Eq c => (Fun b c) -> (Fun a b) -> a -> Bool |
||||
prop_compose (Fun _ f) (Fun _ g) a = (f . g) a == f (g a) |
||||
|
||||
-- 9 |
||||
prop_append :: Eq a => [a] -> [a] -> Bool |
||||
prop_append xs ys = foldr (:) xs ys == (++) xs ys |
||||
prop_concat :: Eq a => [[a]] -> Bool |
||||
prop_concat xs = foldr (++) [] xs == concat xs |
||||
|
||||
-- 10 |
||||
prop_length :: Int -> [a] -> Bool |
||||
prop_length n xs = length (take n xs) == n |
||||
|
||||
-- 11 |
||||
prop_readshow :: (Read a, Show a, Eq a) => a -> Bool |
||||
prop_readshow x = (read (show x)) == x |
||||
|
||||
main :: IO () |
||||
main = do |
||||
quickCheck (prop_half :: Double -> Bool) -- Float can also be checked |
||||
quickCheck (prop_sort :: [Int] -> Bool) -- Lots of other options available |
||||
quickCheck (plusAssociative :: Int -> Int -> Int -> Bool) |
||||
quickCheck (plusCommutative :: Integer -> Integer -> Bool) |
||||
quickCheck (multAssociative :: Int -> Int -> Int -> Bool) |
||||
quickCheck (multCommutative :: Integer -> Integer -> Bool) |
||||
quickCheck (prop_quotRem :: Integer -> Integer -> Bool) |
||||
quickCheck (prop_divMod :: Integer -> Integer -> Bool) |
||||
quickCheck (prop_quotRem' :: NonZero Integer -> NonZero Integer -> Bool) |
||||
quickCheck (prop_divMod' :: NonZero Integer -> NonZero Integer -> Bool) |
||||
quickCheck (prop_eAss :: Integer -> Integer -> Integer -> Bool) -- fails |
||||
quickCheck (prop_eCom :: Integer -> Integer -> Bool) -- fails |
||||
quickCheck (prop_reverse :: [Char] -> Bool) |
||||
quickCheck (prop_apply :: (Fun Integer Integer) -> Integer -> Bool) |
||||
quickCheck (prop_compose :: (Fun Int Int) -> (Fun Int Int) -> Int -> Bool) |
||||
quickCheck (prop_append :: [Char] -> [Char] -> Bool) |
||||
quickCheck (prop_concat :: [[Char]] -> Bool) |
||||
quickCheck (prop_length :: Int -> [String] -> Bool) |
||||
quickCheck (prop_readshow :: Int -> Bool) |
@ -0,0 +1,46 @@ |
||||
module WordNumberTest where |
||||
|
||||
import Test.Hspec |
||||
import Data.List (intersperse) |
||||
|
||||
digitToWord :: Int -> String |
||||
digitToWord 0 = "zero" |
||||
digitToWord 1 = "one" |
||||
digitToWord 2 = "two" |
||||
digitToWord 3 = "three" |
||||
digitToWord 4 = "four" |
||||
digitToWord 5 = "five" |
||||
digitToWord 6 = "six" |
||||
digitToWord 7 = "seven" |
||||
digitToWord 8 = "eight" |
||||
digitToWord 9 = "nine" |
||||
digitToWord _ = "" |
||||
|
||||
digits :: Int -> [Int] |
||||
digits n |
||||
| d == 0 = [r] |
||||
| otherwise = digits d ++ [r] |
||||
where (d,r) = divMod (abs n) 10 |
||||
|
||||
wordNumber :: Int -> String |
||||
wordNumber = concat . intersperse "-" . map digitToWord . digits |
||||
|
||||
main :: IO () |
||||
main = hspec $ do |
||||
describe "digitToWord" $ do |
||||
it "return zero for 0" $ do |
||||
digitToWord 0 `shouldBe` "zero" |
||||
it "returns one for 1" $ do |
||||
digitToWord 1 `shouldBe` "one" |
||||
-- should test all numbers |
||||
describe "digits" $ do |
||||
it "returns [1] for 1" $ do |
||||
digits 1 `shouldBe` [1] |
||||
it "returns [1, 0, 0] for 100" $ do |
||||
digits 100 `shouldBe` [1,0,0] |
||||
describe "wordNumber" $ do |
||||
it "one-zero-zero given 100" $ do |
||||
wordNumber 100 `shouldBe` "one-zero-zero" |
||||
it "nine-zero-zero-one for 9001" $ do |
||||
wordNumber 9001 `shouldBe` "nine-zero-zero-one" |
||||
|
Loading…
Reference in new issue